import bcrypt from 'bcryptjs' import UserRepository from '#repositories/userRepository' import PasswordResetTokenRepository from '#repositories/passwordResetTokenRepository' import config from '#application/config' import NodeMailer from 'nodemailer' import { httpLogger } from '#application/logger' import PasswordResetTokenService from './passwordResetTokenService' // @TODO: Correctly implement this import { User } from '#entities/user' import { Database } from '#application/database' import { PasswordResetToken } from '#entities/passwordResetToken' /** * User service * Handles user login and registration * @class UserService */ class UserService { 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 } } async register(username: string, email: string, password: string): Promise { try { // Check existing users const [userByName, userByEmail] = await Promise.all([UserRepository.getByUsername(username), UserRepository.getByEmail(email)]) if (userByName || userByEmail) { httpLogger.error(`User already exists: ${userByEmail ? email : username}`) return false } const hashedPassword = await bcrypt.hash(password, 10) const newUser = new User() newUser.setUsername(username).setEmail(email).setPassword(hashedPassword) await newUser.save() return newUser } catch (error: any) { httpLogger.error(`Error registering user: ${error instanceof Error ? error.message : String(error)}`) return false } } 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) const isTokenExpired = latestToken.createdAt < tokenExpiryDate if (!isTokenExpired) return false // Delete existing token using MikroORM await latestToken.delete() } // Create new token using MikroORM const passwordResetToken = new PasswordResetToken() passwordResetToken.setUser(user).setToken(token) await passwordResetToken.save() 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}`, html: `

A password reset has been requested, reset your password here: ${config.CLIENT_URL}#${token}

` }) return true } catch (error: any) { httpLogger.error(`Error sending password reset email: ${error instanceof Error ? error.message : String(error)}`) return false } } 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) // Update user password using MikroORM const orm = await Database.getInstance() const em = orm.em.fork() const user = await em.findOne(User, { id: tokenData.userId }) if (!user) return false user.password = hashedPassword await em.persistAndFlush(user) // Delete the token await em.removeAndFlush(tokenData) return true } catch (error: any) { httpLogger.error(`Error setting new password: ${error instanceof Error ? error.message : String(error)}`) return false } } } export default UserService