Skip to content

Commit

Permalink
Implement basic RBAC
Browse files Browse the repository at this point in the history
Forbid front end navigation and API access if at least one role isn't present
  • Loading branch information
kyle1morel committed Feb 6, 2024
1 parent 1bc96a5 commit e7c5c91
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 2 deletions.
10 changes: 9 additions & 1 deletion app/src/components/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,21 @@ export const DEFAULTCORS = Object.freeze({
origin: true
});

/** Current user authentication type */
/** Current user authentication types */
export const IdentityProvider = Object.freeze({
IDIR: 'idir',
BCEID: 'bceidbasic',
BCEIDBUSINESS: 'bceidbusiness'
});

/** Current user access role types */
export const AccessRoles = Object.freeze({
PCNS_ADMIN: 'PCNS_ADMIN',
PCNS_DEVELOPER: 'PCNS_DEVELOPER',
PCNS_NAVIGATOR: 'PCNS_NAVIGATOR',
PCNS_OTHER: 'PCNS_OTHER'
});

/** CHEFS form statuses */
export const APPLICATION_STATUS_LIST = Object.freeze({
NEW: 'New',
Expand Down
32 changes: 32 additions & 0 deletions app/src/middleware/authorization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// @ts-expect-error api-problem lacks a defined interface; code still works fine
import Problem from 'api-problem';

import { AccessRoles } from '../components/constants';

import type { NextFunction, Request, Response } from '../interfaces/IExpress';

/**
* @function hasAccess
* Check if the currentUser has at least one assigned role
* @param {Request} req Express request object
* @param {Response} res Express response object
* @param {NextFunction} next The next callback function
* @returns {function} Express middleware function
* @throws The error encountered upon failure
*/
export const hasAccess = async (req: Request, res: Response, next: NextFunction) => {
try {
// TODO: Can we expand tokenPayload to include client_roles?
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const roles = (req.currentUser?.tokenPayload as any)?.client_roles;
if (!roles || !Object.values(AccessRoles).some((r) => roles.includes(r))) {
throw new Error('Invalid role authorization');
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
return next(new Problem(403, { detail: err.message, instance: req.originalUrl }));
}

// Continue middleware
next();
};
2 changes: 2 additions & 0 deletions app/src/routes/v1/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { currentUser } from '../../middleware/authentication';
import { hasAccess } from '../../middleware/authorization';
import express from 'express';
import chefs from './chefs';
import document from './document';
Expand All @@ -7,6 +8,7 @@ import user from './user';

const router = express.Router();
router.use(currentUser);
router.use(hasAccess);

// Base v1 Responder
router.get('/', (_req, res) => {
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ export default function getRouter() {
if (!user || user.expired) {
router.replace({ name: RouteNames.LOGIN });
}

// Forbid if user does not have at least one assigned role
if (user && (!user?.profile?.client_roles || (user?.profile?.client_roles as []).length === 0)) {
router.replace({ name: RouteNames.FORBIDDEN });
}
}
});

Expand Down
11 changes: 11 additions & 0 deletions frontend/src/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ACCESS_ROLES,
APPLICATION_STATUS_LIST,
INTAKE_STATUS_LIST,
PERMIT_STATUS,
Expand Down Expand Up @@ -51,6 +52,16 @@ export const ToastTimeout = Object.freeze({
WARNING: 5000
});

/**
* Route names
*/
export const AccessRoles = Object.freeze({
PCNS_ADMIN: ACCESS_ROLES.PCNS_ADMIN,
PCNS_DEVELOPER: ACCESS_ROLES.PCNS_DEVELOPER,
PCNS_NAVIGATOR: ACCESS_ROLES.PCNS_NAVIGATOR,
PCNS_OTHER: ACCESS_ROLES.PCNS_OTHER
});

/**
* CHEFS form constants
*/
Expand Down
14 changes: 13 additions & 1 deletion frontend/src/utils/enums.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
// Enums that represent general app-wide values

// Which way to display a button that can appear in multiple modes
/**
* Which way to display a button that can appear in multiple modes
*/
export const enum BUTTON_MODE {
BUTTON = 'BUTTON',
ICON = 'ICON'
}

/**
* Current user access role types
*/
export const ACCESS_ROLES = Object.freeze({
PCNS_ADMIN: 'PCNS_ADMIN',
PCNS_DEVELOPER: 'PCNS_DEVELOPER',
PCNS_NAVIGATOR: 'PCNS_NAVIGATOR',
PCNS_OTHER: 'PCNS_OTHER'
});

/**
* CHEFS form statuses
*/
Expand Down

0 comments on commit e7c5c91

Please sign in to comment.