Skip to content

Conversation

@hardaira
Copy link

No description provided.

Copy link

@mateacademy-ai-mentor mateacademy-ai-mentor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

I am requesting changes because there are several critical issues that break required flows or violate explicit checklist requirements.

Critical blockers that must be fixed:

  1. Activation flow logic: In src/controllers/userController.js, the activation check around activationToken is inverted — you fetch by token and then treat a non‑null user.activationToken as “not activated”, which makes valid activation tokens fail. You need to treat activationToken === null (or a mismatch) as already activated/invalid and only allow activation when the token matches and the user is not yet activated.
  2. Missing password confirmation checks: The reset password controller and the change‑password controller accept only newPassword (and oldPassword for change) and ignore a confirmation field, but the task requires password and confirmation to be equal on reset and change. Add a confirmation parameter, check newPassword === confirmation, and also apply your password rules before saving in both flows.
  3. Route access control not enforced: In src/routes/userRoutes.js, registration, login, activation, and reset‑password routes are not restricted to non‑authenticated users, and profile change routes (/:id/change-name, /:id/change-password, /:id/change-email) and logout are not protected by authMiddleware and ownerMiddleware as required. You should (a) add a guest/non‑auth middleware to block authenticated users from the public auth flows and (b) re‑enable authMiddleware + ownerMiddleware for profile change and logout routes so only the authenticated owner can call them.
  4. authMiddleware doesn’t attach the user: In src/middlewares/authMiddleware.js you verify the token but never set req.user, while ownerMiddleware and profile controllers rely on req.user.id. After verifying, assign req.user = userData (or the fetched DB user) before calling next() so downstream code can correctly identify the authenticated user.
  5. Database initialization / transaction correctness: In src/db.js you import ./setup.js before exporting the Sequelize instance, and setup.js imports models that depend on that instance, creating a circular dependency and possible runtime failures. Also, userService.create in src/services/userService.js ignores the transaction passed from the controller, so user creation is not actually part of the transaction. Move model sync/init out of db.js (e.g., call an async init from index.js after Sequelize is created) and update userService.create to accept and forward { transaction } to User.create.

Other important but non‑blocking issues (fix if possible): add a 404 handler in createServer.js for unknown routes, avoid returning passwords in userService.getAll/getById, clear both access and refresh cookies on logout, and tidy up minor export inconsistencies and console.logs. Your overall structure (controllers/services/models) is solid and covers most flows; once you fix the critical logic, middleware usage, and transaction/DB setup issues, the implementation will align well with the task requirements.


✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨

Important

Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.

Tip

If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.

Caution

Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.

Footnotes

  1. Rate AI review example

  2. Re-request AI review example

  3. Add AI review comments example

return res.status(404).json({ message: 'User not found' });
}

if (user && user.activationToken !== null) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check uses activationToken !== null to decide whether to hide the user. Other parts of the code rely on user.activated. This is inconsistent — prefer checking user.activated (boolean) or use a single canonical approach everywhere to detect activation status.

return res.status(404).json({ message: 'User not found' });
}

if (user && user.activationToken !== null) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic is incorrect: you fetched the user by activation token, so user.activationToken will normally be the token (not null). The current conditional treats a non-null activationToken as "not activated" and returns 404, which will always fail for a valid activation token. You should instead return an error when the activation token is null or doesn't match; e.g. if user.activationToken === null => already activated / invalid.

return res.status(400).json({ message: 'All fields must be filled in ' });
}

if (password.length < 8) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The registration only enforces minimum length. The task requires informing users about password rules and checking them. Consider checking (and returning clear messages) for required rules (min length, maybe complexity) or at least clearly document the required rules in the response.

