import bcrypt from 'bcryptjs' import UserRepository from '../repositories/userRepository' import PasswordResetTokenRepository from '../repositories/passwordResetTokenRepository' import prisma from '../utilities/prisma' import { User } from '@prisma/client' import config from '../utilities/config' import NodeMailer from 'nodemailer' import { httpLogger } from '../utilities/logger' import PasswordResetTokenService from './passwordResetTokenService' /** * User service * Handles user login and registration * @class UserService */ class UserService { /** * Login user * @param username * @param password */ async login(username: string, password: string): Promise { try { const user = await UserRepository.getByUsername(username) if (!user) { return false } const passwordMatch = await bcrypt.compare(password, user.password) if (!passwordMatch) { httpLogger.error(`Failed to login user: ${username}`) return false } return user } catch (error: any) { httpLogger.error(`Error logging in user: ${error instanceof Error ? error.message : String(error)}`) return false } } /** * Register user * @param username * @param email * @param password */ async register(username: string, email: string, password: string): Promise { try { const user = await UserRepository.getByUsername(username) if (user) { return false } const userByEmail = await UserRepository.getByEmail(email) if (userByEmail) { httpLogger.error(`User already exists: ${email}`) return false } const hashedPassword = await bcrypt.hash(password, 10) return prisma.user.create({ data: { username, email, password: hashedPassword } }) } catch (error: any) { httpLogger.error(`Error registering user: ${error instanceof Error ? error.message : String(error)}`) return false } } /** * Reset password * @param email */ async requestPasswordReset(email: string): Promise { try { const user = await UserRepository.getByEmail(email) if (!user) return false const token = await bcrypt.hash(new Date().getTime().toString(), 10) const latestToken = await PasswordResetTokenRepository.getByUserId(user.id) // Check if password reset has been requested recently if (latestToken) { const tokenExpiryDate = new Date(Date.now() - 24 * 60 * 60 * 1000) // 24 hours const isTokenExpired = latestToken.createdAt < tokenExpiryDate if (!isTokenExpired) return false await prisma.passwordResetToken.delete({ where: { id: latestToken.id } }) } await prisma.passwordResetToken.create({ data: { userId: user.id, token: token } }) const transporter = NodeMailer.createTransport({ host: config.SMTP_HOST, port: config.SMTP_PORT, secure: false, auth: { user: config.SMTP_USER, pass: config.SMTP_PASSWORD } }) await transporter.sendMail({ from: config.SMTP_USER, to: email, subject: 'Reset your password', text: 'A password reset has been requested, reset your password here: ' + config.CLIENT_URL + '#' + token, // Plain text body html: "

A password reset has been requested, reset your password here: " + config.CLIENT_URL + '#' + token + '

' // Html body }) return true } catch (error: any) { httpLogger.error(`Error sending password reset email: ${error instanceof Error ? error.message : String(error)}`) return false } } /** * Set new password * @param urlToken * @param password */ async resetPassword(urlToken: string, password: string): Promise { try { const tokenData = await PasswordResetTokenRepository.getByToken(urlToken) if (!tokenData) { return false } const hashedPassword = await bcrypt.hash(password, 10) await prisma.user.update({ where: { id: tokenData.userId }, data: { password: hashedPassword } }) // Delete the token const passwordResetTokenService = new PasswordResetTokenService() await passwordResetTokenService.delete(urlToken) return true } catch (error: any) { httpLogger.error(`Error setting new password: ${error instanceof Error ? error.message : String(error)}`) return false } } } export default UserService