Skip to content

add task implementation#277

Open
ANDRIYTS1234 wants to merge 4 commits intomate-academy:masterfrom
ANDRIYTS1234:develop
Open

add task implementation#277
ANDRIYTS1234 wants to merge 4 commits intomate-academy:masterfrom
ANDRIYTS1234:develop

Conversation

@ANDRIYTS1234
Copy link
Copy Markdown

No description provided.

Copy link
Copy Markdown

@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

The implementation has several critical blockers that prevent task completion. Password validation is insufficient - it only checks length > 6, but requirements explicitly state users must be informed of password rules and validation must check them (uppercase, lowercase, numbers, special characters). Auth routes lack non-authenticated user guards - registration, login, forgot-password, and reset-password must be accessible ONLY to non-authenticated users as explicitly required. Error handlers use incorrect status codes - res.send() returns implicit 200 for errors instead of 400/401/403/500. Registration endpoint returns the hashed password in the response, creating a security vulnerability. Logout route is unprotected - it has no authMiddleware despite being an authenticated-only operation. Activation doesn't clear the activationToken after successful use. Email change immediately updates without requiring confirmation from the new email. The changePassword function also doesn't validate the new password against the same rules enforced at registration. Additionally, ESLint configuration is missing TypeScript parser support, preventing code validation. These issues must be addressed 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

  1. Rate AI review example

  2. Re-request AI review example

  3. Add AI review comments example

Comment thread .eslintrc.js
'no-return-await': 'off',
},
plugins: ['jest']
plugins: ['jest', '@typescript-eslint']
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The registration route doesn't check if user is already authenticated. The task requires this route be accessible ONLY to non-authenticated users.

Comment thread .eslintrc.js
'@typescript-eslint/no-unused-vars': ['warn'],
'no-shadow': 'off',
'@typescript-eslint/no-shadow': ['error'],
'no-return-await': 'off',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error handlers use res.send() with status 200 instead of res.status(). For errors, appropriate status codes like 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden) should be used.

Comment thread src/controllers/authController.ts Outdated
Comment on lines +20 to +22
res.send({ error: (error as Error).message });
}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The password validation only checks length > 6. The requirements state: 'Inform users about the rules for a password and check them'. Consider implementing more comprehensive password rules that can be shown to users during registration.

Comment thread src/controllers/authController.ts Outdated
Comment on lines +29 to +30

res.send('Акаунт активовано');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The activation endpoint returns plain text 'Акаунт активовано'. The requirements state: 'redirect to Profile after the activation'. Since this is an API endpoint, consider returning a response that indicates successful activation so the frontend can redirect (e.g., status code 200 with a success indicator, or redirect URL).

Comment thread src/controllers/authController.ts Outdated
Comment on lines +76 to +77

res.send({ message: 'Успішний вихід' });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The logout endpoint returns a success message but the requirements state: 'Redirect to login after logging out'. Consider returning an appropriate status code so the frontend can redirect to login.

res.send({ message: "Ім'я змінено" });
} catch (error) {
res.send({ error: (error as Error).message });
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The activation catch block uses console.log instead of sending an error response to the client, silently failing on errors.

export const updateName = async (req: Request, res: Response) => {
try {
const email = (req as any).user.email;
const { newName } = req.body;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The task requires confirming new email before the change takes effect. Currently, email is changed immediately. Consider implementing a two-step confirmation where the new email must be verified first.

Comment on lines +14 to +15

return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The Token model limits refreshToken to 500 characters. JWT tokens can vary in size depending on the claims and algorithm. Consider using TEXT type or increasing the limit to prevent potential truncation issues.

Comment thread src/models/Token.ts
@Column({
type: DataType.STRING(500),
allowNull: false,
unique: true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The authMiddleware doesn't validate the token format before accessing it. If header is 'Bearer' without a token, split()[1] would be undefined. Consider adding validation for the token existence after the split.

Comment thread src/routes/authRouter.ts Outdated
Comment on lines +13 to +20

authRouter.post('/registration', registration);
authRouter.get('/activation/:activationToken', activation);
authRouter.post('/login', login);
authRouter.post('/refresh', refreshing);
authRouter.post('/logout', logout);

authRouter.post('/forgot-password', forgotPassword);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The authRouter doesn't have middleware to restrict registration, login, forgot-password, and reset-password routes to non-authenticated users only. The task requires these routes be accessible ONLY to non-authenticated users.

Comment thread src/routes/authRouter.ts Outdated
Comment on lines +18 to +19
authRouter.post('/logout', logout);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The password validation only checks length > 6 characters. The task requires informing users about password rules and validating against them. Consider implementing comprehensive password rules (uppercase, lowercase, numbers, special characters) that can be displayed to users during registration.

Comment on lines +13 to +21
const existingUser = await User.findOne({ where: { email: email } });

if (existingUser) {
throw new Error('Користувач з таким емейлом уже зареєстрований');
}

if (password.length < 6) {
throw new Error('Довжина паролю має бути більше 6 символів');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The authRouter lacks middleware to restrict registration, login, forgot-password, and reset-password routes to non-authenticated users only. The requirements explicitly state these routes should be accessible ONLY to non-authenticated users.

Comment on lines +84 to +88

return {
user,
accessToken,
refreshToken,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The login function returns the full user object which includes the hashed password. Consider returning only necessary user data (id, name, email) without sensitive fields.

Comment on lines +46 to +47

user.isActive = true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The activate function sets isActive to true but doesn't clear the activationToken. After activation, the token remains in the database which is unnecessary and could be a security concern.

Comment thread src/services/authService.ts Outdated
Comment on lines +18 to +20

if (password.length < 6) {
throw new Error('Довжина паролю має бути більше 6 символів');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The password validation only checks length > 6 characters. The requirements state to 'Inform users about the rules for a password and check them'. Consider implementing comprehensive password rules (uppercase, lowercase, numbers, special characters) that can be displayed to users.

Comment on lines +13 to +21

export const sendActivationEmail = async (
email: string,
activationToken: string,
) => {
await transporter.sendMail({
from: process.env.SMTP_USER,
to: email,
subject: 'Активація акаунта',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The authRouter has no middleware to restrict routes to non-authenticated users. According to requirements, registration, login, activation, and password reset must be accessible ONLY to non-authenticated users. Consider adding middleware to check if user is already authenticated on these routes.

email: string,
activationToken: string,
) => {
await transporter.sendMail({
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The logout route has no authentication middleware. According to requirements, logout must be accessible only to authenticated users. Consider adding authMiddleware to this route.

Comment on lines +18 to +20
await transporter.sendMail({
from: process.env.SMTP_USER,
to: email,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The password validation only checks for minimum length (6 characters). The task requires informing users about password rules and validating them. Consider implementing more comprehensive validation (uppercase, lowercase, numbers, special characters) and returning clear error messages about all failed rules.

Comment on lines +7 to +11
secure: false,
auth: {
user: process.env.SMTP_USER as string,
pass: process.env.SMTP_PASSWORD as string,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The register function doesn't validate email format before saving to the database. Invalid email formats could be stored.

Comment thread src/routes/userRouter.ts
import { authMiddleware } from '../middlewares/authMiddleware';

export const userRouter = Router();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The routes are well-structured and the userRouter correctly applies authMiddleware to all protected routes.

Comment on lines +33 to +36

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

user.password = hashedNewPass;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The changePassword function doesn't validate the new password against the same rules used during registration. Consider applying consistent password validation to ensure the new password meets requirements (such as minimum length and complexity rules).

Comment on lines +56 to +62

const newActivationToken = uuidv4();

user.email = newEmail;
user.activationToken = newActivationToken;
user.isActive = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The email change flow correctly requires password validation, sends activation to the new email, and notifies the old email address. However, the new email is immediately updated without waiting for confirmation. Consider whether the new email should be verified first before finalizing the change.

Comment thread tsconfig.json
Comment on lines +1 to +17
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"moduleResolution": "node",
"ignoreDeprecations": "6.0",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"types": ["node"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The tsconfig.json is properly configured with necessary options for TypeScript compilation, including decorators support for sequelize-typescript, strict mode, and CommonJS module output.

Copy link
Copy Markdown

@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

Your implementation has 7 critical blockers that must be fixed before approval:

  1. Password validation is incomplete (REQ-4) - Currently only checks length > 6. You must add regex validation requiring uppercase, lowercase, numbers, and special characters. Update both register and changePassword functions.

  2. Registration exposes hashed password - res.send(newUser) returns the entire user object including the password field. Return only safe fields like { id, name, email, isActive } instead.

  3. Missing route guards for non-authenticated users (REQ-2, REQ-6, REQ-10, REQ-15) - Registration, login, forgot-password, reset-password, and activation routes are accessible to all users. Add notAuthMiddleware to these routes in authRouter.ts.

  4. Logout route is unprotected (REQ-13) - Add authMiddleware to the logout route.

  5. ActivationToken not cleared after use - After successful activation, set user.activationToken = null before saving.

  6. Error handlers return HTTP 200 status - Use res.status(400/401/500).send() instead of res.send() for all error responses.

  7. changePassword doesn't validate new password - Apply the same password rules (uppercase, lowercase, numbers, special characters) in userService.ts.

These issues represent security vulnerabilities and broken functionality that violate explicit task requirements. Please address all 7 issues and resubmit.


✨ 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

Comment thread .eslintrc.js
rules: {
'no-proto': 0
'no-proto': 0,
'no-console': 0,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security issue: res.send(newUser) returns the entire user object including the hashed password field. This exposes sensitive data to the client. Return only safe fields like { message: '...', user: { id, name, email } } or { message: '...' }.

Comment thread .eslintrc.js
'no-proto': 0,
'no-console': 0,
'indent': 'off',
'@typescript-eslint/no-shadow': ['error'],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error handler uses res.send() which returns HTTP 200 status. Should use res.status(400/401/500).send() for error responses to comply with REST conventions.

Comment thread .eslintrc.js
'@typescript-eslint/no-unused-vars': ['warn'],
'no-shadow': 'off',
'@typescript-eslint/no-shadow': ['error'],
'no-return-await': 'off',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Password validation only checks length > 6, but requirements state users must be informed of rules AND validation must check them (uppercase, lowercase, numbers, special characters). Add regex validation for these rules.

Comment thread .eslintrc.js
},
rules: {
'no-proto': 0
'no-proto': 0,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error handler uses implicit 200 status. Should use appropriate error status codes.

Comment thread .eslintrc.js
},
plugins: ['jest']
plugins: ['jest', '@typescript-eslint']
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error handler uses implicit 200 status. Should use appropriate error status codes.

Comment thread .eslintrc.js
jest: true,
node: true,
},
rules: {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requirement not met: Registration route is accessible to all users. According to REQ-2, registration must be accessible ONLY to non-authenticated users. Add a guard middleware like notAuthMiddleware.

Comment thread .eslintrc.js
rules: {
'no-proto': 0
'no-proto': 0,
'no-console': 0,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requirement not met: Login route is accessible to all users. According to REQ-10, login must be accessible ONLY to non-authenticated users. Add a guard middleware.

Comment thread .eslintrc.js
'@typescript-eslint/no-unused-vars': ['warn'],
'no-shadow': 'off',
'@typescript-eslint/no-shadow': ['error'],
'no-return-await': 'off',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requirement not met: Forgot password route is accessible to all users. According to REQ-15, it must be accessible ONLY to non-authenticated users. Add a guard middleware.

Comment thread .eslintrc.js
'no-shadow': 'off',
'@typescript-eslint/no-shadow': ['error'],
'no-return-await': 'off',
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requirement not met: Reset password route is accessible to all users. According to REQ-15, it must be accessible ONLY to non-authenticated users. Add a guard middleware.

Comment thread .eslintrc.js
},
env: {
jest: true
jest: true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requirement not met: Activation route is accessible to all users. According to REQ-6, activation page must be accessible ONLY to non-authenticated users. Add a guard middleware.

Comment on lines +27 to +28

await activate(activationToken);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Password validation only checks length > 6. Per requirements, users must be informed of password rules and validation must check them (uppercase, lowercase, numbers, special characters).


try {
const newUser = await register(name, email, password);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Registration returns the entire user object including hashed password - this is a security vulnerability. Should return user-safe data only (id, name, email).

Comment on lines +54 to +55
const refreshToken = req.cookies.refreshToken;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Activation succeeds but doesn't clear the activationToken. The token remains valid after use.

};

export const login = async (req: Request, res: Response) => {
try {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

changePassword doesn't validate the new password against the password rules enforced at registration. Should check for uppercase, lowercase, numbers, and special characters.

Comment thread src/controllers/authController.ts Outdated

res.send(result.newAccessToken);
} catch (error) {
res.send({ error: (error as Error).message });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

changeEmail immediately updates the email and deactivates the account without requiring confirmation from the new email. Per requirements, email should only change after new email confirmation.

const newUser = await register(name, email, password);

res.send(newUser);
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error responses use res.send() which returns implicit 200 status. Should use res.status(400).send() or similar for validation errors.

Comment thread src/controllers/authController.ts Outdated
Comment on lines +30 to +31
res.send('Акаунт активовано');
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error responses use res.send() with implicit 200 status. Should return appropriate status codes (401 for unauthorized, 400 for bad request, 500 for server errors).

});

res.send({ user: resObj.user, accessToken: resObj.accessToken });
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error responses use res.send() with implicit 200 status instead of proper error status codes.

res.clearCookie('refreshToken');

res.send({ message: 'Успішний вихід' });
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error responses use res.send() with implicit 200 status instead of proper error status codes.


await forgotPass(email);
res.send({ message: 'Лист для відновлення пароля відправлено' });
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error responses use res.send() with implicit 200 status instead of proper error status codes.


await resetPass(resetToken, password);
res.send({ message: 'Пароль успішно змінено' });
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error responses use res.send() with implicit 200 status instead of proper error status codes.

pass: process.env.SMTP_PASSWORD as string,
},
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Registration route lacks guard for non-authenticated users. Per REQ-2, registration must be accessible ONLY to non-authenticated users. Add a notAuthMiddleware.

},
});

export const sendActivationEmail = async (
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Activation route lacks guard for non-authenticated users. Per REQ-6, activation must be accessible ONLY to non-authenticated users. Add a notAuthMiddleware.

});

export const sendActivationEmail = async (
email: string,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Login route lacks guard for non-authenticated users. Per REQ-10, login must be accessible ONLY to non-authenticated users. Add a notAuthMiddleware.

export const sendActivationEmail = async (
email: string,
activationToken: string,
) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Logout route is missing authMiddleware. Per REQ-13, logout must be accessible ONLY to authenticated users. Add authMiddleware to this route.

) => {
await transporter.sendMail({
from: process.env.SMTP_USER,
to: email,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forgot-password route lacks guard for non-authenticated users. Per REQ-15, it must be accessible ONLY to non-authenticated users. Add a notAuthMiddleware.

await transporter.sendMail({
from: process.env.SMTP_USER,
to: email,
subject: 'Активація акаунта',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reset-password route lacks guard for non-authenticated users. Per REQ-15, it must be accessible ONLY to non-authenticated users. Add a notAuthMiddleware.

Comment on lines +18 to +20
await transporter.sendMail({
from: process.env.SMTP_USER,
to: email,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Password validation only checks length < 6. Per REQ-3 and REQ-4, users must be informed of password rules and validation must check them (uppercase, lowercase, numbers, special characters).

from: process.env.SMTP_USER,
to: email,
subject: 'Відновлення пароля',
html: `<a href="${process.env.CLIENT_URL}/reset-password/${resetToken}">Змінити пароль</a>`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Registration returns the entire user object including the hashed password. This is a security vulnerability - the response should only return a success message.


if (existingUser) {
throw new Error('Користувач з таким емейлом уже зареєстрований');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

REQ-13 not met: Logout route has no authMiddleware. Per requirements, logout must be accessible ONLY to authenticated users. Add authMiddleware here.

Comment thread src/services/authService.ts Outdated
Comment on lines +13 to +20
const existingUser = await User.findOne({ where: { email: email } });

if (existingUser) {
throw new Error('Користувач з таким емейлом уже зареєстрований');
}

if (password.length < 6) {
throw new Error('Довжина паролю має бути більше 6 символів');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

REQ-2, REQ-6, REQ-10, REQ-15 not met: These routes lack non-authenticated user guards. Per requirements, registration, activation, login, forgot-password, and reset-password must be accessible ONLY to non-authenticated users. Need to create and apply a notAuthMiddleware to these routes.

Comment thread src/services/authService.ts Outdated
Comment on lines +18 to +20

if (password.length < 6) {
throw new Error('Довжина паролю має бути більше 6 символів');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

REQ-4 not met: Password validation only checks length > 6. Per requirements, validation must check for uppercase, lowercase, numbers, and special characters. Add regex validation for these rules.

});

await sendActivationEmail(email, activationToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security issue: return newUser exposes the user object including hashed password to the controller. Return only safe data like { message: '...' } or extract only safe fields.

Comment on lines +46 to +47

user.isActive = true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security issue: After activation, the activationToken is not cleared from the user record. It should be set to null to prevent token reuse.

if (!user) {
throw new Error('Недійсне або застаріле посилання');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

REQ-4 not met: The new password is not validated against the stated password rules. The resetPassword function should check for uppercase, lowercase, numbers, and special characters.

Comment thread src/routes/authRouter.ts
} from '../controllers/authController';

export const authRouter = Router();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

REQ-2 not met: Registration route is accessible to all users. Per requirements, registration must be accessible ONLY to non-authenticated users. Add notAuthMiddleware guard.

Comment thread src/routes/authRouter.ts Outdated

export const authRouter = Router();

authRouter.post('/registration', registration);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

REQ-6 not met: Activation route is accessible to all users. Per requirements, activation must be accessible ONLY to non-authenticated users. Add notAuthMiddleware guard.

Comment thread src/routes/authRouter.ts Outdated
export const authRouter = Router();

authRouter.post('/registration', registration);
authRouter.get('/activation/:activationToken', activation);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

REQ-10 not met: Login route is accessible to all users. Per requirements, login must be accessible ONLY to non-authenticated users. Add notAuthMiddleware guard.

Comment thread src/routes/authRouter.ts
authRouter.post('/registration', registration);
authRouter.get('/activation/:activationToken', activation);
authRouter.post('/login', login);
authRouter.post('/refresh', refreshing);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

REQ-13 not met: Logout route is not protected. Per requirements, logout must be accessible ONLY to authenticated users. Add authMiddleware guard.

Comment thread src/routes/authRouter.ts Outdated
authRouter.post('/login', login);
authRouter.post('/refresh', refreshing);
authRouter.post('/logout', logout);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

REQ-15 not met: Forgot password route is accessible to all users. Per requirements, password reset must be accessible ONLY to non-authenticated users. Add notAuthMiddleware guard.

Comment thread src/routes/authRouter.ts Outdated
authRouter.post('/refresh', refreshing);
authRouter.post('/logout', logout);

authRouter.post('/forgot-password', forgotPassword);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

REQ-15 not met: Reset password route is accessible to all users. Per requirements, password reset must be accessible ONLY to non-authenticated users. Add notAuthMiddleware guard.

Comment thread src/routes/authRouter.ts Outdated
Comment on lines +18 to +20
authRouter.post('/logout', logout);

authRouter.post('/forgot-password', forgotPassword);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

REQ-4 not met: Password validation only checks length < 6. Per requirements, users must be informed of password rules AND validation must check them (uppercase, lowercase, numbers, special characters). Add regex validation for these rules.

Comment thread src/routes/userRouter.ts

userRouter.use(authMiddleware);

userRouter.patch('/name', updateName);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requirement not met: Registration route is accessible to all users. Per REQ-2, registration must be accessible ONLY to non-authenticated users. Add a guard middleware (e.g., notAuthMiddleware).

Comment thread src/routes/userRouter.ts
userRouter.use(authMiddleware);

userRouter.patch('/name', updateName);
userRouter.patch('/email', updateEmail);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requirement not met: Activation route is accessible to all users. Per REQ-6, activation page must be accessible ONLY to non-authenticated users. Add a guard middleware.

Comment thread src/routes/userRouter.ts

userRouter.patch('/name', updateName);
userRouter.patch('/email', updateEmail);
userRouter.patch('/password', updatePassword);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requirement not met: Login route is accessible to all users. Per REQ-10, login must be accessible ONLY to non-authenticated users. Add a guard middleware.

Comment on lines +16 to +37

export const changePassword = async (
email: string,
oldPassword: string,
newPassword: string,
) => {
const user = await User.findOne({ where: { email } });

if (!user) {
throw new Error('Користувача не існує');
}

const valid = await bcrypt.compare(oldPassword, user.password);

if (!valid) {
throw new Error('Невірний старий пароль');
}

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

user.password = hashedNewPass;
await user.save();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The changePassword function doesn't validate the new password against the stated password rules. Per requirements, the new password must follow the same rules enforced during registration (uppercase, lowercase, numbers, special characters). Add validation similar to what's in the register function in authService.ts.

Copy link
Copy Markdown

@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

The implementation shows solid progress with correct password validation, proper route guards, token cleanup, and safe field patterns. However, two key issues need fixing: (1) Express response chaining must be corrected from 'res.send(201).send(newUser)' to 'res.status(201).send(newUser)', and (2) Data exposure in login/refresh functions must be addressed by returning only safe fields like { id, name, email, isActive } instead of the full Sequelize User object. A minor fix is also needed in authMiddleware.ts where the error message typo 'He авторизовано' should use Cyrillic 'Не'. These fixes will make the auth system production-ready.


✨ 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

Comment thread .eslintrc.js
rules: {
'no-proto': 0
'no-proto': 0,
'no-console': 0,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Invalid syntax: res.send(201).send(newUser). Express res.send() returns Response but chaining two send() calls causes TypeError. Use res.status(201).send(newUser) instead.

Comment thread .eslintrc.js
'no-proto': 0,
'no-console': 0,
'indent': 'off',
'@typescript-eslint/no-shadow': ['error'],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Invalid syntax: res.send(400).send(...). Use res.status(400).send(...).

Comment thread .eslintrc.js
},
plugins: ['jest']
plugins: ['jest', '@typescript-eslint']
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Invalid syntax: res.send(200).send(...). Use res.status(200).send(...).

Comment thread .eslintrc.js
jest: true
jest: true,
node: true,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Invalid syntax: res.send(200).send(...). Use res.status(200).send(...).

Comment thread .eslintrc.js
},
rules: {
'no-proto': 0
'no-proto': 0,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Invalid syntax: res.send(400).send(...). Use res.status(400).send(...).

Comment thread .eslintrc.js
'no-shadow': 'off',
'@typescript-eslint/no-shadow': ['error'],
'no-return-await': 'off',
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Invalid syntax: res.send(200).send(...). Use res.status(200).send(...).

Comment thread .eslintrc.js
},
plugins: ['jest']
plugins: ['jest', '@typescript-eslint']
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Invalid syntax: res.send(400).send(...). Use res.status(400).send(...).

Comment thread src/controllers/userController.ts Outdated

res.send(200).send({ message: 'Лист для підтвердження відправлено на нову пошту' });
} catch (error) {
res.send(400).send({ error: (error as Error).message });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send() returns undefined, so .send(newUser) never executes. Use res.status(201).send(newUser) instead.

await changeName(email, newName);

res.send(200).send({ message: "Ім'я змінено" });
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: Should be res.status(200).send(...)

res.send(200).send({ message: "Ім'я змінено" });
} catch (error) {
res.send(400).send({ error: (error as Error).message });
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: Should be res.status(400).send(...)

Comment thread src/controllers/userController.ts Outdated

await changePassword(email, oldPassword, newPassword);

res.send(200).send({ message: 'Пароль змінено' });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: Should be res.status(200).send({ user: resObj.user, accessToken: resObj.accessToken })

Comment thread src/controllers/userController.ts Outdated

res.send(200).send({ message: 'Пароль змінено' });
} catch (error) {
res.send(400).send({ error: (error as Error).message });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: Should be res.status(401).send(...)

const { newEmail, password } = req.body;

await changeEmail(oldEmail, newEmail, password);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: Should be res.status(200).send(...)

await changeEmail(oldEmail, newEmail, password);

res.send(200).send({ message: 'Лист для підтвердження відправлено на нову пошту' });
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: Should be res.status(400).send(...)

const { newName } = req.body;

await changeName(email, newName);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: Should be res.status(200).send(...)

await changeName(email, newName);

res.send(200).send({ message: "Ім'я змінено" });
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: Should be res.status(400).send(...)

}

await changePassword(email, oldPassword, newPassword);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: Should be res.status(200).send(...)

await changePassword(email, oldPassword, newPassword);

res.send(200).send({ message: 'Пароль змінено' });
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: Should be res.status(400).send(...)


try {
const newUser = await register(name, email, password);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(201).send(newUser) is incorrect. res.send() sends the response and cannot be chained with another send(). Use res.status(201).send(newUser) instead. Same issue on all error handlers in this file - should be res.status(400).send(...).

const newUser = await register(name, email, password);

res.send(201).send(newUser);
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(400).send(...) should be res.status(400).send(...). The send() method ends the response cycle.

const activationToken = req.params.activationToken as string;

await activate(activationToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(200).send(...) should be res.status(200).send(...).

await activate(activationToken);

res.send(200).send('Акаунт активовано');
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(400).send(...) should be res.status(400).send(...).

maxAge: 30 * 24 * 60 * 60 * 1000,
httpOnly: true,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SECURITY ISSUE: resObj.user returns the entire Sequelize User object including the hashed password field. Return only safe fields like { id, name, email, isActive } instead.

});

res.send(200).send({ user: resObj.user, accessToken: resObj.accessToken });
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(401).send(...) should be res.status(401).send(...).

maxAge: 30 * 24 * 60 * 60 * 1000,
httpOnly: true,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(200).send(...) should be res.status(200).send(...).

});

res.send(200).send(result.newAccessToken);
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(401).send(...) should be res.status(401).send(...).

await lout(refreshToken);

res.clearCookie('refreshToken');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(204).send(...) should be res.status(204).send(...) or simply res.sendStatus(204).

res.clearCookie('refreshToken');

res.send(204).send({ message: 'Успішний вихід' });
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(400).send(...) should be res.status(400).send(...).

try {
const email = req.body.email;

await forgotPass(email);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(200).send(...) should be res.status(200).send(...).


await forgotPass(email);
res.send(200).send({ message: 'Лист для відновлення пароля відправлено' });
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(400).send(...) should be res.status(400).send(...).

throw new Error('Паролі не збігаються');
}

await resetPass(resetToken, password);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(200).send(...) should be res.status(200).send(...).


await resetPass(resetToken, password);
res.send(200).send({ message: 'Пароль успішно змінено' });
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(400).send(...) should be res.status(400).send(...).

const { name, email, password } = req.body;

try {
const newUser = await register(name, email, password);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(400).send(...) should be res.status(400).send(...).

try {
const activationToken = req.params.activationToken as string;

await activate(activationToken);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(400).send(...) should be res.status(400).send(...).

const activationToken = req.params.activationToken as string;

await activate(activationToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(400).send(...) should be res.status(400).send(...).

maxAge: 30 * 24 * 60 * 60 * 1000,
httpOnly: true,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(400).send(...) should be res.status(400).send(...).

httpOnly: true,
});

res.send(200).send({ user: resObj.user, accessToken: resObj.accessToken });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BUG: res.send(400).send(...) should be res.status(400).send(...).

) => {
const header = req.headers.authorization;

if (!header) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor: 'He авторизовано' appears to be a typo (Latin 'He' instead of Cyrillic 'Не'), but matches the pattern on line 25 where 'Не авторизовано' is used correctly.

refreshToken: refreshToken,
});

return {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SECURITY ISSUE: Returns entire User object including hashed password. Change to { id: user.id, name: user.name, email: user.email, isActive: user.isActive } to match the safe pattern used in register function (lines 37-42).

Copy link
Copy Markdown

@Anton-Kuchmasov Anton-Kuchmasov left a comment

Choose a reason for hiding this comment

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

Good job!

const user = await User.findOne({ where: { email } });

if (!user) {
throw new Error('Невірний емейл або пароль');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

consider avoiding non-English comments in your code

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.

3 participants