Comment on lines +346 to +371
export const resetUserPassword = async (req, res) => {
const { newPassword } = req.body;
const { resetToken } = req.params;

// 1️⃣ Hash the token from URL (we stored hashed in DB)
const hashedResetToken = crypto
.createHash('sha256')
.update(resetToken)
.digest('hex');

// 2️⃣ Find the user with valid token and not expired
const user = await User.findOne({
where: {
resetToken: hashedResetToken,
resetTokenExpires: {
[Op.gt]: new Date(),
},
},
});

if (!user) {
return res.status(400).json({ message: 'Invalid or expired token' });
}

// 3️⃣ Hash new password before saving
user.password = await bcrypt.hash(newPassword.trim(), 10);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resetUserPassword accepts only newPassword and does not validate a confirmation field or verify that the new password meets the password rules. The checklist requires the Reset Password confirmation page to have password and confirmation fields that must be equal — enforce that equality and apply password rules here on the backend.


export const changeUserPassword = async (req, res) => {
const { id } = req.params;
const { oldPassword, newPassword } = req.body;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change-password endpoint destructures only oldPassword and newPassword. The requirements state the change password flow must require old password, new password and confirmation. Add a confirmation field and verify newPassword === confirmation before saving.


export const changeUserEmail = async (req, res) => {
const { id } = req.params;
const { password, newEmail } = req.body;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When changing email the requirements specify the user should confirm the new email (i.e. provide newEmail twice/confirmation) and the OLD email must be notified. You notify the old email (good) but you do not require a confirmation of the new email. Add a newEmailConfirmation (or confirmNewEmail) and verify equality before changing, or better: do a new-email confirmation flow where the new email is verified before replacing the old one.

Comment on lines 382 to 384
export const changeUserName = async (req, res) => {

const { id } = req.params;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This controller method allows updating any user's name by id without verifying the authenticated user or permissions. The task requires the Profile page be available only to authenticated users; ensure this route is protected by auth middleware and also ensure the acting user can only modify their own profile (or authorize properly). If middleware already enforces auth, add a note or a check to ensure req.user.id === id where appropriate.

const user = await User.findOne({
where: { id },
});
console.log(`New name:{name}`)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.log uses a literal {name} inside template quotes instead of the variable. Use backtick interpolation: console.log(`New name: ${name}`) — right now it logs the literal text.

Comment on lines 491 to 504
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) {
return res.status(401).json({ message: 'No refresh token' });
}

const userData = jwtService.verifyRefresh(refreshToken);
const tokenFromDb = await tokenService.getByToken(refreshToken);

if (!userData || !tokenFromDb) {
return res.status(401).json({ message: 'You have to log in' });
}

await tokenService.remove(userData.id);
res.clearCookie('refreshToken');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logout clears only the refreshToken cookie but not the accessToken cookie; also you return 401 if no refresh token is present. The commented alternative returns 204 (no content) when no cookie exists which is usually preferable. Clear both cookies (with matching options/path) and remove the token by token value or user id consistently. Ensure the cookie clear uses the same path/options used when setting the cookie so it is actually removed by the browser.

import cors from 'cors';
// Function to create the Express server
export const createServer = () => {
// dotenv.config();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have import 'dotenv/config' at the top and a commented dotenv.config() on line 10. import 'dotenv/config' already loads environment variables, so the commented call is redundant — remove the commented line or pick one approach to avoid confusion. See file src/createServer.js for the imports and commented call .

app.use(cookieParser());
app.use(
cors({
origin: process.env.CLIENT_HOST,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CORS origin is set to process.env.CLIENT_HOST with no fallback. If that env var is unset the browser will block requests. Consider providing a default (or allowing '*' in development) or reading multiple allowed origins from env to avoid CORS issues in dev/test environments. (This is defined here) .


app.use('/users', userRoutes);

return app;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no handler for unknown routes. The task requires that the application returns 404 for all other pages — add a final middleware like app.use((req, res) => res.status(404).json({ message: 'Not Found' })) before returning the app to satisfy the requirement .

};

// Export the createServer function
export default { createServer };

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default export is export default { createServer } which exports an object. src/index.js imports the named export createServer (so current usage is fine), but this default is unexpected and can be confusing. Consider export default createServer or removing the default export to keep exports unambiguous (see src/index.js) .

@@ -1 +1,24 @@
'use strict';
/* eslint-disable no-console */

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate eslint-disable for no-console. Keep one directive at the top; remove the redundant line to avoid confusion.

@@ -1 +1,24 @@
'use strict';
/* eslint-disable no-console */

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is a blank line — no action required. (skip)

'use strict';
/* eslint-disable no-console */

/* eslint-disable no-console */

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant duplicate eslint-disable — remove this duplicate so the file has a single top-level directive.

/* eslint-disable no-console */

/* eslint-disable no-console */
'use strict';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A second 'use strict' directive is redundant (the file already starts with it). You can remove this occurrence; in ESM modules strict mode is automatic.

/* eslint-disable no-console */
'use strict';

import './setup.js';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing ./setup.js will run model synchronization on import (see setup.js). That is fine, but ensure model sync is intentional at startup and doesn’t perform long blocking async work during tests/CI .


import './setup.js';
import http from 'http';
import { createServer } from './createServer.js';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Named import of createServer — good. Ensure this matches the named export in createServer.js (it does; routes are mounted there) .

import './setup.js';
import http from 'http';
import { createServer } from './createServer.js';
// import { initWebSocket } from './wsServer.js';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebSocket import is commented out. If you intend to use WebSockets, uncomment this and provide/verify wsServer.js exports initWebSocket. Otherwise remove the unused commented import to keep the codebase clean.

// import { initWebSocket } from './wsServer.js';

// create express app
const app = createServer();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling createServer() to get the Express app is correct — createServer mounts the /users routes as implemented in createServer.js .

const server = http.createServer(app);

// initialize websocket server
// initWebSocket(server);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebSocket initialization is commented out. Keep or remove consistently with the import; if WebSockets are required by the project, ensure you initialize them here (uncomment import and this call).

// initWebSocket(server);

// start server (http + websocket)
server.listen(5000, () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Port is hard-coded to 5000. Prefer using const PORT = process.env.PORT || 5000 and server.listen(PORT, ...) so the port can be configured in different environments.


// start server (http + websocket)
server.listen(5000, () => {
console.log('Server is running on localhost:5000');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Console log uses a hard-coded port string. Use the actual port variable in the log (e.g. console.log(Server is running on localhost:${PORT})) so the message remains accurate when the port changes.

import { Sequelize } from 'sequelize';
import * as utils from 'util';
import dotenv from 'dotenv';
import './setup.js';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing ./setup.js here causes two problems: (a) it runs before dotenv.config() (so env vars may be missing) and (b) it creates a circular dependency — setup.js imports model files which import sequelize from this file, but sequelize has not been exported yet. This can make models receive an undefined sequelize and break DB sync. Remove this import from db.js and call model sync (setup) after sequelize is created/exported (for example, from index.js) instead. See src/setup.js for what runs during that import.

import './setup.js';

// Load environment variables
dotenv.config();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dotenv.config() must run before any module that relies on environment variables. Because ./setup.js is imported earlier (line 6), the setup may run with missing env vars. Move dotenv.config() to the top (before importing anything that uses process.env) or remove the setup import here and call setup after config is loaded.

dotenv.config();

// Needed for testing purposes, do not remove
global.TextEncoder = utils.TextEncoder;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assigning global.TextEncoder directly from utils.TextEncoder may set undefined if the runtime's util does not export TextEncoder. Make this defensive, e.g. global.TextEncoder = global.TextEncoder || utils.TextEncoder; or check if (utils.TextEncoder) global.TextEncoder = utils.TextEncoder;. This avoids overriding an existing implementation or setting undefined for tests.

'use strict';

import { Sequelize } from 'sequelize';
import * as utils from 'util';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing the whole util module is fine, but util.TextEncoder is not guaranteed across Node versions. Consider using a fallback (see comment on line 12) or relying on the global TextEncoder if available.

username: POSTGRES_USER,
host: POSTGRES_HOST,
dialect: 'postgres',
port: POSTGRES_PORT,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

POSTGRES_PORT comes from process.env and is a string. Sequelize accepts numeric ports, so cast it to a number here: port: Number(POSTGRES_PORT) (or parseInt(POSTGRES_PORT,10)) to avoid potential type issues.

@@ -0,0 +1,24 @@
import { DataTypes } from 'sequelize';
import { sequelize } from '../db.js';
import User from './userModel.js';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You import User as the default export here. Other project files generally reference the named User export from the user model; choose one consistent export/import style to avoid confusion (either export only named or import the named export). This file currently uses import User from './userModel.js' while other files use import { User } from './models/userModel.js' .

Comment on lines +7 to +8
refreshToken: {
type: DataTypes.STRING,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refreshToken has no uniqueness/index. Your tokenService assumes one token per user (it looks up by UserId and overwrites), so consider adding a DB-level uniqueness constraint either on UserId or making refreshToken unique to prevent duplicates and enforce expected behaviour. Add unique/index in the model definition if that is the intended invariant.

Comment on lines +21 to +22
Token.belongsTo(User);
User.hasOne(Token);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you declare associations, consider making the foreign key explicit and adding constraints, e.g. Token.belongsTo(User, { foreignKey: { name: 'UserId', allowNull: false, unique: true }, onDelete: 'CASCADE' }) and User.hasOne(Token, { foreignKey: 'UserId' }). This enforces one-token-per-user and removes orphan rows when a user is deleted — it aligns the DB with tokenService/logout logic and improves data integrity.

Comment on lines 11 to 17
const userData = jwtService.verify(token);
if (!userData) {
return res
.status(401)
.json({ message: 'Unauthorized: no userData' });
}
next();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After verifying the token you must attach the user payload to the request so downstream handlers/middlewares can use it (for example ownerMiddleware and protected profile routes expect req.user). Consider req.user = userData here, or optionally fetch the full user from DB and attach that object. See owner middleware usage for why req.user is required.

Comment on lines +28 to +30
activationToken: {
type: DataTypes.STRING,
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

activationToken field is defined but has no explicit allowNull or defaultValue. Make this explicit (e.g. allowNull: true or defaultValue: null) so the DB schema is clear and consistent with resetToken which has defaultValue: null. This improves readability and avoids implicit behavior.

Comment on lines +6 to +52
export const User = sequelize.define(
'User',
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
activationToken: {
type: DataTypes.STRING,
},
activated: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
resetToken: {
type: DataTypes.STRING,
defaultValue: null,
},
resetTokenExpires: {
type: DataTypes.DATE,
defaultValue: null,
},
},
{
tableName: 'users',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
},
);
export default User;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're exporting User both as a named export (line 6) and as a default export on line 52. This is redundant and can be confusing for imports. Prefer one style — either export const User (and import with { User }) or export default User (and import the default).

Comment on lines +19 to +23
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding validation and normalization to the email field (for example validate: { isEmail: true } and set(value){ this.setDataValue('email', value.trim().toLowerCase()) }). This helps avoid invalid addresses and duplicate entries differing only by case/whitespace. It's not strictly required by the checklist but improves robustness.

Comment on lines 6 to 8
// if (!req.params.id) {
// return res.status(400).json({ message: 'Missing id param ownerMiddleware' });
// }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You previously had an explicit check for req.params.id (commented out). Restore a presence check and return 400 when the id param is missing to avoid incorrect 403 responses when req.params.id is undefined.

// };

export const ownerMiddleware = (req, res, next) => {
if (!req.user) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You check req.user exists, but do not verify req.user.id. If req.user is present but has no id, Number(req.user.id) will be NaN. Add a check like if (!req.user?.id) return res.status(401).json({ message: 'Unauthorized' }) or handle this case explicitly.

}
console.log(req.user, req.params);
const tokenUserId = Number(req.user.id);
const paramUserId = Number(req.params.id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When converting route param to number, prefer const paramUserId = parseInt(req.params.id, 10) and then check Number.isNaN(paramUserId) to return a 400 for invalid ids. This avoids relying on Number(undefined) which yields NaN and leads to confusing 403s.

Comment on lines 24 to 27
console.log(req.user, req.params);
const tokenUserId = Number(req.user.id);
const paramUserId = Number(req.params.id);
console.log(tokenUserId);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are console.log calls logging req.user/req.params and tokenUserId. Remove or wrap these logs (e.g. only in development) before production — they may leak sensitive info.

const router = express.Router();

router.get('/', getAllUsers);
router.post('/registration', createUser);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration route is currently unprotected — the task requires registration to be available only to non-authenticated users. Add a middleware (e.g. guest or check authMiddleware) to prevent authenticated users from calling this endpoint. See the registration route here .

router.get('/', getAllUsers);
router.post('/registration', createUser);

router.post('/login', loginUser);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Login route is exposed to all users — per requirements login should be available only to non-authenticated users. Add a middleware that blocks requests when the user is already authenticated (or document why it's intentionally open).


router.post('/login', loginUser);
router.get('/refresh', refresh);
router.post('/reset-password', resetPassword,)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Password reset initiation (/reset-password) must be available only to non-authenticated users according to the spec; currently there's no protection here. Add a guest middleware or similar to enforce this rule.

router.post('/login', loginUser);
router.get('/refresh', refresh);
router.post('/reset-password', resetPassword,)
router.patch('/reset-password/:resetToken', resetUserPassword,)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reset password confirmation route (/reset-password/:resetToken) is exposed without guest-only enforcement. Ensure the reset flow is only for non-authenticated users and validate password/confirmation equality in the controller/UI as required by the checklist (controller already handles saving; the route should be guarded as needed).

router.post('/reset-password', resetPassword,)
router.patch('/reset-password/:resetToken', resetUserPassword,)

router.patch('/activation/:activationToken', updateUserActivated);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Activation routes (/activation/:activationToken) should be available only for non-authenticated users. Add guest-only protection so authenticated users cannot call activation (or redirect as appropriate after activation).

Comment on lines 35 to 39
router.patch(
'/:id/change-name',
// authMiddleware,
// ownerMiddleware,
changeUserName

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authMiddleware and ownerMiddleware usages are commented out for the profile-change routes. These routes must be protected: apply authMiddleware and enforce ownership (ownerMiddleware) for /:id/change-name to satisfy the Profile-only and owner-only requirements.

Comment on lines 41 to 45
router.patch(
'/:id/change-password',
// authMiddleware,
// ownerMiddleware,
changeUserPassword

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue for /:id/change-password — the route must require authentication and ownership and also the controller expects old/new password/confirmation validation; ensure authMiddleware and ownerMiddleware are enabled here.

Comment on lines 47 to 51
router.patch(
'/:id/change-email',
// authMiddleware,
// ownerMiddleware,
changeUserEmail

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue for /:id/change-email — require authMiddleware + ownerMiddleware, and ensure the controller flow (password check, new email confirmation and notification to old email) is protected and aligned with the checklist.

// const user = await User.findByPk(req.user.id);
// res.json(user);
// });
router.post('/logout', logout);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logout route is exposed without authMiddleware. Logout must be available only for authenticated users (the controller expects a refresh cookie). Protect this route with authMiddleware to prevent misuse and to meet the requirement that logout is only for authenticated users.

@@ -0,0 +1,59 @@
'use strict';
import { authMiddleware } from '../middlewares/authMiddleware.js';
// import { ownerMiddleware } from '../middlewares/ownerMiddleware.js';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ownerMiddleware import is commented out — if you intend to enforce owner checks on profile routes (recommended), re-enable this import and apply it where needed. If you don't have an owner middleware, implement one that compares req.user.id with req.params.id before allowing changes.

@@ -0,0 +1,41 @@
import { Token } from "../models/tokenModel.js"

export const save = async(UserId, newToken) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save parameter naming is newToken. Consider renaming to refreshToken to make intent explicit and/or validate input. Also consider returning the created/updated token instance (useful for callers).

const token = await Token.findOne({ where: { UserId } });

if (!token) {
await Token.create({ UserId, refreshToken: newToken });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When creating a token you call Token.create({ UserId, refreshToken: newToken }). If the DB allows multiple tokens per user this can create duplicates. If you intend one-token-per-user either enforce a DB uniqueness (on UserId) or use an upsert/update pattern to avoid duplicate rows (aligns with how controllers expect single token per user) — see Token model and setup for associations .

return;
}

token.refreshToken = newToken;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After finding an existing token you set token.refreshToken = newToken and await token.save(). That's fine, but consider returning the saved instance (or calling Token.update(...)) so callers can use the result immediately and to make behaviour explicit.


await token.save();
}
export const getByToken = (refreshToken) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getByToken currently returns a Promise (fine). For consistency/clarity consider marking it async (and await internally) so the function signature clearly returns a resolved token object rather than a raw Promise — this improves readability for future maintainers.

return Token.findOne({ where: { refreshToken } });
}

export const remove = (userId) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two functions that delete by user id: remove and removeByUserId — they do the same thing. Keep a single canonical method to avoid confusion and duplicate exports; update usages accordingly (controllers call remove in logout) .

Comment on lines +1 to +29
// import jwt from 'jsonwebtoken';
// import 'dotenv/config';

// const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET;
// const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET;

// function sign(payload) {
// return jwt.sign(payload, ACCESS_SECRET, { expiresIn: '15m' });
// }

// function verify(token) {
// // throws error if invalid
// return jwt.verify(token, ACCESS_SECRET);
// }

// function signRefresh(payload) {
// return jwt.sign(payload, REFRESH_SECRET, { expiresIn: '7d' });
// }

// function verifyRefresh(token) {
// return jwt.verify(token, REFRESH_SECRET);
// }

// export const jwtService = {
// sign,
// verify,
// signRefresh,
// verifyRefresh,
// };

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove or consolidate the large commented legacy implementation at the top — it adds noise and may confuse future readers. Keep only the current implementation or briefly explain why the old block is retained.

import jwt from 'jsonwebtoken';
import 'dotenv/config';

const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're reading secrets from env: ensure these exist and fail fast with a clear error message if they are missing (e.g. throw or console.error + process.exit). Silent undefined secrets will cause jwt operations to behave unexpectedly at runtime.

import 'dotenv/config';

const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET;
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above for the refresh secret — validate process.env.JWT_REFRESH_SECRET and fail early if it's missing to avoid runtime surprises.

Comment on lines 36 to 38
function sign(user) {
const token = jwt.sign(user, ACCESS_SECRET);
return token;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sign currently signs the entire user object without an expiry. Consider signing a minimal payload (e.g. { id, email }) and add an expiresIn (e.g. '15m') for the access token to follow standard access-token semantics and limit exposure if leaked.

Comment on lines 49 to 51
function signRefresh(user) {
const token = jwt.sign(user, REFRESH_SECRET);
return token;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

signRefresh does not set an expiration. Refresh tokens are usually long-lived but should still have an expiry (e.g. '7d') and ideally be signed with a different secret — add expiresIn for clarity and safety.

Copy link

@mateacademy-ai-mentor mateacademy-ai-mentor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

I am requesting changes for your submission. While there's good progress, particularly with database initialization, several critical issues related to security and data integrity need to be addressed before approval. Specifically, your password reset and change functionalities (resetUserPassword, changeUserPassword) are missing the required confirmation field validation and password rule checks. The application also has significant gaps in route access control, as many routes lack the necessary authMiddleware, ownerMiddleware, or a 'guest' middleware to protect them. Furthermore, the authMiddleware is not correctly attaching user data to req.user, which is crucial for subsequent middleware and controller logic, and the userService.create function isn't properly utilizing transactions for atomic user creation. Lastly, please correct the .eslintrc.сjs file extension to .eslintrc.cjs (using a Latin 'c') to resolve the linting failures. Addressing these high-priority items will bring your application into compliance with the task requirements.


✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨

Important

Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.

Tip

If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.

Caution

Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.

Footnotes

  1. Rate AI review example

  2. Re-request AI review example

  3. Add AI review comments example

import { Sequelize } from 'sequelize';
import * as utils from 'util';
import dotenv from 'dotenv';
import './setup.js';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import statement still pulls in the model synchronization (User.sync, Token.sync) into db.js. This was a critical issue from the previous review regarding database initialization and circular dependencies. The model syncing should be moved out of this file, likely to index.js after the Sequelize instance and models are fully defined.

Comment on lines +36 to +37
if (user && user.activationToken !== null) {
return res.status(404).json({ message: 'User is not activated' });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getUserById endpoint is meant to retrieve a user by their ID. If a user is found but not activated (meaning user.activationToken !== null), returning a 404 Not Found status with the message 'User is not activated' can be misleading. A 403 Forbidden status might be more appropriate if the intention is to restrict access to unactivated users, or simply return the user object with its activation status and let the client handle it.

if (!email || !password) {
return res.status(400).json({ message: 'Email and password are required.' });
}
console.log('AAAA');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.log should be removed in production code.

if (user.activated === false) {
return res.status(403).json({ message: 'Check your email to activate your account' });
}
console.log('AAAAlllllllllllll');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.log should be removed in production code.

if (!isMatch) {
return res.status(400).json({ message: 'Wrong password' });
}
console.log('--------');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.log should be removed in production code.

// accessToken
// });
} catch (error) {
console.error('Login error:', error);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.error is fine for debugging, but consider replacing it with a more robust logging solution for production environments.

// }

export const generateTokens = async (res, user) => {
console.log('/////////');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.log should be removed in production code.

Comment on lines +157 to +159

if (user && user.activationToken !== null) {
return res.status(404).json({ message: 'User is not activated' });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to getUserById, if a user is found by activationToken but user.activationToken !== null, it implies the user exists but hasn't completed activation. Returning a 404 Not Found is semantically incorrect as the user was found. If the intention is to prevent fetching unactivated users, a 403 Forbidden or simply returning the user with their activated status is more accurate.

try {
await emailService.sendActivationEmail(email, newUser.activationToken);
} catch (emailError) {
console.error('Activation email failed:', emailError);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.error is fine for debugging, but consider replacing it with a more robust logging solution for production environments.

});
} catch (error) {
await transaction.rollback();
console.error(error);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.error is fine for debugging, but consider replacing it with a more robust logging solution for production environments.


return generateTokens(res, user);
} catch (err) {
console.error(err);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.error is fine for debugging, but consider replacing it with a more robust logging solution for production environments.



export const resetUserPassword = async (req, res) => {
const { newPassword } = req.body;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requirements state that the reset password confirmation page must have password and confirmation fields that must be equal. You are currently only destructing newPassword from req.body and not checking for a confirmation field. This needs to be added to ensure the new password is confirmed by the user.

return res.status(400).json({ message: 'Invalid or expired token' });
}

// 3️⃣ Hash new password before saving

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before hashing and saving, you need to apply the password rules (e.g., minimum length) as specified in the requirements, similar to what you do during registration (createUser).

});

} catch (error) {
console.error(error);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.error is fine for debugging, but consider replacing it with a more robust logging solution for production environments.

const user = await User.findOne({
where: { id },
});
console.log(`New name:{name}`)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.log should be removed in production code.


export const changeUserPassword = async (req, res) => {
const { id } = req.params;
const { oldPassword, newPassword } = req.body;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requirements state that changing a password on the profile page requires an old one, new password, and confirmation. You are currently only destructing oldPassword and newPassword from req.body and not checking for a confirmation field. This needs to be added to ensure the new password is confirmed by the user.

return res.status(400).json({ message: 'New password is required' });
}

user.password = await bcrypt.hash(newPassword.trim(), 10);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before hashing and saving the new password, you need to apply the password rules (e.g., minimum length) as specified in the requirements, similar to what you do during registration (createUser).

};

export const logout = async (req, res) => {
console.log('ssssssssssssssssss');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.log should be removed in production code.

}

await tokenService.remove(userData.id);
res.clearCookie('refreshToken');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On logout, both the refreshToken and accessToken cookies should be cleared to ensure a complete logout, as both are set in generateTokens.

);
// Use routes for the respective paths

app.use('/users', userRoutes);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The application is missing a 404 (Not Found) handler for routes that are not defined. Add an app.use((req, res, next) => { ... }) middleware at the end of your route definitions to catch all unhandled requests and send a 404 response.

if (!req.user) {
return res.status(401).json({ message: 'owner Unauthorized' });
}
console.log(req.user, req.params);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These console.log statements should be removed as they expose internal data and are not suitable for production.

console.log(req.user, req.params);
const tokenUserId = Number(req.user.id);
const paramUserId = Number(req.params.id);
console.log(tokenUserId);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.log should also be removed.

return res
.status(401)
.json({ message: 'Unauthorized: no userData' });
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: After successfully verifying the token and obtaining userData, you must assign it to req.user. This was a high-priority issue in the previous review and is crucial for other middlewares (e.g., ownerMiddleware) and controllers to function correctly, as they rely on req.user to identify the authenticated user.

Comment on lines +23 to +26
export const removeByUserId = (userId) => {
return Token.destroy({
where: { UserId: userId },
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removeByUserId function is functionally identical to the remove function. To reduce redundancy and maintain a cleaner codebase, consider removing one of them, as remove(userId) already serves the purpose of removing a token by user ID.

Comment on lines +13 to +14
debug: true,
logger: true,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These options (debug and logger) are very useful for development and debugging, but they typically expose sensitive information in logs and should be set to false or removed in a production environment to enhance security and reduce log verbosity.

return send({ email, html, subject: 'Change of email' });
}

console.log('Email is sent');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.log is a general debugging statement and should be removed from the production code.

const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET;
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET;
function sign(user) {
const token = jwt.sign(user, ACCESS_SECRET);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Access tokens should have an explicit, short expiration time (e.g., '15m' as in your commented-out code) for security reasons. Add an expiresIn option to jwt.sign() here.

}

function signRefresh(user) {
const token = jwt.sign(user, REFRESH_SECRET);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refresh tokens should also have an explicit, longer expiration time (e.g., '7d' as in your commented-out code). Add an expiresIn option to jwt.sign() here.

Comment on lines +61 to +67

function verifyToken(token) {
try {
return jwt.verify(token, ACCESS_SECRET);
} catch {
return null;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This verifyToken function is functionally identical to the verify function defined earlier. It is redundant and should be removed, and all calls to verifyToken should be replaced with verify.

verify,
signRefresh,
verifyRefresh,
verifyToken,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the export for the redundant verifyToken function.

const router = express.Router();

router.get('/', getAllUsers);
router.post('/registration', createUser);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This route should only be accessible to non-authenticated users. You need to implement and apply a 'guest' middleware here to block authenticated users from registering again.

router.get('/', getAllUsers);
router.post('/registration', createUser);

router.post('/login', loginUser);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This route should only be accessible to non-authenticated users. Apply a 'guest' middleware here to block authenticated users from logging in.


router.post('/login', loginUser);
router.get('/refresh', refresh);
router.post('/reset-password', resetPassword,)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This route should only be accessible to non-authenticated users. Apply a 'guest' middleware here to prevent authenticated users from initiating a password reset.

router.post('/login', loginUser);
router.get('/refresh', refresh);
router.post('/reset-password', resetPassword,)
router.patch('/reset-password/:resetToken', resetUserPassword,)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This route, part of the password reset flow, should also be restricted to non-authenticated users. Apply a 'guest' middleware here.

router.post('/reset-password', resetPassword,)
router.patch('/reset-password/:resetToken', resetUserPassword,)

router.patch('/activation/:activationToken', updateUserActivated);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This route should only be accessible to non-authenticated users for account activation. Apply a 'guest' middleware here.

router.patch('/reset-password/:resetToken', resetUserPassword,)

router.patch('/activation/:activationToken', updateUserActivated);
router.get('/activation/:activationToken', getUserByActivationToken);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This route, for getting user by activation token, should also be restricted to non-authenticated users. Apply a 'guest' middleware here.

Comment on lines 37 to 39
// authMiddleware,
// ownerMiddleware,
changeUserName

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: This route for changing a user's name must be protected by authMiddleware and ownerMiddleware as specified in the requirements and previous feedback. These middlewares are currently commented out.

Comment on lines 43 to 45
// authMiddleware,
// ownerMiddleware,
changeUserPassword

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: This route for changing a user's password must be protected by authMiddleware and ownerMiddleware as specified in the requirements and previous feedback. These middlewares are currently commented out.

Comment on lines 49 to 51
// authMiddleware,
// ownerMiddleware,
changeUserEmail

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: This route for changing a user's email must be protected by authMiddleware and ownerMiddleware as specified in the requirements and previous feedback. These middlewares are currently commented out.

// const user = await User.findByPk(req.user.id);
// res.json(user);
// });
router.post('/logout', logout);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logout route must be protected by authMiddleware to ensure only authenticated users can log out. This was a high-priority issue in the previous review and is still missing.

return User.findOne({ where: { email } });
};

const create = async ({ name, email, password, activated }) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: The create function signature needs to be updated to accept the transaction object that is passed from the controller. Currently, it's not defined as a parameter, so the transaction is not used for User.create.

Expected signature: const create = async (userData, transaction) => { ... }


// const rawResetToken = crypto.randomBytes(32).toString('hex');
// const resetToken = hashToken(rawResetToken);
return User.create({ name, email, password, activationToken, activated });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: The transaction object received by the create function must be passed to User.create() to ensure that the user creation is part of the ongoing transaction from the controller.

Expected usage: return User.create({ name, email, password, activationToken, activated }, { transaction });

Comment on lines +5 to +8
const getAll = async () => {
const result = await User.findAll();

return result;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This getAll function currently returns all user data, including the password hash. The task requires avoiding returning passwords. You should use the normalize function (already defined in this service) for each user before returning the array, or specify attributes in the findAll call to exclude sensitive fields.

Comment on lines 11 to 12
const getById = async (id) => {
return User.findByPk(id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This getById function currently returns the full user object, which includes the password hash. The task requires avoiding returning passwords. You should use the normalize function (already defined in this service) on the found user before returning, or specify attributes in the findByPk call to exclude sensitive fields.

Copy link

@mateacademy-ai-mentor mateacademy-ai-mentor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

I am requesting changes as there are several critical issues that affect core functionality and security.

Please address these key points to meet the task requirements:

  • Missing Route Protection: Several routes in src/routes/userRoutes.js (e.g., for changing name, password, and email) are unprotected. You must apply the authMiddleware to secure them.
  • Incomplete Authentication: The authMiddleware correctly verifies the token but doesn't attach the user data to req.user. This is a critical step for any subsequent authorization logic.
  • Runtime Errors: The crypto module is used in userController.js and userService.js but is not imported, which will cause the application to crash during registration and password reset.
  • Missing Validation: The password reset and change password functions are missing the required confirmation field and the logic to validate that it matches the new password.
  • Incorrect Transaction Handling: The userService.create function must be updated to use the database transaction passed from the controller to ensure user creation is an atomic operation.
  • Missing 404 Handler: Your server setup in src/createServer.js needs a catch-all route to handle requests for non-existent pages and respond with a 404 status.

✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨

Important

Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.

Tip

If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.

Caution

Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.

Footnotes

  1. Rate AI review example

  2. Re-request AI review example

  3. Add AI review comments example

);
// Use routes for the respective paths

app.use('/users', userRoutes);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The application is missing a handler for non-existent routes. According to the requirements, you need to "Display a 404 page for all other pages". You should add a catch-all middleware after your defined routes to handle any requests that don't match and respond with a 404 status.

import { userService } from '../services/userService.js';
import { User } from '../models/userModel.js';
import bcrypt from 'bcrypt';
// import crypto from 'crypto';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The crypto module is used in resetPassword and resetUserPassword, but the import is commented out. Please uncomment this line to prevent runtime errors.

};

export const resetUserPassword = async (req, res) => {
const { newPassword } = req.body;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The task requirements for the password reset page specify that it should have password and confirmation fields that must be equal. This implementation is missing the confirmation field from req.body and the logic to validate that it matches newPassword.

}

// 3️⃣ Hash new password before saving
user.password = await bcrypt.hash(newPassword.trim(), 10);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Password rule validation is missing. According to the task requirements, the new password should be checked against the defined rules (e.g., minimum length), similar to the check in the createUser function.


export const changeUserPassword = async (req, res) => {
const { id } = req.params;
const { oldPassword, newPassword } = req.body;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the task requirements, changing a password requires a new password and a confirmation field. The confirmation field is missing from req.body, and there's no validation to ensure it matches the newPassword.

Comment on lines +37 to +38
function sign(user) {
const token = jwt.sign(user, ACCESS_SECRET);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is signing a token that never expires. For security, access tokens should be short-lived. You should add an expiresIn option, for example: jwt.sign(user, ACCESS_SECRET, { expiresIn: '15m' }).

return token;
}

function verify(user) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name user is misleading here, as the function expects a token string, not a user object. Renaming it to token would improve code clarity.

Comment on lines +51 to +52
function signRefresh(user) {
const token = jwt.sign(user, REFRESH_SECRET);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refresh tokens should also have an expiration date, though it can be longer than the access token's. Please add an expiresIn option here as well, for example: { expiresIn: '7d' }.

return token;
}

function verifyRefresh(user) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the verify function, the parameter user should be renamed to token to accurately reflect that it's verifying a token string.

Comment on lines +65 to +71
function verifyToken(token) {
try {
return jwt.verify(token, ACCESS_SECRET);
} catch {
return null;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function verifyToken has the exact same implementation as the verify function on line 43. This is redundant and can be removed to avoid confusion.

Copy link

@mateacademy-ai-mentor mateacademy-ai-mentor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

I am approving your submission as an educational override to help you progress, but there are a few important issues to address. You've done a great job setting up the data models and protecting the profile routes! However, the application will crash on registration and password reset because the crypto module isn't imported in src/controllers/userController.js and src/services/userService.js. Also, your authMiddleware needs to attach the decoded user data to req.user for protected routes to work correctly. Finally, remember to add password confirmation validation, protect the /logout route, and implement the 404 handler as required. Addressing these points will make your application robust and fully functional. Keep up the great work!


✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨

Footnotes

  1. Rate AI review example

);
// Use routes for the respective paths

app.use('/users', userRoutes);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The task requires a 404 handler for any pages that are not explicitly defined. You need to add a catch-all route at the end of your route definitions to handle these cases and respond with a 404 status.

import { userService } from '../services/userService.js';
import { User } from '../models/userModel.js';
import bcrypt from 'bcrypt';
// import crypto from 'crypto';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The crypto module is used in the resetPassword and resetUserPassword functions, but it is not imported because this line is commented out. This will cause the application to crash. Please uncomment this line to make the crypto module available.

};

export const resetUserPassword = async (req, res) => {
const { newPassword } = req.body;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The task requirements state that the password reset process must include a confirmation field that is equal to the new password. Please update this function to accept a confirmation field from the request body and add validation to ensure it matches the newPassword.


export const changeUserPassword = async (req, res) => {
const { id } = req.params;
const { oldPassword, newPassword } = req.body;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the task requirements, changing a password requires a confirmation field for the new password. You need to destructure confirmation from the request body and add logic to verify that it matches newPassword.


export const changeUserEmail = async (req, res) => {
const { id } = req.params;
const { password, newEmail } = req.body;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requirements for changing an email state that the user must confirm the new email. Please update this function to handle an email confirmation field from the request body and validate that it matches the newEmail.

if (!userData) {
return res.status(401).json({ message: 'Unauthorized: no userData' });
}
next();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The token is successfully verified, but the userData is not attached to the request object. According to the previous review feedback, you must assign the decoded user data to req.user (e.g., req.user = userData;) before calling next(). This makes the user's information available to the protected route handlers.

@@ -0,0 +1,91 @@
'use strict';
// import crypto from 'crypto';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The crypto module is used on line 33 to generate an activation token, but its import is commented out. This will cause a runtime error whenever a new user is created. Please uncomment this line to import the module.

Comment on lines +32 to +44
const create = async ({ name, email, password, activated }) => {
const activationToken = crypto.randomBytes(32).toString('hex');
// const activationToken = hashToken(rawActivationToken);

// const rawResetToken = crypto.randomBytes(32).toString('hex');
// const resetToken = hashToken(rawResetToken);
return User.create({
name,
email,
password,
activationToken,
activated,
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is called with a transaction object from the controller, but the function signature doesn't accept it. You need to update the function to accept the transaction as a second argument. Then, you must pass this transaction to the User.create call to ensure the operation is atomic, as required by the previous review.

return token;
}

function verify(user) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter for this function is a JWT string, but it's named user. This is confusing. For clarity and better readability, consider renaming it to token.

return token;
}

function verifyRefresh(user) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the verify function, the parameter here is a refresh token, not a user object. Renaming user to token would make the function's purpose clearer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants