Refresh token
This commit is contained in:
parent
eef6e6fa97
commit
4e39aacedf
@ -12,6 +12,7 @@ import { IJWTPayload } from "../infraestructure";
|
|||||||
export interface IAuthService {
|
export interface IAuthService {
|
||||||
generateAccessToken(payload: IJWTPayload): string;
|
generateAccessToken(payload: IJWTPayload): string;
|
||||||
generateRefreshToken(payload: IJWTPayload): string;
|
generateRefreshToken(payload: IJWTPayload): string;
|
||||||
|
verifyRefreshToken(token: string): IJWTPayload;
|
||||||
|
|
||||||
registerUser(params: {
|
registerUser(params: {
|
||||||
username: Username;
|
username: Username;
|
||||||
|
|||||||
@ -39,6 +39,10 @@ export class AuthService implements IAuthService {
|
|||||||
return JwtHelper.generateToken(payload, REFRESH_EXPIRATION);
|
return JwtHelper.generateToken(payload, REFRESH_EXPIRATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verifyRefreshToken(token: string): IJWTPayload {
|
||||||
|
return JwtHelper.verifyToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Registra un nuevo usuario en la base de datos bajo transacción.
|
* Registra un nuevo usuario en la base de datos bajo transacción.
|
||||||
@ -147,16 +151,16 @@ export class AuthService implements IAuthService {
|
|||||||
|
|
||||||
// 🔹 Generar Access Token y Refresh Token
|
// 🔹 Generar Access Token y Refresh Token
|
||||||
const accessToken = this.generateAccessToken({
|
const accessToken = this.generateAccessToken({
|
||||||
userId: user.id.toString(),
|
user_id: user.id.toString(),
|
||||||
email: email.toString(),
|
email: email.toString(),
|
||||||
tabId: tabId.toString(),
|
tab_id: tabId.toString(),
|
||||||
roles: ["USER"],
|
roles: ["USER"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const refreshToken = this.generateRefreshToken({
|
const refreshToken = this.generateRefreshToken({
|
||||||
userId: user.id.toString(),
|
user_id: user.id.toString(),
|
||||||
email: email.toString(),
|
email: email.toString(),
|
||||||
tabId: tabId.toString(),
|
tab_id: tabId.toString(),
|
||||||
roles: ["USER"],
|
roles: ["USER"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import { Result, ValueObject } from "@common/domain";
|
import { Result, ValueObject } from "@common/domain";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const NULLED_EMAIL_ADDRESS = null;
|
||||||
|
|
||||||
export class EmailAddress extends ValueObject<string | null> {
|
export class EmailAddress extends ValueObject<string | null> {
|
||||||
static create(email: string | null): Result<EmailAddress, Error> {
|
static create(email: string | null): Result<EmailAddress, Error> {
|
||||||
const normalizedEmail = email?.trim() === "" ? null : email?.toLowerCase() || null;
|
const normalizedEmail =
|
||||||
|
email?.trim() === "" ? NULLED_EMAIL_ADDRESS : email?.toLowerCase() || NULLED_EMAIL_ADDRESS;
|
||||||
|
|
||||||
const result = EmailAddress.validate(normalizedEmail);
|
const result = EmailAddress.validate(normalizedEmail);
|
||||||
|
|
||||||
@ -16,4 +19,8 @@ export class EmailAddress extends ValueObject<string | null> {
|
|||||||
const schema = z.string().email({ message: "Invalid email format" }).or(z.null());
|
const schema = z.string().email({ message: "Invalid email format" }).or(z.null());
|
||||||
return schema.safeParse(email);
|
return schema.safeParse(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isDefined(): boolean {
|
||||||
|
return !this.isEmpty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
const SECRET_KEY = process.env.JWT_SECRET || "supersecretkey";
|
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 JwtHelper {
|
export class JwtHelper {
|
||||||
static generateToken(payload: object, expiresIn = "1h"): string {
|
static generateToken(payload: object, expiresIn = "1h"): string {
|
||||||
|
|||||||
@ -9,9 +9,9 @@ import { Strategy as LocalStrategy } from "passport-local";
|
|||||||
const SECRET_KEY = process.env.JWT_SECRET || "supersecretkey";
|
const SECRET_KEY = process.env.JWT_SECRET || "supersecretkey";
|
||||||
|
|
||||||
export interface IJWTPayload {
|
export interface IJWTPayload {
|
||||||
userId: string;
|
user_id: string;
|
||||||
email: string;
|
email: string;
|
||||||
tabId: string;
|
tab_id: string;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,2 +1,4 @@
|
|||||||
export * from "./login";
|
export * from "./login";
|
||||||
|
export * from "./logout";
|
||||||
|
export * from "./refreshToken";
|
||||||
export * from "./register";
|
export * from "./register";
|
||||||
|
|||||||
@ -26,6 +26,10 @@ class LoginController extends ExpressController {
|
|||||||
return this.clientError("Invalid input data", resultValidation.error);
|
return this.clientError("Invalid input data", resultValidation.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (emailVO.data.isEmpty()) {
|
||||||
|
return this.clientError("Invalid input data");
|
||||||
|
}
|
||||||
|
|
||||||
const loginResultOrError = await this._authService.loginUser({
|
const loginResultOrError = await this._authService.loginUser({
|
||||||
email: emailVO.data,
|
email: emailVO.data,
|
||||||
plainPassword: plainPasswordVO.data,
|
plainPassword: plainPasswordVO.data,
|
||||||
|
|||||||
@ -23,6 +23,10 @@ class LogoutController extends ExpressController {
|
|||||||
return this.clientError("Invalid input data", resultValidation.error);
|
return this.clientError("Invalid input data", resultValidation.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (emailVO.data.isEmpty()) {
|
||||||
|
return this.clientError("Invalid input data");
|
||||||
|
}
|
||||||
|
|
||||||
await this._authService.logoutUser({
|
await this._authService.logoutUser({
|
||||||
email: emailVO.data,
|
email: emailVO.data,
|
||||||
tabId: tabIdVO.data,
|
tabId: tabIdVO.data,
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./refresh-token.controller";
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
import { ExpressController } from "@common/presentation";
|
||||||
|
import { createAuthService, IAuthService } from "@contexts/auth/application";
|
||||||
|
import { IRefreshTokenPresenter, RefreshTokenPresenter } from "./refresh-token.presenter";
|
||||||
|
|
||||||
|
class RefreshTokenController extends ExpressController {
|
||||||
|
private readonly _authService!: IAuthService;
|
||||||
|
private readonly _presenter!: IRefreshTokenPresenter;
|
||||||
|
|
||||||
|
public constructor(authService: IAuthService, presenter: IRefreshTokenPresenter) {
|
||||||
|
super();
|
||||||
|
this._authService = authService;
|
||||||
|
this._presenter = presenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeImpl() {
|
||||||
|
const tabId = String(this.req.headers["x-tab-id"]);
|
||||||
|
const refreshToken = String(this.req.body.refresh_token);
|
||||||
|
|
||||||
|
const result = this._authService.verifyRefreshToken(refreshToken);
|
||||||
|
if (!result || !result.email || !result.user_id || !result.tab_id || !result.roles) {
|
||||||
|
return this.clientError("Invalid input data");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user_id, tab_id, email, roles } = result;
|
||||||
|
|
||||||
|
const newRefreshToken = this._authService.generateRefreshToken({
|
||||||
|
user_id,
|
||||||
|
tab_id,
|
||||||
|
email,
|
||||||
|
roles,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.created(this._presenter.map({ refreshToken: newRefreshToken }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createRefreshTokenController = () => {
|
||||||
|
const authService = createAuthService();
|
||||||
|
return new RefreshTokenController(authService, RefreshTokenPresenter);
|
||||||
|
};
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { IRefreshTokenResponseDTO } from "../../dto";
|
||||||
|
|
||||||
|
export interface IRefreshTokenPresenter {
|
||||||
|
map: (data: { refreshToken: string }) => IRefreshTokenResponseDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RefreshTokenPresenter: IRefreshTokenPresenter = {
|
||||||
|
map: (data: { refreshToken: string }): IRefreshTokenResponseDTO => {
|
||||||
|
const { refreshToken } = data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
refresh_token: refreshToken,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -22,6 +22,10 @@ class RegisterController extends ExpressController {
|
|||||||
return this.clientError("Invalid input data");
|
return this.clientError("Invalid input data");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (emailVO.data.isEmpty()) {
|
||||||
|
return this.clientError("Invalid input data");
|
||||||
|
}
|
||||||
|
|
||||||
const userOrError = await this._authService.registerUser({
|
const userOrError = await this._authService.registerUser({
|
||||||
username: usernameVO.data,
|
username: usernameVO.data,
|
||||||
email: emailVO.data,
|
email: emailVO.data,
|
||||||
|
|||||||
@ -15,7 +15,10 @@ export interface ILoginUserResponseDTO {
|
|||||||
access_token: string;
|
access_token: string;
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
};
|
};
|
||||||
//tab_id: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILogoutResponseDTO {}
|
export interface ILogoutResponseDTO {}
|
||||||
|
|
||||||
|
export interface IRefreshTokenResponseDTO {
|
||||||
|
refresh_token: string;
|
||||||
|
}
|
||||||
|
|||||||
@ -11,6 +11,6 @@ export const LoginUserSchema = z.object({
|
|||||||
password: z.string().min(6, "Password must be at least 6 characters long"),
|
password: z.string().min(6, "Password must be at least 6 characters long"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SelectCompanySchema = z.object({
|
export const RefreshTokenSchema = z.object({
|
||||||
companyId: z.string().min(1, "Company ID is required"),
|
refresh_token: z.string().min(1, "Refresh token is required"),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
export * from "../../../routes/auth.routes";
|
|
||||||
export * from "./controllers";
|
export * from "./controllers";
|
||||||
export * from "./dto";
|
export * from "./dto";
|
||||||
export * from "./middleware";
|
export * from "./middleware";
|
||||||
|
|||||||
5
apps/server/src/contexts/companies/domain/index.ts
Normal file
5
apps/server/src/contexts/companies/domain/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./aggregates";
|
||||||
|
export * from "./entities";
|
||||||
|
export * from "./events";
|
||||||
|
export * from "./repositories";
|
||||||
|
export * from "./value-objects";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export interface ICompanyRepository {}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./company-repository.interface";
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./mappers";
|
||||||
|
export * from "./passport";
|
||||||
|
export * from "./sequelize";
|
||||||
@ -1,10 +1,17 @@
|
|||||||
import { validateRequestDTO } from "@common/presentation";
|
import { validateRequestDTO } from "@common/presentation";
|
||||||
import { createAuthProvider } from "@contexts/auth/infraestructure";
|
import { createAuthProvider } from "@contexts/auth/infraestructure";
|
||||||
import { validateTabContextHeader } from "@contexts/auth/presentation";
|
import { validateTabContextHeader } from "@contexts/auth/presentation";
|
||||||
import { createLoginController } from "@contexts/auth/presentation/controllers";
|
import {
|
||||||
|
createLoginController,
|
||||||
|
createRefreshTokenController,
|
||||||
|
} from "@contexts/auth/presentation/controllers";
|
||||||
import { createLogoutController } from "@contexts/auth/presentation/controllers/logout/logout.controller";
|
import { createLogoutController } from "@contexts/auth/presentation/controllers/logout/logout.controller";
|
||||||
import { createRegisterController } from "@contexts/auth/presentation/controllers/register/register.controller";
|
import { createRegisterController } from "@contexts/auth/presentation/controllers/register/register.controller";
|
||||||
import { LoginUserSchema, RegisterUserSchema } from "@contexts/auth/presentation/dto";
|
import {
|
||||||
|
LoginUserSchema,
|
||||||
|
RefreshTokenSchema,
|
||||||
|
RegisterUserSchema,
|
||||||
|
} from "@contexts/auth/presentation/dto";
|
||||||
import { NextFunction, Request, Response, Router } from "express";
|
import { NextFunction, Request, Response, Router } from "express";
|
||||||
|
|
||||||
export const authRouter = (appRouter: Router) => {
|
export const authRouter = (appRouter: Router) => {
|
||||||
@ -73,5 +80,15 @@ export const authRouter = (appRouter: Router) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authRoutes.post(
|
||||||
|
"/refresh",
|
||||||
|
validateRequestDTO(RefreshTokenSchema),
|
||||||
|
//validateTabContextHeader,
|
||||||
|
//authProvider.authenticateJWT(),
|
||||||
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
createRefreshTokenController().execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
appRouter.use("/auth", authRoutes);
|
appRouter.use("/auth", authRoutes);
|
||||||
};
|
};
|
||||||
|
|||||||
49
apps/server/src/routes/company.routes.ts
Normal file
49
apps/server/src/routes/company.routes.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { validateRequestDTO } from "@common/presentation";
|
||||||
|
import { createAuthProvider } from "@contexts/company/infraestructure";
|
||||||
|
import { validateTabContextHeader } from "@contexts/company/presentation";
|
||||||
|
import { createLoginController } from "@contexts/company/presentation/controllers";
|
||||||
|
import { createLogoutController } from "@contexts/company/presentation/controllers/logout/logout.controller";
|
||||||
|
import { createRegisterController } from "@contexts/company/presentation/controllers/register/register.controller";
|
||||||
|
import { LoginUserSchema, RegisterUserSchema } from "@contexts/company/presentation/dto";
|
||||||
|
import { NextFunction, Request, Response, Router } from "express";
|
||||||
|
|
||||||
|
export const companyRouter = (appRouter: Router) => {
|
||||||
|
const companyRoutes: Router = Router({ mergeParams: true });
|
||||||
|
const authProvider = createAuthProvider();
|
||||||
|
|
||||||
|
companyRoutes.get(
|
||||||
|
"/",
|
||||||
|
/*validateRequestDTO(ListCompaniesSchema),*/
|
||||||
|
validateTabContextHeader,
|
||||||
|
authProvider.companyenticateJWT(),
|
||||||
|
|
||||||
|
getDealerMiddleware,
|
||||||
|
handleRequest(listQuotesController)
|
||||||
|
);
|
||||||
|
companyRoutes.get("/:quoteId", checkUser, getDealerMiddleware, handleRequest(getQuoteController));
|
||||||
|
companyRoutes.post("/", checkUser, getDealerMiddleware, handleRequest(createQuoteController));
|
||||||
|
|
||||||
|
companyRoutes.post("/register", validateRequestDTO(RegisterUserSchema), (req, res, next) => {
|
||||||
|
createRegisterController().execute(req, res, next);
|
||||||
|
});
|
||||||
|
|
||||||
|
companyRoutes.post(
|
||||||
|
"/login",
|
||||||
|
validateRequestDTO(LoginUserSchema),
|
||||||
|
validateTabContextHeader,
|
||||||
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
createLoginController().execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
companyRoutes.post(
|
||||||
|
"/logout",
|
||||||
|
validateTabContextHeader,
|
||||||
|
authProvider.companyenticateJWT(),
|
||||||
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
createLogoutController().execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
appRouter.use("/company", companyRoutes);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user