-
Notifications
You must be signed in to change notification settings - Fork 299
Final code #234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Final code #234
Conversation
mateacademy-ai-mentor
left a comment
There was a problem hiding this 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:
- Activation flow logic: In
src/controllers/userController.js, the activation check aroundactivationTokenis inverted — you fetch by token and then treat a non‑nulluser.activationTokenas “not activated”, which makes valid activation tokens fail. You need to treatactivationToken === null(or a mismatch) as already activated/invalid and only allow activation when the token matches and the user is not yet activated. - Missing password confirmation checks: The reset password controller and the change‑password controller accept only
newPassword(andoldPasswordfor change) and ignore aconfirmationfield, but the task requirespasswordandconfirmationto be equal on reset and change. Add aconfirmationparameter, checknewPassword === confirmation, and also apply your password rules before saving in both flows. - 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 byauthMiddlewareandownerMiddlewareas required. You should (a) add a guest/non‑auth middleware to block authenticated users from the public auth flows and (b) re‑enableauthMiddleware+ownerMiddlewarefor profile change and logout routes so only the authenticated owner can call them. - authMiddleware doesn’t attach the user: In
src/middlewares/authMiddleware.jsyou verify the token but never setreq.user, whileownerMiddlewareand profile controllers rely onreq.user.id. After verifying, assignreq.user = userData(or the fetched DB user) before callingnext()so downstream code can correctly identify the authenticated user. - Database initialization / transaction correctness: In
src/db.jsyou import./setup.jsbefore exporting the Sequelize instance, andsetup.jsimports models that depend on that instance, creating a circular dependency and possible runtime failures. Also,userService.createinsrc/services/userService.jsignores thetransactionpassed from the controller, so user creation is not actually part of the transaction. Move model sync/init out ofdb.js(e.g., call an async init fromindex.jsafter Sequelize is created) and updateuserService.createto accept and forward{ transaction }toUser.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
| return res.status(404).json({ message: 'User not found' }); | ||
| } | ||
|
|
||
| if (user && user.activationToken !== null) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
| 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); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
| export const changeUserName = async (req, res) => { | ||
|
|
||
| const { id } = req.params; |
There was a problem hiding this comment.
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.
src/controllers/userController.js
Outdated
| const user = await User.findOne({ | ||
| where: { id }, | ||
| }); | ||
| console.log(`New name:{name}`) |
There was a problem hiding this comment.
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.
src/controllers/userController.js
Outdated
| 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'); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 }; |
There was a problem hiding this comment.
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 */ | |||
There was a problem hiding this comment.
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 */ | |||
|
|
|||
There was a problem hiding this comment.
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 */ |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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, () => { |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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'; | |||
There was a problem hiding this comment.
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' .
| refreshToken: { | ||
| type: DataTypes.STRING, |
There was a problem hiding this comment.
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.
| Token.belongsTo(User); | ||
| User.hasOne(Token); |
There was a problem hiding this comment.
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.
src/middlewares/authMiddleware.js
Outdated
| const userData = jwtService.verify(token); | ||
| if (!userData) { | ||
| return res | ||
| .status(401) | ||
| .json({ message: 'Unauthorized: no userData' }); | ||
| } | ||
| next(); |
There was a problem hiding this comment.
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.
| activationToken: { | ||
| type: DataTypes.STRING, | ||
| }, |
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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).
| email: { | ||
| type: DataTypes.STRING, | ||
| allowNull: false, | ||
| unique: true, | ||
| }, |
There was a problem hiding this comment.
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.
src/middlewares/ownerMiddleware.js
Outdated
| // if (!req.params.id) { | ||
| // return res.status(400).json({ message: 'Missing id param ownerMiddleware' }); | ||
| // } |
There was a problem hiding this comment.
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.
src/middlewares/ownerMiddleware.js
Outdated
| // }; | ||
|
|
||
| export const ownerMiddleware = (req, res, next) => { | ||
| if (!req.user) { |
There was a problem hiding this comment.
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.
src/middlewares/ownerMiddleware.js
Outdated
| } | ||
| console.log(req.user, req.params); | ||
| const tokenUserId = Number(req.user.id); | ||
| const paramUserId = Number(req.params.id); |
There was a problem hiding this comment.
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.
src/middlewares/ownerMiddleware.js
Outdated
| console.log(req.user, req.params); | ||
| const tokenUserId = Number(req.user.id); | ||
| const paramUserId = Number(req.params.id); | ||
| console.log(tokenUserId); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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).
src/routes/userRoutes.js
Outdated
|
|
||
| router.post('/login', loginUser); | ||
| router.get('/refresh', refresh); | ||
| router.post('/reset-password', resetPassword,) |
There was a problem hiding this comment.
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.
src/routes/userRoutes.js
Outdated
| router.post('/login', loginUser); | ||
| router.get('/refresh', refresh); | ||
| router.post('/reset-password', resetPassword,) | ||
| router.patch('/reset-password/:resetToken', resetUserPassword,) |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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).
src/routes/userRoutes.js
Outdated
| router.patch( | ||
| '/:id/change-name', | ||
| // authMiddleware, | ||
| // ownerMiddleware, | ||
| changeUserName |
There was a problem hiding this comment.
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.
src/routes/userRoutes.js
Outdated
| router.patch( | ||
| '/:id/change-password', | ||
| // authMiddleware, | ||
| // ownerMiddleware, | ||
| changeUserPassword |
There was a problem hiding this comment.
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.
src/routes/userRoutes.js
Outdated
| router.patch( | ||
| '/:id/change-email', | ||
| // authMiddleware, | ||
| // ownerMiddleware, | ||
| changeUserEmail |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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'; | |||
There was a problem hiding this comment.
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.
src/services/tokenService.js
Outdated
| @@ -0,0 +1,41 @@ | |||
| import { Token } from "../models/tokenModel.js" | |||
|
|
|||
| export const save = async(UserId, newToken) => { | |||
There was a problem hiding this comment.
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 }); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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) .
| // 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, | ||
| // }; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
| function sign(user) { | ||
| const token = jwt.sign(user, ACCESS_SECRET); | ||
| return token; |
There was a problem hiding this comment.
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.
| function signRefresh(user) { | ||
| const token = jwt.sign(user, REFRESH_SECRET); | ||
| return token; |
There was a problem hiding this comment.
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.
mateacademy-ai-mentor
left a comment
There was a problem hiding this 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
| import { Sequelize } from 'sequelize'; | ||
| import * as utils from 'util'; | ||
| import dotenv from 'dotenv'; | ||
| import './setup.js'; |
There was a problem hiding this comment.
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.
| if (user && user.activationToken !== null) { | ||
| return res.status(404).json({ message: 'User is not activated' }); |
There was a problem hiding this comment.
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.
src/controllers/userController.js
Outdated
| if (!email || !password) { | ||
| return res.status(400).json({ message: 'Email and password are required.' }); | ||
| } | ||
| console.log('AAAA'); |
There was a problem hiding this comment.
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.
src/controllers/userController.js
Outdated
| if (user.activated === false) { | ||
| return res.status(403).json({ message: 'Check your email to activate your account' }); | ||
| } | ||
| console.log('AAAAlllllllllllll'); |
There was a problem hiding this comment.
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.
src/controllers/userController.js
Outdated
| if (!isMatch) { | ||
| return res.status(400).json({ message: 'Wrong password' }); | ||
| } | ||
| console.log('--------'); |
There was a problem hiding this comment.
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.
src/controllers/userController.js
Outdated
| // accessToken | ||
| // }); | ||
| } catch (error) { | ||
| console.error('Login error:', error); |
There was a problem hiding this comment.
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.
src/controllers/userController.js
Outdated
| // } | ||
|
|
||
| export const generateTokens = async (res, user) => { | ||
| console.log('/////////'); |
There was a problem hiding this comment.
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 && user.activationToken !== null) { | ||
| return res.status(404).json({ message: 'User is not activated' }); |
There was a problem hiding this comment.
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.
src/controllers/userController.js
Outdated
| try { | ||
| await emailService.sendActivationEmail(email, newUser.activationToken); | ||
| } catch (emailError) { | ||
| console.error('Activation email failed:', emailError); |
There was a problem hiding this comment.
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.
src/controllers/userController.js
Outdated
| }); | ||
| } catch (error) { | ||
| await transaction.rollback(); | ||
| console.error(error); |
There was a problem hiding this comment.
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.
src/controllers/userController.js
Outdated
|
|
||
| return generateTokens(res, user); | ||
| } catch (err) { | ||
| console.error(err); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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).
src/controllers/userController.js
Outdated
| }); | ||
|
|
||
| } catch (error) { | ||
| console.error(error); |
There was a problem hiding this comment.
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.
src/controllers/userController.js
Outdated
| const user = await User.findOne({ | ||
| where: { id }, | ||
| }); | ||
| console.log(`New name:{name}`) |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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).
src/controllers/userController.js
Outdated
| }; | ||
|
|
||
| export const logout = async (req, res) => { | ||
| console.log('ssssssssssssssssss'); |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
src/middlewares/ownerMiddleware.js
Outdated
| if (!req.user) { | ||
| return res.status(401).json({ message: 'owner Unauthorized' }); | ||
| } | ||
| console.log(req.user, req.params); |
There was a problem hiding this comment.
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.
src/middlewares/ownerMiddleware.js
Outdated
| console.log(req.user, req.params); | ||
| const tokenUserId = Number(req.user.id); | ||
| const paramUserId = Number(req.params.id); | ||
| console.log(tokenUserId); |
There was a problem hiding this comment.
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.
src/middlewares/authMiddleware.js
Outdated
| return res | ||
| .status(401) | ||
| .json({ message: 'Unauthorized: no userData' }); | ||
| } |
There was a problem hiding this comment.
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.
| export const removeByUserId = (userId) => { | ||
| return Token.destroy({ | ||
| where: { UserId: userId }, | ||
| }); |
There was a problem hiding this comment.
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.
| debug: true, | ||
| logger: true, |
There was a problem hiding this comment.
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.
src/services/emailService.js
Outdated
| return send({ email, html, subject: 'Change of email' }); | ||
| } | ||
|
|
||
| console.log('Email is sent'); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
|
|
||
| function verifyToken(token) { | ||
| try { | ||
| return jwt.verify(token, ACCESS_SECRET); | ||
| } catch { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
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.
src/services/jwtService.js
Outdated
| verify, | ||
| signRefresh, | ||
| verifyRefresh, | ||
| verifyToken, |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
src/routes/userRoutes.js
Outdated
|
|
||
| router.post('/login', loginUser); | ||
| router.get('/refresh', refresh); | ||
| router.post('/reset-password', resetPassword,) |
There was a problem hiding this comment.
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.
src/routes/userRoutes.js
Outdated
| router.post('/login', loginUser); | ||
| router.get('/refresh', refresh); | ||
| router.post('/reset-password', resetPassword,) | ||
| router.patch('/reset-password/:resetToken', resetUserPassword,) |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
src/routes/userRoutes.js
Outdated
| // authMiddleware, | ||
| // ownerMiddleware, | ||
| changeUserName |
There was a problem hiding this comment.
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.
src/routes/userRoutes.js
Outdated
| // authMiddleware, | ||
| // ownerMiddleware, | ||
| changeUserPassword |
There was a problem hiding this comment.
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.
src/routes/userRoutes.js
Outdated
| // authMiddleware, | ||
| // ownerMiddleware, | ||
| changeUserEmail |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 }) => { |
There was a problem hiding this comment.
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) => { ... }
src/services/userService.js
Outdated
|
|
||
| // const rawResetToken = crypto.randomBytes(32).toString('hex'); | ||
| // const resetToken = hashToken(rawResetToken); | ||
| return User.create({ name, email, password, activationToken, activated }); |
There was a problem hiding this comment.
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 });
| const getAll = async () => { | ||
| const result = await User.findAll(); | ||
|
|
||
| return result; |
There was a problem hiding this comment.
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.
src/services/userService.js
Outdated
| const getById = async (id) => { | ||
| return User.findByPk(id); |
There was a problem hiding this comment.
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.
mateacademy-ai-mentor
left a comment
There was a problem hiding this 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 theauthMiddlewareto secure them. - Incomplete Authentication: The
authMiddlewarecorrectly verifies the token but doesn't attach the user data toreq.user. This is a critical step for any subsequent authorization logic. - Runtime Errors: The
cryptomodule is used inuserController.jsanduserService.jsbut 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
confirmationfield and the logic to validate that it matches the new password. - Incorrect Transaction Handling: The
userService.createfunction 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.jsneeds 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
| ); | ||
| // Use routes for the respective paths | ||
|
|
||
| app.use('/users', userRoutes); |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
| function sign(user) { | ||
| const token = jwt.sign(user, ACCESS_SECRET); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
| function signRefresh(user) { | ||
| const token = jwt.sign(user, REFRESH_SECRET); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
| function verifyToken(token) { | ||
| try { | ||
| return jwt.verify(token, ACCESS_SECRET); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
mateacademy-ai-mentor
left a comment
There was a problem hiding this 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
| ); | ||
| // Use routes for the respective paths | ||
|
|
||
| app.use('/users', userRoutes); |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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'; | |||
There was a problem hiding this comment.
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.
| 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, | ||
| }); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
No description provided.