import { Result, UniqueID } from "@common/domain"; import { ITransactionManager } from "@common/infrastructure/database"; import jwt from "jsonwebtoken"; import { AuthenticatedUser, EmailAddress, HashPassword, IAuthenticatedUserRepository, PlainPassword, TabContext, Username, } from "../domain"; import { ITabContextRepository } from "../domain/repositories/tab-context-repository.interface"; import { IAuthService } from "./auth-service.interface"; const SECRET_KEY = process.env.JWT_SECRET || "supersecretkey"; const ACCESS_EXPIRATION = process.env.JWT_ACCESS_EXPIRATION || "1h"; const REFRESH_EXPIRATION = process.env.JWT_REFRESH_EXPIRATION || "7d"; export class AuthService implements IAuthService { private readonly _userRepo!: IAuthenticatedUserRepository; private readonly _tabContactRepo!: ITabContextRepository; private readonly _transactionManager!: ITransactionManager; constructor( userRepo: IAuthenticatedUserRepository, tabContextRepo: ITabContextRepository, transactionManager: ITransactionManager ) { this._userRepo = userRepo; this._tabContactRepo = tabContextRepo; this._transactionManager = transactionManager; } generateAccessToken(payload: object): string { return jwt.sign(payload, SECRET_KEY, { expiresIn: ACCESS_EXPIRATION }); } generateRefreshToken(payload: object): string { return jwt.sign(payload, SECRET_KEY, { expiresIn: REFRESH_EXPIRATION }); } verifyToken(token: string): any { return jwt.verify(token, SECRET_KEY); } /** * * Registra un nuevo usuario en la base de datos bajo transacción. */ async registerUser(params: { username: Username; email: EmailAddress; hashPassword: HashPassword; }): Promise> { try { return await this._transactionManager.complete(async (transaction) => { const { username, email, hashPassword } = params; // Verificar si el usuario ya existe const userExists = await this._userRepo.userExists(email, transaction); if (userExists.isSuccess && userExists.data) { return Result.fail(new Error("Email is already registered")); } const newUserId = UniqueID.generateNewID().data; const userOrError = AuthenticatedUser.create( { username, email, hashPassword, roles: ["USER"], }, newUserId ); if (userOrError.isFailure) { return Result.fail(userOrError.error); } const createdResult = await this._userRepo.createUser(userOrError.data, transaction); if (createdResult.isFailure) { return Result.fail(createdResult.error); } return Result.ok(userOrError.data); }); } catch (error: unknown) { return Result.fail(error as Error); } } /** * * Autentica a un usuario validando su email y contraseña. */ async loginUser(params: { email: EmailAddress; plainPassword: HashPassword; tabId: UniqueID; }): Promise< Result< { user: AuthenticatedUser; tokens: { accessToken: string; refreshToken: string; }; }, Error > > { try { return await this._transactionManager.complete(async (transaction) => { const { email, plainPassword, tabId } = params; // Verificar que el tab ID está definido if (!tabId.isDefined()) { return Result.fail(new Error("Invalid tab id")); } // 🔹 Verificar si el usuario existe en la base de datos const userResult = await this._userRepo.getUserByEmail(email, transaction); if (userResult.isFailure) { return Result.fail(new Error("Invalid email or password")); } const user = userResult.data; // 🔹 Verificar que la contraseña sea correcta const isValidPassword = await user.verifyPassword(plainPassword); if (!isValidPassword) { return Result.fail(new Error("Invalid email or password")); } // Registrar o actualizar el contexto de ese tab ID const contextOrError = TabContext.create({ userId: user.id, tabId: tabId, }); if (contextOrError.isFailure) { return Result.fail(new Error("Error creating user context")); } await this._tabContactRepo.registerContextByTabId(contextOrError.data, transaction); // 🔹 Generar Access Token y Refresh Token const accessToken = this.generateAccessToken({ userId: user.id.toString(), email: email.toString(), tabId: tabId.toString(), roles: ["USER"], }); const refreshToken = this.generateRefreshToken({ userId: user.id.toString(), }); return Result.ok({ user, tokens: { accessToken, refreshToken, }, }); }); } catch (error: unknown) { return Result.fail(error as Error); } } /** * * Autentica a un usuario validando su email y contraseña. */ async logoutUser(params: { email: EmailAddress; tabId: UniqueID }): Promise> { try { return await this._transactionManager.complete(async (transaction) => { const { email, tabId } = params; // Verificar que el tab ID está definido if (!tabId.isDefined()) { return Result.fail(new Error("Invalid tab id")); } // 🔹 Verificar si el usuario existe en la base de datos const userResult = await this._userRepo.getUserByEmail(email, transaction); if (userResult.isFailure) { return Result.fail(new Error("Invalid email or password")); } const user = userResult.data; const contextOrError = TabContext.create({ userId: user.id, tabId: tabId, }); if (contextOrError.isFailure) { return Result.fail(new Error("Error creating user context")); } // Desregistrar el contexto de ese tab ID await this._tabContactRepo.unregisterContextByTabId(contextOrError.data, transaction); return Result.ok(); }); } catch (error: unknown) { return Result.fail(error as Error); } } async verifyUser(params: { email: EmailAddress; plainPassword: PlainPassword; }): Promise> { try { return await this._transactionManager.complete(async (transaction) => { const { email, plainPassword } = params; const userResult = await this._userRepo.getUserByEmail(email, transaction); if (userResult.isFailure || !userResult.data) { return Result.fail(new Error("Invalid email or password")); } const user = userResult.data; const isValidPassword = await user.verifyPassword(plainPassword); if (!isValidPassword) { return Result.fail(new Error("Invalid email or password")); } return Result.ok(user); }); } catch (error: unknown) { return Result.fail(error as Error); } } }