Skip to content

Conversation

@NickDevX
Copy link

@NickDevX NickDevX commented Dec 1, 2025

First version auth app, Using library:
express.js for endpoint,
sequelize ORM,
nodemon - for fast server reload,
valibot - for middleware data validation,

Copy link

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

Choose a reason for hiding this comment

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

Review Summary

This is a strong submission with a well-organized structure and solid implementation of most features. However, a few critical issues related to the task requirements need to be addressed before this can be approved.

  1. Login Redirect: The application must redirect to the profile page after a successful login, but it currently returns a JSON response. This is because the userService handles the HTTP response directly. Please refactor the login function to return data and let the authController be responsible for sending the redirect.
  2. Unprotected Logout Route: The /logout route in src/routes/auth.route.js is missing the authMiddleware. This contradicts the requirement that logout should only be accessible to authenticated users.
  3. Incorrect Error Handling: In src/services/user.service.js, the ApiError.badRequest method is called with an object instead of a string, leading to unhelpful error messages. Please ensure you pass a proper message string.

Once these key functional requirements are met, your project will be in great shape.


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

Important

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

Tip

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

Caution

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

Footnotes

  1. Rate AI review example

  2. Re-request AI review example

  3. Add AI review comments example

const login = async (req, res) => {
const { email, password } = req.body;

await userService.login(res, { email, password });

Choose a reason for hiding this comment

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

According to the requirements, the user should be redirected to the profile page after a successful login. This function currently relies on the userService to send a response, which doesn't perform the redirect. Consider refactoring userService.login to return the necessary data (like tokens) instead of handling the res object directly. This would allow this controller to handle the response and perform the required redirect.

await emailService.sendActivationMail(email, activationToken);
}

