Skip to content

Commit

Permalink
WIP: yars service and hasPermission middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle1morel committed Jul 22, 2024
1 parent effb7d6 commit 59024f7
Show file tree
Hide file tree
Showing 13 changed files with 281 additions and 103 deletions.
9 changes: 0 additions & 9 deletions app/src/controllers/sso.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,6 @@ const controller = {
} catch (e: unknown) {
next(e);
}
},

getRoles: async (req: Request, res: Response, next: NextFunction) => {
try {
const response = await ssoService.getRoles();
res.status(response.status).json(response.data);
} catch (e: unknown) {
next(e);
}
}
};

Expand Down
42 changes: 23 additions & 19 deletions app/src/middleware/authorization.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
// @ts-expect-error api-problem lacks a defined interface; code still works fine
import Problem from 'api-problem';

import { ACCESS_ROLES_LIST } from '../utils/constants/application';
import { yarsService } from '../services';

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
* @function hasPermission
* Obtains the roles for the current users identity
* Checks if the permission mappings contain the given resource/action pair for any of the users roles
* @param {string} resource a resource name
* @param {string} action an action name
* @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 || ACCESS_ROLES_LIST.some((r) => roles.includes(r))) {
throw new Error('Invalid role authorization');
export const hasPermission = (resource: string, action: string) => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const roles = await yarsService.getIdentityRoles((req.currentUser?.tokenPayload as any).preferred_username);

const results = await Promise.all(roles.map((x) => yarsService.roleHasPermission(x.roleId, resource, action)));

if (!results.includes(true)) {
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 }));
}
// 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();
// Continue middleware
next();
};
};
15 changes: 12 additions & 3 deletions app/src/routes/v1/document.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import express from 'express';
import { documentController } from '../../controllers';
import { hasPermission } from '../../middleware/authorization';
import { requireSomeAuth } from '../../middleware/requireSomeAuth';
import { Action, Resource } from '../../utils/enums/application';
import { documentValidator } from '../../validators';

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

const router = express.Router();
router.use(requireSomeAuth);

router.put('/', documentValidator.createDocument, (req: Request, res: Response, next: NextFunction): void => {
documentController.createDocument(req, res, next);
});
router.put(
'/',
hasPermission(Resource.DOCUMENT, Action.CREATE),
documentValidator.createDocument,
(req: Request, res: Response, next: NextFunction): void => {
documentController.createDocument(req, res, next);
}
);

