Conversation
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
This requires changes because several critical, blocking requirements are unimplemented or broken. First, password reset functionality is completely missing, including routes (/reset-password, /reset-password/:token), email service integration, and controller logic — yet it's explicitly required in the task. Second, login does not check if the user is activated, violating the requirement that inactive users must be prompted to activate their email. Third, auth middleware fails to attach req.user, breaking all protected profile routes. Fourth, profile updates lack essential validations: password changes don’t verify the old password or confirm new password matches, and email changes don’t require password verification or notify the old email address. Finally, there’s no 404 handler for undefined routes, and multiple await calls are missing (e.g., Token.destroy() in token.service.js), risking race conditions and runtime errors like res.statusStatus(204) in auth.controller.js. These issues prevent core functionality from working as specified and must be fixed before approval.
✨ 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
| app.use(authRoute); | ||
| app.use(userRouter); | ||
|
|
||
| app.get('/', (req, res) => { | ||
| res.send('Hello'); | ||
| }); |
There was a problem hiding this comment.
The task requires a 404 page for all other pages. You have registered your application routes, but you are missing a catch-all middleware to handle requests that don't match any of them. You should add a middleware here, before the errorMiddleware, that sends a 404 response for any unhandled routes.
| import { User } from '../models/user'; | ||
| import bcrypt from 'bcrypt'; | ||
|
|
||
| const updateProfile = async (req, res) => { |
There was a problem hiding this comment.
The requirements state changing password should require an old one, new password and confirmation. Currently accepting only password without validating old password or confirming new password matches
|
|
||
| const updateData = { name, email }; | ||
|
|
||
| if (password) { |
There was a problem hiding this comment.
Missing validation that new password matches confirmation and verification of old password. The requirements specify these are needed for password changes
| if (password) { | ||
| updateData.password = await bcrypt.hash(password, 10); | ||
| } | ||
|
|
There was a problem hiding this comment.
Requirements state: to change an email you should type the password, confirm the new email and notify the old email about the change. None of this is implemented here
|
|
||
| const login = async (req, res) => { | ||
| const { email, password } = req.body; | ||
|
|
||
| const user = await userService.findByEmail(email); | ||
|
|
||
| if (!user) { | ||
| throw ApiError.badRequest('No such user'); | ||
| } | ||
|
|
||
| const isPasswordValid = await bcrypt.compare(password, user.password); | ||
|
|
||
| if (!isPasswordValid) { | ||
| throw ApiError.badRequest('Wrong password'); | ||
| } | ||
|
|
||
| generateTokens(res, user); |
There was a problem hiding this comment.
Missing check for user activation status. According to requirements, if user is not active, they should be asked to activate their email.
|
|
||
| res.statusStatus(204); |
There was a problem hiding this comment.
Typo: res.statusStatus should be res.status. This will cause a runtime error.
| import { User } from '../models/user.js'; | ||
| import { userService } from '../services/user.service.js'; | ||
| import { jwtService } from '../services/jwt.service.js'; | ||
| import { ApiError } from '../exeptions/api.error.js'; | ||
| import { tokenService } from '../services/token.service.js'; | ||
|
|
||
| const validateName = (value) => { | ||
| if (!value.trim()) { | ||
| return 'Name is required'; | ||
| } | ||
|
|
||
| if (value.length < 4) { | ||
| return 'At least 4 characters'; | ||
| } | ||
| }; | ||
|
|
||
| const validateEmail = (value) => { | ||
| if (!value) { | ||
| return 'Email is required'; | ||
| } |
There was a problem hiding this comment.
The authMiddleware extracts user data from JWT but doesn't attach it to req.user, so protected routes won't have access to user information.
| import { userService } from '../services/user.service.js'; | ||
| import { jwtService } from '../services/jwt.service.js'; | ||
| import { ApiError } from '../exeptions/api.error.js'; | ||
| import { tokenService } from '../services/token.service.js'; | ||
|
|
||
| const validateName = (value) => { | ||
| if (!value.trim()) { | ||
| return 'Name is required'; | ||
| } | ||
|
|
||
| if (value.length < 4) { | ||
| return 'At least 4 characters'; | ||
| } |
There was a problem hiding this comment.
Profile update doesn't require old password verification. Requirements specify: 'to change an email you should type the password, confirm the new email and notify the old email about the change'.
| const validateName = (value) => { | ||
| if (!value.trim()) { | ||
| return 'Name is required'; |
There was a problem hiding this comment.
Profile update doesn't validate password confirmation. Requirements specify changing password requires an old one, new password and confirmation fields that must be equal.
| const authorization = req.headers['authorization'] || ''; | ||
| const [, token] = authorization.split(' '); | ||
|
|
||
| if (!authorization || !token) { | ||
| res.status(401); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const userData = jwtService.verify(token); | ||
|
|
||
| if (!userData) { | ||
| res.status(401); |
There was a problem hiding this comment.
Profile update doesn't validate old password when changing password. Per requirements: changing password requires an old one, 'new password' and 'confirmation'. Current implementation only accepts 'password' field.
| const authorization = req.headers['authorization'] || ''; | ||
| const [, token] = authorization.split(' '); | ||
|
|
||
| if (!authorization || !token) { | ||
| res.status(401); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const userData = jwtService.verify(token); | ||
|
|
||
| if (!userData) { | ||
| res.status(401); |
There was a problem hiding this comment.
Profile update doesn't handle email changes with proper validation. Per requirements: to change an email you should type the password, confirm the new email and notify the old email about the change.
| if (!authorization || !token) { | ||
| res.status(401); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const userData = jwtService.verify(token); | ||
|
|
||
| if (!userData) { | ||
| res.status(401); | ||
|
|
There was a problem hiding this comment.
authMiddleware sets 401 status but doesn't send a response or return, which could cause the request to hang.
| import { DataTypes } from 'sequelize'; | ||
| import { client } from '../utils/db.js'; | ||
| import { User } from './user.js'; | ||
|
|
||
| export const Token = client.define( | ||
| 'Token', | ||
| { | ||
| refreshToken: { | ||
| type: DataTypes.STRING, | ||
| allowNull: false, | ||
| }, | ||
| }, | ||
| { | ||
| tableName: 'tokens', | ||
| }, | ||
| ); |
There was a problem hiding this comment.
Routes are missing password reset endpoints and user profile routes required by the task (profile page to change name, password, email).
| import express from 'express'; | ||
| import { authController } from '../controllers/auth.controller.js'; | ||
| import { catchError } from '../utils/catchError.js'; | ||
|
|
||
| export const authRoute = new express.Router(); | ||
|
|
||
| authRoute.post('/registration', catchError(authController.registration)); | ||
| authRoute.post('/login', catchError(authController.login)); | ||
|
|
||
| authRoute.get( | ||
| '/activation/:activationToken', | ||
| catchError(authController.activate), | ||
| ); | ||
|
|
||
| authRoute.get('/refresh', catchError(authController.refresh)); | ||
| authRoute.post('/logout', catchError(authController.logout)); |
There was a problem hiding this comment.
Auth routes are missing password reset endpoints. The task requires: password reset (ask for email → show email sent page → reset password confirmation page with password/confirmation fields → success page with link to login). No routes defined for /reset-password, /reset-password/:token, etc.
| import express from 'express'; | ||
| import { authController } from '../controllers/auth.controller.js'; | ||
| import { catchError } from '../utils/catchError.js'; | ||
|
|
||
| export const authRoute = new express.Router(); | ||
|
|
||
| authRoute.post('/registration', catchError(authController.registration)); | ||
| authRoute.post('/login', catchError(authController.login)); | ||
|
|
||
| authRoute.get( | ||
| '/activation/:activationToken', | ||
| catchError(authController.activate), | ||
| ); | ||
|
|
||
| authRoute.get('/refresh', catchError(authController.refresh)); | ||
| authRoute.post('/logout', catchError(authController.logout)); |
There was a problem hiding this comment.
No profile routes defined for authenticated users to update their profile (change name, password, email). This is required by the task: 'Profile page: You can change a name', 'change a password', 'change an email'.
|
|
||
| activationToken: { | ||
| type: DataTypes.STRING, | ||
| }, |
There was a problem hiding this comment.
User model is missing isActive field. The requirements state 'the user should be activated only after email confirmation' and 'If user is not active ask them to activate their email'. The current approach uses activationToken: null as a proxy for activation status, but a boolean isActive field would be more explicit and reliable.
| import { DataTypes } from 'sequelize'; | ||
| import { client } from '../utils/db.js'; | ||
|
|
||
| export const User = client.define( | ||
| 'User', | ||
| { | ||
| name: { | ||
| type: DataTypes.STRING, | ||
| allowNull: false, | ||
| }, | ||
|
|
||
| email: { | ||
| type: DataTypes.STRING, | ||
| allowNull: false, | ||
| unique: true, | ||
| }, |
There was a problem hiding this comment.
No routes defined for password reset flow. Requirements explicitly require: 'Ask for an email' → 'Show email sent page' → 'Reset Password confirmation page' → 'Show Success page with link to login'. None of these are implemented.
| type: DataTypes.STRING, | ||
| allowNull: false, | ||
| unique: true, | ||
| }, |
There was a problem hiding this comment.
No 404 handling for undefined routes. Requirements state: '404 for all the other pages'. Need to add a catch-all route at the end that returns 404.
| throw ApiError.badRequest('User already exist', { | ||
| email: 'User already exist', | ||
| }); |
There was a problem hiding this comment.
The token service's remove function is missing await before Token.destroy(). The function is async but doesn't wait for the database operation to complete, which could cause race conditions.
| return token; | ||
| } |
There was a problem hiding this comment.
The Token.destroy() call is not awaited. Since it's an async database operation, it should be await Token.destroy(...) to ensure proper execution.
| @@ -0,0 +1,36 @@ | |||
| import jwt from 'jsonwebtoken'; | |||
|
|
|||
| function sing(user) { | |||
There was a problem hiding this comment.
The access token expires in 30 seconds which is very short. This will cause users to re-authenticate frequently. Consider increasing to something like '1h' for better UX while maintaining security.
| import jwt from 'jsonwebtoken'; | ||
|
|
||
| function sing(user) { | ||
| const token = jwt.sign(user, process.env.JWT_KEY, { expiresIn: '30s' }); | ||
|
|
||
| return token; | ||
| } | ||
|
|
||
| function verify(token) { | ||
| try { | ||
| return jwt.verify(token, process.env.JWT_KEY); | ||
| } catch (error) { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| function singRefresh(user) { | ||
| const token = jwt.sign(user, process.env.JWT_REFRESH_KEY); | ||
|
|
||
| return token; | ||
| } | ||
|
|
||
| function verifyRefresh(token) { | ||
| try { | ||
| return jwt.verify(token, process.env.JWT_REFRESH_KEY); | ||
| } catch (error) { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| export const jwtService = { | ||
| sing, | ||
| verify, | ||
| singRefresh, | ||
| verifyRefresh, |
There was a problem hiding this comment.
User service is missing functions for: password reset (generating reset token, resetting password with validation), and profile update functions (change name, change password with old password verification, change email with notification). These are required by the task.
| function sendActivationEmail(email, token) { | ||
| const href = `${process.env.CLIENT_HOST}/activate/${token}`; |
There was a problem hiding this comment.
The remove function calls Token.destroy which returns a promise, but it's not awaited. This could cause unhandled promise warnings or timing issues.
|
|
||
| function remove(userId) { | ||
| Token.destroy({ where: { userId } }); |
There was a problem hiding this comment.
The remove function is async but doesn't use await before Token.destroy. This should be await Token.destroy({ where: { userId } }); for proper async handling.
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
Great work on the improvements from the last review, especially with the user activation flow! I am requesting a few changes because some core features are incomplete and have critical bugs that prevent the application from working as required.
Here are the main issues to address:
- Password Reset is Incomplete: This feature is not functional. It's missing the routes in
src/routes/auth.route.js, the required logic insrc/services/user.service.js(updateResetToken, etc.), and aresetTokenfield in theUsermodel. - Missing Profile Validations: When updating a profile in
src/controllers/user.controller.js, you need to add a check for a matchingconfirmationfield when changing a password and send a notification to the user's old email address when their email is changed. - Critical Bugs:
- In
src/services/token.service.js, theremovefunction is missingawaitforToken.destroy(), which breaks the logout functionality. - The
authMiddleware.jshangs on invalid tokens because it doesn't send a response. You should use your error handler to send a proper401error.
- In
Additionally, your build is failing because .eslintrc.js should be renamed to .eslintrc.cjs to work with your ES module setup. Addressing these points will get the project fully functional.
✨ 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
| import { ApiError } from '../exeptions/api.error'; | ||
| import { User } from '../models/user'; | ||
| import bcrypt from 'bcrypt'; | ||
| import { userService } from '../services/user.service'; |
There was a problem hiding this comment.
These import statements are missing the .js file extension. While this might work in some environments, it's best practice in Node.js ESM to include the full file extension for clarity and to avoid potential module resolution issues. Other files in the project correctly include the extension.
| }; | ||
|
|
||
| const updateProfile = async (req, res) => { | ||
| const { name, email, oldPassword, newPassword } = req.body; |
There was a problem hiding this comment.
According to the task requirements, when changing a password, the user must provide a new password and a confirmation. The confirmation field is missing from the request body, and there's no logic here to verify that the new password and its confirmation match.
| if (email && email !== user.email) { | ||
| updateData.email = email; | ||
| } |
There was a problem hiding this comment.
The requirements state that when a user's email is changed, a notification must be sent to their old email address. This notification logic is missing from this block.
| throw ApiError.badRequest('User with this email does not exist'); | ||
| } | ||
|
|
||
| const resetToken = crypto.randomBytes(32).toString('hex'); |
There was a problem hiding this comment.
The crypto module is used here but it's not imported. You need to add import crypto from 'crypto'; at the top of the file for this to work.
|
|
||
| const resetToken = crypto.randomBytes(32).toString('hex'); | ||
|
|
||
| await userService.updateResetToken(user.id, resetToken); |
There was a problem hiding this comment.
The updateResetToken function is called here, but it's not defined or exported in src/services/user.service.js. The same issue exists for findByResetToken and updatePassword used in the resetPassword function. The password reset feature needs to be fully implemented in the service layer, including adding the necessary fields to the User model.
| activationToken: { | ||
| type: DataTypes.STRING, | ||
| }, |
There was a problem hiding this comment.
The task requires a password reset feature, which typically involves storing a temporary token for the user. This model is missing a field, such as resetToken, to hold that value. You might also want to add a resetTokenExpiry field to ensure the token expires after a certain period.
| @@ -0,0 +1,35 @@ | |||
| import { ApiError } from '../exeptions/api.error.js'; | |||
| import { User } from '../models/user'; | |||
There was a problem hiding this comment.
This import statement is missing the .js file extension. While this might work in some environments, it's inconsistent with other imports in this file (like for ApiError and emailService) and the project as a whole. It's best practice in Node.js ESM to include the full file extension.
| await emailService.sendActivationEmail(email, activationToken); | ||
| } | ||
|
|
||
| export const userService = { normalize, findByEmail, register }; |
There was a problem hiding this comment.
This service is missing the implementation for the password reset functionality. The auth.controller.js file calls several functions that should be defined here, such as:
updateResetTokenfindByResetTokenupdatePassword
These functions are essential for the password reset flow to work as required.
| @@ -0,0 +1,25 @@ | |||
| import { Token } from '../models/token'; | |||
There was a problem hiding this comment.
This import is missing the .js extension. While it might work depending on the Node.js version and configuration, it's a best practice to include it for consistency with other files in your project (like user.service.js) and to prevent potential module resolution issues.
| } | ||
|
|
||
| function remove(userId) { | ||
| Token.destroy({ where: { userId } }); |
There was a problem hiding this comment.
Token.destroy() is an asynchronous operation and must be awaited. Without await, the function will return before the token is actually deleted from the database. This means the logout process might not work as expected.
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I am approving this submission as an educational override to help you progress, but there are a few critical issues to address before the application will run correctly. You've done an excellent job fixing previous bugs and building out the core services! However, the password reset feature is currently non-functional. In src/controllers/auth.controller.js, you're using the crypto module without importing it, and you're calling a userService.updatePassword function that doesn't exist yet in the user service. Additionally, several files like src/services/user.service.js are missing the .js extension on local imports, which will prevent the server from starting. For the profile update, please also add the required emailConfirmation check when a user changes their email. You're very close to finishing—address these points, and you'll have a fully working application!
✨ 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
| user.password = await bcrypt.hash(password, 10); | ||
| } | ||
|
|
||
| if (email && email !== user.email) { |
There was a problem hiding this comment.
According to the task description, when changing an email, you also need to "confirm the new email". This is similar to how you handle password changes. You should expect an emailConfirmation field in the request body and verify that it matches the new email before proceeding with the update.
| throw ApiError.badRequest('User with this email does not exist'); | ||
| } | ||
|
|
||
| const resetToken = crypto.randomBytes(32).toString('hex'); |
There was a problem hiding this comment.
The built-in crypto module is used here to generate a token, but it hasn't been imported at the top of the file. This will cause a ReferenceError at runtime.
| const resetPassword = async (req, res) => { | ||
| const { token, newPassword } = req.body; | ||
| const user = await userService.findByResetToken(token); | ||
|
|
||
| if (!user) { | ||
| throw ApiError.badRequest('Invalid or expired reset token'); | ||
| } | ||
|
|
||
| const hashedPassword = await bcrypt.hash(newPassword, 10); | ||
|
|
||
| await userService.updatePassword(user.id, hashedPassword); | ||
|
|
||
| res.send({ message: 'Password has been reset' }); | ||
| }; |
There was a problem hiding this comment.
This password reset implementation is missing two key parts from the requirements:
- Password Confirmation: You need to get a
confirmationfield fromreq.bodyand verify that it matchesnewPassword. - Update Logic: The
userService.updatePasswordfunction doesn't exist in the user service. You should implement the logic to update the user's password and set theirresetTokentonulldirectly in this function, then save the user.
| @@ -0,0 +1,53 @@ | |||
| import { ApiError } from '../exeptions/api.error.js'; | |||
| import { User } from '../models/user'; | |||
There was a problem hiding this comment.
When using ES modules in Node.js, you need to include the file extension in your relative imports. This should be from '../models/user.js'; to ensure the module is found correctly.
| export const userService = { | ||
| normalize, | ||
| findByEmail, | ||
| register, | ||
| updateResetToken, | ||
| findByResetToken, | ||
| }; |
There was a problem hiding this comment.
The resetPassword function in auth.controller.js calls userService.updatePassword, but that function is not implemented or exported in this service. You need to create this function to handle updating the user's password in the database.
| @@ -0,0 +1,25 @@ | |||
| import { Token } from '../models/token'; | |||
There was a problem hiding this comment.
In an ES Modules environment like this project, you need to include the file extension in your relative imports. This should be import { Token } from '../models/token.js'; to match the pattern used in your other files.
No description provided.