-
Notifications
You must be signed in to change notification settings - Fork 347
solution #273
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?
solution #273
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,226 @@ | ||
| /* eslint-disable no-useless-return */ | ||
| import { userServices } from '../services/user.services.js'; | ||
| import { emailServices } from '../services/email.services.js'; | ||
| import { jwtService } from '../services/jwt.services.js'; | ||
|
aakash1sadhu marked this conversation as resolved.
Outdated
|
||
| import bcrypt, { compare } from 'bcrypt'; | ||
| import { v4 as uuidv4 } from 'uuid'; | ||
|
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,}$/; | ||
|
|
||
|
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; | ||
|
|
||
|
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, | ||
| }; | ||
| 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'; | ||
|
aakash1sadhu marked this conversation as resolved.
|
||
|
|
||
| const getAllUsers = async (req, res) => { | ||
| const users = await User.findAll(); | ||
|
|
||
| res.send(users); | ||
|
aakash1sadhu marked this conversation as resolved.
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); | ||
| } | ||
| }; | ||
|
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' }); | ||
|
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, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,23 @@ | ||
| 'use strict'; | ||
| import express from 'express'; | ||
|
aakash1sadhu marked this conversation as resolved.
|
||
| import authRouter from './router/auth.router.js'; | ||
| import userRouter from './router/user.router.js'; | ||
| import cookieParser from 'cookieParser'; | ||
|
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
|
||
| app.use(express.json()); | ||
| app.use(cookieParser()); | ||
|
|
||
| app.use('/', authRouter); | ||
|
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}`); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { jwtService } from '../services/jwt.service.js'; | ||
|
aakash1sadhu marked this conversation as resolved.
Outdated
aakash1sadhu marked this conversation as resolved.
Outdated
|
||
|
|
||
| export const authMiddleware = (req, res, next) => { | ||
|
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.