import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; import { v4 as uuidv4 } from 'uuid'; import { env } from '../config/env'; import userRepository from '../repositories/userRepository'; import licenseRepository from '../repositories/licenseRepository'; import { JwtPayload } from '../middlewares/auth'; import { BadRequestError, UnauthorizedError, ConflictError, validateEmail, validatePassword, validateRequired, } from '../utils/helpers'; export class AuthService { async register(email: string, password: string, name: string) { validateRequired({ email, password, name }); if (!validateEmail(email)) { throw new BadRequestError('Invalid email format'); } if (!validatePassword(password)) { throw new BadRequestError('Password must be at least 8 characters'); } const existingUser = await userRepository.findByEmail(email); if (existingUser) { throw new ConflictError('Email already registered'); } const hashedPassword = await bcrypt.hash(password, 12); const emailVerifyToken = uuidv4(); const user = await userRepository.create({ email, password: hashedPassword, name, emailVerifyToken, }); // Create default free license const licenseKey = `MS-FREE-${uuidv4().substring(0, 8).toUpperCase()}`; await licenseRepository.create({ key: licenseKey, plan: 'FREE', maxScan: 50, user: { connect: { id: user.id } }, }); const tokens = this.generateTokens({ userId: user.id, email: user.email, role: user.role }); await userRepository.update(user.id, { refreshToken: tokens.refreshToken }); return { user: { id: user.id, email: user.email, name: user.name, role: user.role, }, tokens, licenseKey, }; } async login(email: string, password: string) { validateRequired({ email, password }); const user = await userRepository.findByEmail(email); if (!user) { throw new UnauthorizedError('Invalid credentials'); } if (user.status === 'SUSPENDED') { throw new UnauthorizedError('Account suspended'); } const isPasswordValid = await bcrypt.compare(password, user.password); if (!isPasswordValid) { throw new UnauthorizedError('Invalid credentials'); } const tokens = this.generateTokens({ userId: user.id, email: user.email, role: user.role }); await userRepository.update(user.id, { refreshToken: tokens.refreshToken, lastLoginAt: new Date(), }); return { user: { id: user.id, email: user.email, name: user.name, role: user.role, }, tokens, }; } async refreshToken(refreshToken: string) { try { const decoded = jwt.verify(refreshToken, env.JWT_REFRESH_SECRET) as JwtPayload; const user = await userRepository.findById(decoded.userId); if (!user || user.refreshToken !== refreshToken) { throw new UnauthorizedError('Invalid refresh token'); } const tokens = this.generateTokens({ userId: user.id, email: user.email, role: user.role }); await userRepository.update(user.id, { refreshToken: tokens.refreshToken }); return tokens; } catch { throw new UnauthorizedError('Invalid refresh token'); } } async verifyEmail(token: string) { const user = await userRepository.findByEmailVerifyToken(token); if (!user) { throw new BadRequestError('Invalid verification token'); } await userRepository.update(user.id, { isEmailVerified: true, emailVerifyToken: null, }); return { message: 'Email verified successfully' }; } async logout(userId: string) { await userRepository.update(userId, { refreshToken: null }); return { message: 'Logged out successfully' }; } private generateTokens(payload: JwtPayload) { const accessToken = jwt.sign(payload, env.JWT_SECRET, { expiresIn: env.JWT_EXPIRES_IN, } as jwt.SignOptions); const refreshToken = jwt.sign(payload, env.JWT_REFRESH_SECRET, { expiresIn: env.JWT_REFRESH_EXPIRES_IN, } as jwt.SignOptions); return { accessToken, refreshToken }; } } export default new AuthService();