| const crypto = require('crypto'); |
| const bcrypt = require('bcryptjs'); |
| const User = require('../../models/User'); |
| const Session = require('../../models/Session'); |
| const Token = require('../../models/schema/tokenSchema'); |
| const { registerSchema, errorsToString } = require('../../strategies/validators'); |
| const config = require('../../../config/loader'); |
| const { sendEmail } = require('../utils'); |
| const domains = config.domains; |
| const isProduction = config.isProduction; |
|
|
| |
| |
| |
| |
| |
| |
| |
| const logoutUser = async (userId, refreshToken) => { |
| try { |
| const hash = crypto.createHash('sha256').update(refreshToken).digest('hex'); |
|
|
| |
| const session = await Session.findOne({ user: userId, refreshTokenHash: hash }); |
| if (session) { |
| try { |
| await Session.deleteOne({ _id: session._id }); |
| } catch (deleteErr) { |
| console.error(deleteErr); |
| return { status: 500, message: 'Failed to delete session.' }; |
| } |
| } |
|
|
| return { status: 200, message: 'Logout successful' }; |
| } catch (err) { |
| return { status: 500, message: err.message }; |
| } |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| const registerUser = async (user) => { |
| const { error } = registerSchema.safeParse(user); |
| if (error) { |
| const errorMessage = errorsToString(error.errors); |
| console.info( |
| 'Route: register - Validation Error', |
| { name: 'Request params:', value: user }, |
| { name: 'Validation error:', value: errorMessage }, |
| ); |
|
|
| return { status: 422, message: errorMessage }; |
| } |
|
|
| const { email, password, name, username } = user; |
|
|
| try { |
| const existingUser = await User.findOne({ email }).lean(); |
|
|
| if (existingUser) { |
| console.info( |
| 'Register User - Email in use', |
| { name: 'Request params:', value: user }, |
| { name: 'Existing user:', value: existingUser }, |
| ); |
|
|
| |
| await new Promise((resolve) => setTimeout(resolve, 1000)); |
|
|
| |
| return { status: 500, message: 'Something went wrong' }; |
| } |
|
|
| |
| const isFirstRegisteredUser = (await User.countDocuments({})) === 0; |
|
|
| const newUser = await new User({ |
| provider: 'local', |
| email, |
| password, |
| username, |
| name, |
| avatar: null, |
| role: isFirstRegisteredUser ? 'ADMIN' : 'USER', |
| }); |
|
|
| const salt = bcrypt.genSaltSync(10); |
| const hash = bcrypt.hashSync(newUser.password, salt); |
| newUser.password = hash; |
| newUser.save(); |
|
|
| return { status: 200, user: newUser }; |
| } catch (err) { |
| return { status: 500, message: err?.message || 'Something went wrong' }; |
| } |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| const requestPasswordReset = async (email) => { |
| const user = await User.findOne({ email }).lean(); |
| if (!user) { |
| return new Error('Email does not exist'); |
| } |
|
|
| let token = await Token.findOne({ userId: user._id }); |
| if (token) { |
| await token.deleteOne(); |
| } |
|
|
| let resetToken = crypto.randomBytes(32).toString('hex'); |
| const hash = await bcrypt.hashSync(resetToken, 10); |
|
|
| await new Token({ |
| userId: user._id, |
| token: hash, |
| createdAt: Date.now(), |
| }).save(); |
|
|
| const link = `${domains.client}/reset-password?token=${resetToken}&userId=${user._id}`; |
|
|
| const emailEnabled = |
| !!process.env.EMAIL_SERVICE && |
| !!process.env.EMAIL_USERNAME && |
| !!process.env.EMAIL_PASSWORD && |
| !!process.env.EMAIL_FROM; |
|
|
| if (emailEnabled) { |
| sendEmail( |
| user.email, |
| 'Password Reset Request', |
| { |
| name: user.name, |
| link: link, |
| }, |
| 'requestPasswordReset.handlebars', |
| ); |
| return { link: '' }; |
| } else { |
| return { link }; |
| } |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| const resetPassword = async (userId, token, password) => { |
| let passwordResetToken = await Token.findOne({ userId }); |
|
|
| if (!passwordResetToken) { |
| return new Error('Invalid or expired password reset token'); |
| } |
|
|
| const isValid = bcrypt.compareSync(token, passwordResetToken.token); |
|
|
| if (!isValid) { |
| return new Error('Invalid or expired password reset token'); |
| } |
|
|
| const hash = bcrypt.hashSync(password, 10); |
|
|
| await User.updateOne({ _id: userId }, { $set: { password: hash } }, { new: true }); |
|
|
| const user = await User.findById({ _id: userId }); |
|
|
| sendEmail( |
| user.email, |
| 'Password Reset Successfully', |
| { |
| name: user.name, |
| }, |
| 'resetPassword.handlebars', |
| ); |
|
|
| await passwordResetToken.deleteOne(); |
|
|
| return { message: 'Password reset was successful' }; |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| const setAuthTokens = async (userId, res, sessionId = null) => { |
| try { |
| const user = await User.findOne({ _id: userId }); |
| const token = await user.generateToken(); |
|
|
| let session; |
| let refreshTokenExpires; |
| if (sessionId) { |
| session = await Session.findById(sessionId); |
| refreshTokenExpires = session.expiration.getTime(); |
| } else { |
| session = new Session({ user: userId }); |
| const { REFRESH_TOKEN_EXPIRY } = process.env ?? {}; |
| const expires = eval(REFRESH_TOKEN_EXPIRY) ?? 1000 * 60 * 60 * 24 * 7; |
| refreshTokenExpires = Date.now() + expires; |
| } |
|
|
| const refreshToken = await session.generateRefreshToken(); |
|
|
| res.cookie('refreshToken', refreshToken, { |
| expires: new Date(refreshTokenExpires), |
| httpOnly: true, |
| secure: isProduction, |
| sameSite: 'strict', |
| }); |
|
|
| return token; |
| } catch (error) { |
| console.log('Error in setting authentication tokens:', error); |
| throw error; |
| } |
| }; |
|
|
| module.exports = { |
| registerUser, |
| logoutUser, |
| requestPasswordReset, |
| resetPassword, |
| setAuthTokens, |
| }; |
|
|