import { EmailAddress } from "@/core/common/domain"; import { Result } from "@repo/rdx-utils"; import { AuthenticatedUser, type IJWTPayload, type LoginData, type RegisterData, TabContext, Token, } from ".."; import { UniqueID } from "@/core/common/domain"; import { IAuthenticatedUserRepository, JWTPayload } from ".."; import { JwtHelper } from "../../infraestructure/passport/jwt.helper"; import { ITabContextRepository } from "../repositories/tab-context-repository.interface"; import { IAuthService } from "./auth-service.interface"; const ACCESS_EXPIRATION = process.env.JWT_ACCESS_EXPIRATION || "1h"; const REFRESH_EXPIRATION = process.env.JWT_REFRESH_EXPIRATION || "7d"; export class AuthService implements IAuthService { constructor( private readonly authUserRepo: IAuthenticatedUserRepository, private readonly tabContextRepo: ITabContextRepository ) {} generateAccessToken(payload: IJWTPayload): Result { const data = payload.toPersistenceData(); return Token.create(JwtHelper.generateToken(data, ACCESS_EXPIRATION)); } generateRefreshToken(payload: IJWTPayload): Result { const data = payload.toPersistenceData(); return Token.create(JwtHelper.generateToken(data, REFRESH_EXPIRATION)); } verifyRefreshToken(token: Token): IJWTPayload { return JwtHelper.verifyToken(token.toString()); } /** * * Registra un nuevo usuario en la base de datos bajo transacción. */ async registerUser( registerData: RegisterData, transaction?: any ): Promise> { const { username, email, hashPassword } = registerData; // Verificar si el usuario ya existe const userExists = await this.authUserRepo.userExists(username, 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.authUserRepo.createUser(userOrError.data, transaction); if (createdResult.isFailure) { return Result.fail(createdResult.error); } return Result.ok(userOrError.data); } /** * * Autentica a un usuario validando su email y contraseña. */ async loginUser( loginData: LoginData, transaction?: any ): Promise< Result< { user: AuthenticatedUser; tabContext: TabContext; tokens: { accessToken: Token; refreshToken: Token; }; }, Error > > { let result: any; const { email, plainPassword, tabId } = loginData; // 🔹 Verificar si el usuario existe en la base de datos result = await this.authUserRepo.getUserByEmail(email, transaction); if (result.isFailure) { return Result.fail(new Error("Invalid email or password")); } const user = result.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, }); // 🔹 Generar Access Token y Refresh Token const payloadOrError = JWTPayload.create({ userId: user.id, email: email, tabId: tabId, //roles: ["USER"], }); result = Result.combine([contextOrError, payloadOrError]); if (result.isFailure) { return Result.fail(new Error("Error on login")); } const tabContext = contextOrError.data; await this.tabContextRepo.registerContextByTabId(tabContext, transaction); const accessTokenOrError = this.generateAccessToken(payloadOrError.data); const refreshTokenOrError = this.generateRefreshToken(payloadOrError.data); result = Result.combine([accessTokenOrError, refreshTokenOrError]); if (result.isFailure) { return Result.fail(result.error); } return Result.ok({ user, tabContext, tokens: { accessToken: accessTokenOrError.data, refreshToken: refreshTokenOrError.data, }, }); } /** * * Autentica a un usuario validando su email y contraseña. */ async logoutUser( params: { email: EmailAddress; tabId: UniqueID }, transaction?: any ): Promise> { const { email, tabId } = params; // 🔹 Verificar si el usuario existe en la base de datos const userResult = await this.authUserRepo.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.tabContextRepo.unregisterContextByTabId(contextOrError.data, transaction); return Result.ok(); } async getUserByEmail( email: EmailAddress, transaction?: any ): Promise> { const userResult = await this.authUserRepo.getUserByEmail(email, transaction); if (userResult.isFailure || !userResult.data) { return Result.fail(new Error("Invalid email or password")); } return Result.ok(userResult.data); } }