async function login(res, { email, password }) {

Choose a reason for hiding this comment

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

This function, along with refresh and generateTokens, accepts the res object to send a response. This is considered an anti-pattern as it mixes business logic (service layer) with presentation logic (controller layer). It also directly conflicts with the requirement to redirect to the profile page after login, as this implementation sends a JSON response instead. The controller should be responsible for all response handling.

Copy link

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

Choose a reason for hiding this comment

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

Review Summary

Excellent work on this project! I am approving your submission as you've successfully implemented all the core requirements. Your error handling, validation, and routing are all very well done.

I have one important architectural suggestion for you to consider in future projects. While your current code works correctly, a more standard practice for protected routes is to have the authMiddleware verify the accessToken, decode the user's payload (e.g., { id, email }), and attach it to the request object, like req.user = userPayload;. Your controllers can then simply use req.user.id instead of relying on the refreshToken for user identification. This makes your API design cleaner and more aligned with industry standards.

This is a non-critical suggestion to help you grow as a developer. Fantastic job overall!


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

Footnotes

  1. Rate AI review example

Comment on lines +3 to +52
const getProfile = async (req, res) => {
const { refreshToken } = req.cookies;

const user = await userService.getProfile(refreshToken);

res.send(userService.normalize(user));
};

const nameUpdate = async (req, res) => {
const { name } = req.body;
const { refreshToken } = req.cookies;

await userService.nameUpdate({ refreshToken, name });

res.sendStatus(200);
};

const emailUpdate = async (req, res) => {
const { password, newEmail, confirmEmail } = req.body;
const { refreshToken } = req.cookies;

await userService.emailUpdate({
refreshToken,
password,
newEmail,
confirmEmail,
});

res.sendStatus(200);
};

const passwordUpdate = async (req, res) => {
const { password, newPassword, confirmPassword } = req.body;
const { refreshToken } = req.cookies;

await userService.passwordUpdate({
refreshToken,
password,
newPassword,
confirmPassword,
});

res.sendStatus(200);
};

export const profileController = {
getProfile,
nameUpdate,
emailUpdate,
passwordUpdate,

Choose a reason for hiding this comment

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

While this implementation works, there's a good opportunity here to improve the application's design by changing how you handle user identity in authenticated routes.

Currently, every profile-related action relies on the refreshToken passed from the cookies to identify the user. However, the authMiddleware already validates an accessToken. The standard practice is to use the accessToken for authenticating the user for each request.

A better approach would be:

  1. In your authMiddleware, after verifying the accessToken, decode it and attach the user's payload (e.g., id and email) to the req object, like req.user = userData;.
  2. In this controller, instead of getting the refreshToken from cookies, you would get the user's ID from req.user.id.
  3. Your service functions (getProfile, nameUpdate, etc.) would then accept the userId instead of the refreshToken.

This change would make your API more robust and align with standard practices, as the accessToken's primary role is to authorize requests, while the refreshToken's role is to generate new access tokens.

Comment on lines +4 to +18
export const authMiddleware = async (req, res, next) => {
const authorization = req.headers['authorization'] || '';
const [, token] = authorization.split(' ');

if (!authorization || !token) {
throw ApiError.unauthorized();
}

const userToken = jwtService.verify(token);

if (!userToken) {
throw ApiError.unauthorized();
}

next();

Choose a reason for hiding this comment

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

This middleware correctly protects the route, which is great. To make it even more powerful and align with standard practices, you can attach the decoded user payload to the request object.

The jwtService.verify(token) function returns the user data that was signed into the token. You can assign this to req, for example: req.user = userToken; before calling next().

This way, any subsequent controller or middleware that runs after authMiddleware will have direct access to the authenticated user's information (like their ID) through req.user, which simplifies the logic in your controllers.

Comment on lines +246 to +338
async function getProfile(refreshToken) {
const isValidToken = jwtService.verifyRefresh(refreshToken);

if (!isValidToken) {
throw ApiError.unauthorized();
}

const tokenData = await tokenService.findByToken(
refreshToken,
TOKEN_TYPES.REFRESH,
);

if (!tokenData || tokenData.expireAt < new Date()) {
throw ApiError.unauthorized();
}

const user = await User.findByPk(tokenData.userId);

if (!user) {
throw ApiError.unauthorized();
}

return user;
}

async function nameUpdate({ refreshToken, name }) {
const user = await getProfile(refreshToken);

user.name = name;
await user.save();
}

async function passwordUpdate({
refreshToken,
password,
newPassword,
confirmPassword,
}) {
const user = await getProfile(refreshToken);

if (!(await bcrypt.compare(password, user.password))) {
throw ApiError.badRequest('Wrong password');
}

if (newPassword !== confirmPassword) {
throw ApiError.badRequest('Confirmation and password must be equal');
}

if (await bcrypt.compare(newPassword, user.password)) {
throw ApiError.badRequest(
'New password must be different from old password',
);
}

const hashedPassword = await bcrypt.hash(newPassword, 10);

user.password = hashedPassword;
await user.save();
}

async function emailUpdate({ refreshToken, password, newEmail, confirmEmail }) {
const user = await getProfile(refreshToken);
const isUsedEmail = await findByEmail(newEmail);

if (isUsedEmail) {
throw ApiError.badRequest('Email is already in use');
}

if (!(await bcrypt.compare(password, user.password))) {
throw ApiError.badRequest('Wrong password');
}

if (newEmail !== confirmEmail) {
throw ApiError.badRequest('Email confirmation does not match');
}

if (newEmail === user.email) {
throw ApiError.badRequest('New email must be different from old email');
}

const oldEmail = user.email;

user.email = newEmail;
await user.save();

const html = `
<h1>Your email ${oldEmail} has been changed to ${newEmail}</h1>`;

await emailService.send({
email: oldEmail,
subject: 'Your email has been changed',
html,
});

Choose a reason for hiding this comment

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

This set of profile management functions (getProfile, nameUpdate, passwordUpdate, emailUpdate) currently relies on the refreshToken to identify and authenticate the user for every action. This leads to some design issues:

  1. Redundant Authentication: The getProfile function re-validates the refresh token, but this should be the responsibility of the authMiddleware using the accessToken.
  2. Incorrect Token Usage: The primary purpose of an accessToken is to authorize requests like these. The refreshToken should only be used to get a new accessToken.

A more standard approach would be:

  1. In authMiddleware, verify the accessToken and attach the user's decoded payload (e.g., { id: 1, email: '...' }) to req.user.
  2. In the profileController, pass the user's ID (req.user.id) to the service functions.
  3. Refactor these service functions to accept a userId instead of a refreshToken. For example, getProfile would become getProfile(userId) and would simply fetch the user with User.findByPk(userId).

This change improves the separation of concerns, uses tokens for their intended purposes, and makes the service layer cleaner and more focused on business logic.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants