Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 226 additions & 0 deletions src/controller/auth.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/* eslint-disable no-useless-return */
Comment thread
aakash1sadhu marked this conversation as resolved.
import { userServices } from '../services/user.services.js';
import { emailServices } from '../services/email.services.js';
import { jwtService } from '../services/jwt.services.js';
Comment thread
aakash1sadhu marked this conversation as resolved.
Outdated
import bcrypt, { compare } from 'bcrypt';
import { v4 as uuidv4 } from 'uuid';
Comment thread
aakash1sadhu marked this conversation as resolved.
import { User } from '../models/User.model.js';

function validateEmail(value) {
const EMAIL_PATTERN = /^[\w.+-]+@([\w-]+\.){1,3}[\w-]{2,}$/;

Comment thread
aakash1sadhu marked this conversation as resolved.
if (!value) {
return 'Email is required';
}

if (!EMAIL_PATTERN.test(value)) {
return 'Email is not valid';
}
}

function validateName(value) {
if (!value) {
return 'Name is required';
}

if (value.trim().length < 4) {
return 'Name length must be more than 4 symbols';
}
}

function validatePassword(value) {
if (!value) {
return 'Password is required';
}

if (value.length < 6) {
return 'At least 6 characters';
}
}

const registerUser = async (req, res) => {
try {
const { name, email, password } = req.body;
const activationToken = uuidv4();

if (!name || !email || !password) {
res.status(400).json({ message: 'All fields are required' });

return;
}

const errors = {
email: validateEmail(email),
password: validatePassword(password),
name: validateName(name),
};

if (errors.email || errors.password || errors.name) {
res.status(400).json(errors);

return;
}

const hashPassword = bcrypt.hashSync(password, 10);

await userServices.registerUser(name, email, hashPassword, activationToken);

await emailServices.sendActivationEmail(email, activationToken);

res
.status(201)
.json({ message: 'User registered. Check your email for activation.' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};

const activateUser = async (req, res) => {
const { activationToken } = req.params;

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

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

return;
}

user.activationToken = null;
await user.save();

res.redirect('/profile');
};

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

if (!email || !password) {
return res.status(400).send({ message: 'All fields are required' });
}

const user = await userServices.findUser(email);

if (!user) {
res.status(401).json({ message: 'User not found' });

return;
}

if (user.activationToken !== null) {
res.status(403).json({ message: 'Please activate your email' });

return;
}

const isPasswordValid = await compare(password, user.password);

if (!isPasswordValid) {
res.status(401).json({ message: 'Invalid credentials' });

return;
}

await generateTokens(res, user);
res.redirect('/profile');
};

const refresh = (req, res) => {
const { refreshToken } = req.cookies;

const user = jwtService.verifyRefresh(refreshToken);

if (!user) {
res.sendStatus(401);

return;
}

generateTokens(res, user);
};

const generateTokens = async (res, user) => {
const normilizeUser = userServices.normilizeUser(user);
const accessToken = jwtService.sign(normilizeUser);
const refreshToken = jwtService.signRefresh(normilizeUser);

res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true,
maxAge: 30 * 24 * 60 * 60 * 1000,
});

res.send({
user: normilizeUser,
accessToken,
});
};

const logout = (req, res) => {
res.clearCookie('refreshToken').redirect('/login');
};

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

if (!email) {
res.status(400).json({ message: 'Email is required' });

return;
}

const user = await userServices.findUser(email);

if (!user) {
res.sendStatus(200);

return;
}

const resetToken = uuidv4();

user.resetToken = resetToken;

await user.save();
await emailServices.sendResetPasswordEmail(email, resetToken);

res.send({ message: 'Password reset email sent' });
};

const resetPassword = async (req, res) => {
const { resetToken } = req.params;
const { password, confirmation } = req.body;

Comment thread
aakash1sadhu marked this conversation as resolved.
if (!password || password !== confirmation) {
res.status(400).json({ message: 'Passwords do not match' });

return;
}

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

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

return;
}

user.password = bcrypt.hashSync(password, 10);
user.resetToken = null;

await user.save();

res.send({ message: 'Password successfully changed' });
};

export const authController = {
registerUser,
activateUser,
loginUser,
refresh,
logout,
forgot,
resetPassword,
};
108 changes: 108 additions & 0 deletions src/controller/user.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { User } from '../models/User.model.js';
import { userServices } from '../services/user.services.js';
import { emailServices } from '../services/email.services.js';
import bcrypt from 'bcrypt';
Comment thread
aakash1sadhu marked this conversation as resolved.

const getAllUsers = async (req, res) => {
const users = await User.findAll();

res.send(users);
Comment thread
aakash1sadhu marked this conversation as resolved.
Comment thread
aakash1sadhu marked this conversation as resolved.
};

const getUserById = async (req, res) => {
const { userId } = req.params;

const user = await userServices.findUserById(userId);

res.send(user);
};

const updateName = async (req, res) => {
try {
const userId = req.user.userId;
const { name } = req.body;

if (!name || !name.trim()) {
res.status(400).json({ message: 'Name is required' });

return;
}

const user = await userServices.updateNameService(userId, name);

res.send(user);
} catch (error) {
res.status(500).send(error);
}
};
Comment thread
aakash1sadhu marked this conversation as resolved.

const updatePassword = async (req, res) => {
const { oldPassword, newPassword, confirmation } = req.body;
const userId = req.user.userId;

if (newPassword !== confirmation) {
res.status(400).json({ message: 'Passwords do not match' });

return;
}

const user = await userServices.findUserById(userId);
const isValid = await bcrypt.compare(oldPassword, user.password);

if (!isValid) {
res.status(401).json({ message: 'Old password is incorrect' });

return;
}

user.password = bcrypt.hashSync(newPassword, 10);
await user.save();

res.send({ message: 'Password updated successfully' });
Comment thread
aakash1sadhu marked this conversation as resolved.
};

const updateEmail = async (req, res) => {
const { password, newEmail, confirmation } = req.body;
const userId = req.user.userId;

if (!newEmail || !confirmation) {
res
.status(400)
.json({ message: 'New email and confirmation are required' });

return;
}

if (newEmail !== confirmation) {
res.status(400).json({ message: 'Emails do not match' });

return;
}

const user = await userServices.findUserById(userId);

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

if (!isValidPassword) {
res.status(401).json({ message: 'Invalid password' });

return;
}

const oldEmail = user.email;

user.email = newEmail;

await user.save();
await emailServices.sendEmailChangedNotification(oldEmail, newEmail);

res.send({ message: 'Email updated successfully' });
};

export const userController = {
getAllUsers,
getUserById,
updateName,
updateEmail,
updatePassword,
};
24 changes: 23 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,23 @@
'use strict';
import express from 'express';
Comment thread
aakash1sadhu marked this conversation as resolved.
import authRouter from './router/auth.router.js';
import userRouter from './router/user.router.js';
import cookieParser from 'cookieParser';
Comment thread
aakash1sadhu marked this conversation as resolved.
Outdated
import { authMiddleware } from './middlewares/auth.mildware.js';

const app = express();
const port = process.env.PORT || 3000;
Comment on lines +5 to +8
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 GET / endpoint returns ALL users without any context about which user is requesting. An authenticated user can see all other users' data. Consider removing this endpoint or restricting it to admin users only.


app.use(express.json());
app.use(cookieParser());

app.use('/', authRouter);
Comment thread
aakash1sadhu marked this conversation as resolved.
app.use('/user', authMiddleware, userRouter);

app.use('*', (req, res) => {
res.status(404).json({ message: 'Not found' });
});

app.listen(port, () => {
// eslint-disable-next-line no-console
console.log(`Server running at http://localhost:${port}`);
});
24 changes: 24 additions & 0 deletions src/middlewares/auth.mildware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { jwtService } from '../services/jwt.service.js';
Comment thread
aakash1sadhu marked this conversation as resolved.
Outdated
Comment thread
aakash1sadhu marked this conversation as resolved.
Outdated

export const authMiddleware = (req, res, next) => {
Comment thread
aakash1sadhu marked this conversation as resolved.
const authorization = req.headers.authorization;

if (!authorization) {
return res.sendStatus(401);
}

const [, token] = authorization.split(' ');

if (!token) {
return res.sendStatus(401);
}

const userData = jwtService.verify(token);

if (!userData) {
Comment on lines +9 to +18
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 validation functions return error messages but lack early returns after the first check. If both conditions fail, only the first error is returned correctly, but adding explicit returns after each condition improves clarity.

return res.sendStatus(401);
}

req.user = userData;
next();
};
Loading
Loading