Uecko_ERP/apps/server/src/contexts/auth/application/auth.service.ts
2025-02-06 12:55:47 +01:00

243 lines
7.1 KiB
TypeScript

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<Result<AuthenticatedUser, Error>> {
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<Result<void, Error>> {
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<Result<AuthenticatedUser, Error>> {
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);
}
}
}