2025-05-02 21:43:51 +00:00
|
|
|
import { EmailAddress } from "@/core/common/domain";
|
2025-05-09 10:45:32 +00:00
|
|
|
import { Result } from "@repo/rdx-utils";
|
2025-05-04 20:06:57 +00:00
|
|
|
import {
|
|
|
|
|
AuthenticatedUser,
|
|
|
|
|
type IJWTPayload,
|
|
|
|
|
type LoginData,
|
|
|
|
|
type RegisterData,
|
|
|
|
|
TabContext,
|
|
|
|
|
Token,
|
|
|
|
|
} from "..";
|
2025-02-21 10:06:27 +00:00
|
|
|
|
2025-05-02 21:43:51 +00:00
|
|
|
import { UniqueID } from "@/core/common/domain";
|
2025-02-21 10:06:27 +00:00
|
|
|
import { IAuthenticatedUserRepository, JWTPayload } from "..";
|
2025-02-15 21:30:12 +00:00
|
|
|
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<Token, Error> {
|
2025-02-16 19:30:20 +00:00
|
|
|
const data = payload.toPersistenceData();
|
|
|
|
|
return Token.create(JwtHelper.generateToken(data, ACCESS_EXPIRATION));
|
2025-02-15 21:30:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
generateRefreshToken(payload: IJWTPayload): Result<Token, Error> {
|
2025-02-16 19:30:20 +00:00
|
|
|
const data = payload.toPersistenceData();
|
|
|
|
|
return Token.create(JwtHelper.generateToken(data, REFRESH_EXPIRATION));
|
2025-02-15 21:30:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<Result<AuthenticatedUser, Error>> {
|
|
|
|
|
const { username, email, hashPassword } = registerData;
|
|
|
|
|
|
|
|
|
|
// Verificar si el usuario ya existe
|
2025-02-16 19:30:20 +00:00
|
|
|
const userExists = await this.authUserRepo.userExists(username, email, transaction);
|
2025-02-15 21:30:12 +00:00
|
|
|
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
|
|
|
|
|
>
|
|
|
|
|
> {
|
2025-02-16 19:30:20 +00:00
|
|
|
let result: any;
|
2025-02-15 21:30:12 +00:00
|
|
|
const { email, plainPassword, tabId } = loginData;
|
|
|
|
|
|
|
|
|
|
// 🔹 Verificar si el usuario existe en la base de datos
|
2025-02-16 19:30:20 +00:00
|
|
|
result = await this.authUserRepo.getUserByEmail(email, transaction);
|
|
|
|
|
if (result.isFailure) {
|
2025-02-15 21:30:12 +00:00
|
|
|
return Result.fail(new Error("Invalid email or password"));
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-16 19:30:20 +00:00
|
|
|
const user = result.data;
|
2025-02-15 21:30:12 +00:00
|
|
|
|
|
|
|
|
// 🔹 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,
|
|
|
|
|
});
|
|
|
|
|
|
2025-02-16 19:30:20 +00:00
|
|
|
// 🔹 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"));
|
2025-02-15 21:30:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const tabContext = contextOrError.data;
|
|
|
|
|
await this.tabContextRepo.registerContextByTabId(tabContext, transaction);
|
|
|
|
|
|
2025-02-16 19:30:20 +00:00
|
|
|
const accessTokenOrError = this.generateAccessToken(payloadOrError.data);
|
|
|
|
|
const refreshTokenOrError = this.generateRefreshToken(payloadOrError.data);
|
2025-02-15 21:30:12 +00:00
|
|
|
|
2025-02-16 19:30:20 +00:00
|
|
|
result = Result.combine([accessTokenOrError, refreshTokenOrError]);
|
2025-02-15 21:30:12 +00:00
|
|
|
|
|
|
|
|
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<Result<void, Error>> {
|
|
|
|
|
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<Result<AuthenticatedUser, Error>> {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|