1
0
forked from noxious/server
noxious_server/src/services/userService.ts

135 lines
4.3 KiB
TypeScript

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<boolean | User> {
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<boolean | User> {
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<boolean> {
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: `<p>A password reset has been requested, reset your password here: <a href="${config.CLIENT_URL}#${token}">${config.CLIENT_URL}#${token}</a></p>`
})
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<boolean> {
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()