import bcrypt from 'bcryptjs' import NodeMailer from 'nodemailer' import { BaseService } from '#application/base/baseService' import config from '#application/config' import { PasswordResetToken } from '#entities/passwordResetToken' import { User } from '#entities/user' import PasswordResetTokenRepository from '#repositories/passwordResetTokenRepository' import UserRepository from '#repositories/userRepository' /** * User service * Handles user login and registration * @class UserService */ class UserService extends BaseService { protected readonly userRepository = new UserRepository() protected readonly passwordResetTokenRepository = new PasswordResetTokenRepository() async login(username: string, password: string): Promise { try { const user = await this.userRepository.getByUsername(username) if (!user) { return false } const passwordMatch = await bcrypt.compare(password, user.password) if (!passwordMatch) { this.logger.error(`Failed to login user: ${username}`) return false } return user } catch (error: any) { this.logger.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([this.userRepository.getByUsername(username), this.userRepository.getByEmail(email)]) if (userByName || userByEmail) { this.logger.error(`User already exists: ${userByEmail ? email : username}`) return false } const newUser = new User() await newUser.setUsername(username).setEmail(email).setPassword(password).save() return newUser } catch (error: any) { this.logger.error(`Error registering user: ${error instanceof Error ? error.message : String(error)}`) return false } } async requestPasswordReset(email: string): Promise { try { const user = await this.userRepository.getByEmail(email) if (!user) return false const token = await bcrypt.hash(new Date().getTime().toString(), 10) const latestToken = await this.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() await passwordResetToken.setUser(user).setToken(token).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) { this.logger.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 this.passwordResetTokenRepository.getByToken(urlToken) if (!tokenData) { return false } const user = await this.userRepository.getById(tokenData.userId) if (!user) { return false } user.setPassword(password) await user.save() // Delete the token using MikroORM entity method await tokenData.delete() return true } catch (error: any) { this.logger.error(`Error setting new password: ${error instanceof Error ? error.message : String(error)}`) return false } } } export default new UserService()