243 lines
7.1 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
}
|