router.delete(
'/:documentId',
hasPermission(Resource.DOCUMENT, Action.DELETE),
documentValidator.deleteDocument,
(req: Request, res: Response, next: NextFunction): void => {
documentController.deleteDocument(req, res, next);
Expand All @@ -22,6 +30,7 @@ router.delete(

router.get(
'/list/:activityId',
hasPermission(Resource.DOCUMENT, Action.READ),
documentValidator.listDocuments,
(req: Request, res: Response, next: NextFunction): void => {
documentController.listDocuments(req, res, next);
Expand Down
46 changes: 34 additions & 12 deletions app/src/routes/v1/enquiry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import express from 'express';
import { enquiryController } from '../../controllers';
import { hasPermission } from '../../middleware/authorization';
import { requireSomeAuth } from '../../middleware/requireSomeAuth';
import { Action, Resource } from '../../utils/enums/application';
import { enquiryValidator } from '../../validators';

import type { NextFunction, Request, Response } from '../../interfaces/IExpress';
Expand All @@ -23,23 +25,36 @@ const decideValidation = (validator: Middleware) => {
};

/** Gets a list of enquiries */
router.get('/', (req: Request, res: Response, next: NextFunction): void => {
enquiryController.getEnquiries(req, res, next);
});
router.get(
'/',
hasPermission(Resource.ENQUIRY, Action.READ),
(req: Request, res: Response, next: NextFunction): void => {
enquiryController.getEnquiries(req, res, next);
}
);

/** Gets a specific enquiry */
router.get('/:enquiryId', (req: Request, res: Response, next: NextFunction): void => {
enquiryController.getEnquiry(req, res, next);
});
router.get(
'/:enquiryId',
hasPermission(Resource.ENQUIRY, Action.READ),
(req: Request, res: Response, next: NextFunction): void => {
enquiryController.getEnquiry(req, res, next);
}
);

/** Deletes an enquiry */
router.delete('/:enquiryId', (req: Request, res: Response, next: NextFunction): void => {
enquiryController.deleteEnquiry(req, res, next);
});
router.delete(
'/:enquiryId',
hasPermission(Resource.ENQUIRY, Action.DELETE),
(req: Request, res: Response, next: NextFunction): void => {
enquiryController.deleteEnquiry(req, res, next);
}
);

/** Creates an enquiry with Draft status */
router.put(
'/draft',
hasPermission(Resource.ENQUIRY, Action.CREATE),
decideValidation(enquiryValidator.createDraft),
(req: Request, res: Response, next: NextFunction): void => {
enquiryController.createDraft(req, res, next);
Expand All @@ -49,20 +64,27 @@ router.put(
/** Updates an enquiry with Draft status */
router.put(
'/draft/:enquiryId',
hasPermission(Resource.ENQUIRY, Action.UPDATE),
decideValidation(enquiryValidator.updateDraft),
(req: Request, res: Response, next: NextFunction): void => {
enquiryController.updateDraft(req, res, next);
}
);

/** Updates an enquiry */
router.put('/:enquiryId', enquiryValidator.updateEnquiry, (req: Request, res: Response, next: NextFunction): void => {
enquiryController.updateEnquiry(req, res, next);
});
router.put(
'/:enquiryId',
hasPermission(Resource.ENQUIRY, Action.UPDATE),
enquiryValidator.updateEnquiry,
(req: Request, res: Response, next: NextFunction): void => {
enquiryController.updateEnquiry(req, res, next);
}
);

/** Updates is_deleted flag for an enquiry */
router.patch(
'/:enquiryId/delete',
hasPermission(Resource.ENQUIRY, Action.DELETE),
enquiryValidator.updateIsDeletedFlag,
(req: Request, res: Response, next: NextFunction): void => {
enquiryController.updateIsDeletedFlag(req, res, next);
Expand Down
59 changes: 42 additions & 17 deletions app/src/routes/v1/note.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import express from 'express';
import { noteController } from '../../controllers';
import { hasPermission } from '../../middleware/authorization';
import { requireSomeAuth } from '../../middleware/requireSomeAuth';
import { Action, Resource } from '../../utils/enums/application';
import { noteValidator } from '../../validators';

import type { NextFunction, Request, Response } from '../../interfaces/IExpress';
Expand All @@ -9,26 +11,49 @@ const router = express.Router();
router.use(requireSomeAuth);

// Note create endpoint
router.put('/', noteValidator.createNote, (req: Request, res: Response, next: NextFunction): void => {
noteController.createNote(req, res, next);
});

router.put('/:noteId', noteValidator.updateNote, (req: Request, res: Response, next: NextFunction): void => {
noteController.updateNote(req, res, next);
});
router.put(
'/',
hasPermission(Resource.NOTE, Action.CREATE),
noteValidator.createNote,
(req: Request, res: Response, next: NextFunction): void => {
noteController.createNote(req, res, next);
}
);

router.put(
'/:noteId',
hasPermission(Resource.NOTE, Action.UPDATE),
noteValidator.updateNote,
(req: Request, res: Response, next: NextFunction): void => {
noteController.updateNote(req, res, next);
}
);

// Note delete endpoint
router.delete('/:noteId', (req: Request, res: Response, next: NextFunction): void => {
noteController.deleteNote(req, res, next);
});
router.delete(
'/:noteId',
hasPermission(Resource.NOTE, Action.DELETE),
(req: Request, res: Response, next: NextFunction): void => {
noteController.deleteNote(req, res, next);
}
);

// Note list endpoints
router.get('/bringForward', (req: Request, res: Response, next: NextFunction): void => {
noteController.listBringForward(req, res, next);
});

router.get('/list/:activityId', noteValidator.listNotes, (req: Request, res: Response, next: NextFunction): void => {
noteController.listNotes(req, res, next);
});
router.get(
'/bringForward',
hasPermission(Resource.NOTE, Action.READ),
(req: Request, res: Response, next: NextFunction): void => {
noteController.listBringForward(req, res, next);
}
);

router.get(
'/list/:activityId',
hasPermission(Resource.NOTE, Action.READ),
noteValidator.listNotes,
(req: Request, res: Response, next: NextFunction): void => {
noteController.listNotes(req, res, next);
}
);

export default router;
46 changes: 34 additions & 12 deletions app/src/routes/v1/permit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import express from 'express';
import { permitController } from '../../controllers';
import { hasPermission } from '../../middleware/authorization';
import { requireSomeAuth } from '../../middleware/requireSomeAuth';
import { Action, Resource } from '../../utils/enums/application';
import { permitValidator } from '../../validators';

import type { NextFunction, Request, Response } from '../../interfaces/IExpress';
Expand All @@ -9,32 +11,52 @@ const router = express.Router();
router.use(requireSomeAuth);

// Permit create endpoint
router.put('/', permitValidator.createPermit, (req: Request, res: Response, next: NextFunction): void => {
permitController.createPermit(req, res, next);
});
router.put(
'/',
hasPermission(Resource.PERMIT, Action.CREATE),
permitValidator.createPermit,
(req: Request, res: Response, next: NextFunction): void => {
permitController.createPermit(req, res, next);
}
);

// Permit update endpoint
router.put('/:permitId', permitValidator.updatePermit, (req: Request, res: Response, next: NextFunction): void => {
permitController.updatePermit(req, res, next);
});
router.put(
'/:permitId',
hasPermission(Resource.PERMIT, Action.UPDATE),
permitValidator.updatePermit,
(req: Request, res: Response, next: NextFunction): void => {
permitController.updatePermit(req, res, next);
}
);

// Permit delete endpoint
router.delete('/:permitId', permitValidator.deletePermit, (req: Request, res: Response, next: NextFunction): void => {
permitController.deletePermit(req, res, next);
});
router.delete(
'/:permitId',
hasPermission(Resource.PERMIT, Action.DELETE),
permitValidator.deletePermit,
(req: Request, res: Response, next: NextFunction): void => {
permitController.deletePermit(req, res, next);
}
);

// Permit list by activity endpoint
router.get(
'/list/:activityId',
hasPermission(Resource.PERMIT, Action.READ),
permitValidator.listPermits,
(req: Request, res: Response, next: NextFunction): void => {
permitController.listPermits(req, res, next);
}
);

// Permit types endpoint
router.get('/types', (req: Request, res: Response, next: NextFunction): void => {
permitController.getPermitTypes(req, res, next);
});
router.get(
'/types',
hasPermission(Resource.PERMIT, Action.READ),
(req: Request, res: Response, next: NextFunction): void => {
permitController.getPermitTypes(req, res, next);
}
);

export default router;
13 changes: 10 additions & 3 deletions app/src/routes/v1/roadmap.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import express from 'express';
import { roadmapController } from '../../controllers';
import { hasPermission } from '../../middleware/authorization';
import { requireSomeAuth } from '../../middleware/requireSomeAuth';
import { Action, Resource } from '../../utils/enums/application';
import { roadmapValidator } from '../../validators';

import type { NextFunction, Request, Response } from '../../interfaces/IExpress';
Expand All @@ -9,8 +11,13 @@ const router = express.Router();
router.use(requireSomeAuth);

// Send an email with the roadmap data
router.put('/', roadmapValidator.send, (req: Request, res: Response, next: NextFunction): void => {
roadmapController.send(req, res, next);
});
router.put(
'/',
hasPermission(Resource.ROADMAP, Action.CREATE),
roadmapValidator.send,
(req: Request, res: Response, next: NextFunction): void => {
roadmapController.send(req, res, next);
}
);

export default router;
Loading

0 comments on commit 59024f7

Please sign in to comment.