Skip to content

Commit

Permalink
feat: handle error protect routing
Browse files Browse the repository at this point in the history
  • Loading branch information
lcaohoanq committed Jul 5, 2024
1 parent 0b663c6 commit 6e8acfe
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 25 deletions.
13 changes: 12 additions & 1 deletion src/errors/errors.entityError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,30 @@ import { USER_MESSAGES } from "~/modules/user/user.messages";
type ErrorsType = Record<string, { msg: string; [key: string]: string }>;

interface IErrorWithStatus {
from?: string; // Make sure the from field is optional
message: string;
status: number;
}

export class ErrorWithStatus implements IErrorWithStatus {
from?: string; // Include the from field here
message: string;
status: number;
constructor({ message, status }: IErrorWithStatus) {

// Adjusted constructor to handle the from field
constructor({ from, message, status }: IErrorWithStatus) {
this.from = from;
this.message = message;
this.status = status;
}
}

export class ProtectRouterError extends ErrorWithStatus {
constructor({ message, status }: IErrorWithStatus) {
super({ from: "ProtectRouterError", message, status });
}
}

export class ErrorEntity extends ErrorWithStatus {
data: ErrorsType;
constructor({
Expand Down
Empty file.
3 changes: 3 additions & 0 deletions src/modules/protectRouting/protect.messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ export const PROTECT_MESSAGES = {
ROLE_CUSTOMER: "You are customer",
ROLE_EMPLOYEE: "You are employee",
ROLE_NOT_FOUND: "Role not found",
ACCESS_DENIED: "Access denied",
UNAUTHORIZED: "Unauthorized",
ROUTE_AND_ROLE_MIS_MATCH: "Route and role mismatch",
ROUTES_CONFIG_WRONG: "Routes config is wrong",
MISSING_ACCESS_TOKEN: "Missing access token",
} as const;
30 changes: 22 additions & 8 deletions src/modules/protectRouting/protect.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ interface Module {
route: { [key: string]: Route };
}

interface RouteConfig {
path: string;
roles: UserRole[];
}

export const routesConfig: RouteConfig[] = [
{
path: "/admin",
roles: [UserRole.Employee, UserRole.Customer, UserRole.Admin],
}, //Admin can access all
{ path: "/user", roles: [UserRole.Customer] }, // User only access to Customer
{ path: "/employee", roles: [UserRole.Employee] }, // Employee only access to Employee
];

export function getOpenRoutes(): string[] {
const openRoutes: string[] = [];

Expand All @@ -31,12 +45,12 @@ export function getOpenRoutes(): string[] {
return openRoutes;
}

export function checkRole(role: UserRole): void {
if (role === UserRole.Admin) {
console.log("User are Admin");
} else if (role === UserRole.Customer) {
console.log("User are Customer");
} else {
console.log("User are Employee");
}
// math the route.path with the req.path (/user/login) but take the first part only (/user)
export function checkRole(
path: string,
pattern: string,
): RouteConfig | undefined {
return routesConfig.find(
(route) => route.path === pattern + path.split(pattern)[1],
);
}
58 changes: 42 additions & 16 deletions src/modules/user/user.middlewares.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import "dotenv/config";
import { NextFunction, Request, Response } from "express";
import { NextFunction, Request, Response, response } from "express";
import { ParamsDictionary } from "express-serve-static-core";
import { ParamSchema, checkSchema } from "express-validator";
import { StatusCodes } from "http-status-codes";
Expand All @@ -9,7 +9,11 @@ import { ObjectId } from "mongodb";
import validator from "validator";
import { HTTP_STATUS } from "~/constants/httpStatus";
import databaseService from "~/database/database.services";
import { ErrorEntity, ErrorWithStatus } from "~/errors/errors.entityError";
import {
ErrorEntity,
ErrorWithStatus,
ProtectRouterError,
} from "~/errors/errors.entityError";
import { USER_MESSAGES } from "~/modules/user/user.messages";
import { isDeveloperAgent } from "~/utils/agent";
import { encrypt, hashPassword } from "~/utils/crypto";
Expand All @@ -20,7 +24,7 @@ import { OTP_STATUS } from "../otp/otp.enum";
import { OTP_MESSAGES } from "../otp/otp.messages";
import otpService from "../otp/otp.services";
import { PROTECT_MESSAGES } from "../protectRouting/protect.messages";
import { checkRole } from "../protectRouting/protect.utils";
import { checkRole, routesConfig } from "../protectRouting/protect.utils";
import { NoticeUser, UserRole, UserVerifyStatus } from "./user.enum";
import { LoginRequestBody, TokenPayload } from "./user.requests";
import usersService from "./user.services";
Expand Down Expand Up @@ -997,12 +1001,12 @@ export const accessTokenValidatorV2 = validate(
custom: {
options: async (value: string, { req }) => {
const access_token = value.split(" ")[1];
// if do not have access_token, throw error
// if do not have access_token, throw error bad request (reason: lack of access_token)
// because we already passed openRoutes
if (!access_token) {
throw new ErrorWithStatus({
message: USER_MESSAGES.ACCESS_TOKEN_IS_REQUIRED,
status: HTTP_STATUS.UNAUTHORIZED,
throw new ProtectRouterError({
message: PROTECT_MESSAGES.ACCESS_DENIED,
status: HTTP_STATUS.BAD_REQUEST,
});
}

Expand All @@ -1023,20 +1027,42 @@ export const accessTokenValidatorV2 = validate(

const role = user?.role;

// if role not found, throw error
// this case only happen when data in database do not have role field
if (!role) {
throw new ErrorWithStatus({
throw new ProtectRouterError({
message: PROTECT_MESSAGES.ROLE_NOT_FOUND,
status: HTTP_STATUS.UNAUTHORIZED,
status: HTTP_STATUS.BAD_REQUEST,
});
}

const route = checkRole(req.path, "/");

// route not found when req.path is undefined or req.path is not in routesConfig
if (!route) {
throw new ProtectRouterError({
message:
PROTECT_MESSAGES.ROUTE_AND_ROLE_MIS_MATCH +
PROTECT_MESSAGES.ROUTES_CONFIG_WRONG,
status: HTTP_STATUS.BAD_REQUEST,
});
} else if (!route.roles.includes(role)) {
throw new ProtectRouterError({
message: PROTECT_MESSAGES.ACCESS_DENIED,
status: HTTP_STATUS.NOT_FOUND,
});
}
checkRole(role);
} catch (error) {
throw new ErrorWithStatus({
message: capitalize(
(error as JsonWebTokenError).message,
),
status: HTTP_STATUS.UNAUTHORIZED,
});
if (error instanceof JsonWebTokenError) {
throw new ProtectRouterError({
message: capitalize(
(error as JsonWebTokenError).message,
),
status: HTTP_STATUS.UNAUTHORIZED,
});
} else if (error instanceof ProtectRouterError) {
throw error;
}
}
return true;
},
Expand Down

0 comments on commit 6e8acfe

Please sign in to comment.