diff --git a/app/src/controllers/sso.ts b/app/src/controllers/sso.ts index 0cba97da..b8d48e87 100644 --- a/app/src/controllers/sso.ts +++ b/app/src/controllers/sso.ts @@ -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); - } } }; diff --git a/app/src/controllers/submission.ts b/app/src/controllers/submission.ts index 809f31b8..a6011718 100644 --- a/app/src/controllers/submission.ts +++ b/app/src/controllers/submission.ts @@ -9,7 +9,7 @@ import { permitService, userService } from '../services'; -import { BasicResponse, Initiative } from '../utils/enums/application'; +import { BasicResponse, Initiative, Scope } from '../utils/enums/application'; import { ApplicationStatus, IntakeStatus, @@ -21,7 +21,15 @@ import { import { camelCaseToTitleCase, deDupeUnsure, getCurrentIdentity, isTruthy, toTitleCase } from '../utils/utils'; import type { NextFunction, Request, Response } from '../interfaces/IExpress'; -import type { ChefsFormConfig, ChefsFormConfigData, Submission, ChefsSubmissionExport, Permit, Email } from '../types'; +import type { + ChefsFormConfig, + ChefsFormConfigData, + Submission, + ChefsSubmissionExport, + Permit, + Email, + ApiScope +} from '../types'; const controller = { checkAndStoreNewSubmissions: async () => { @@ -368,13 +376,15 @@ const controller = { getSubmissions: async (req: Request, res: Response, next: NextFunction) => { try { + const apiScope = req.currentUser?.apiScope as ApiScope; + // Check for and store new submissions in CHEFS await controller.checkAndStoreNewSubmissions(); // Pull from PCNS database let response = await submissionService.getSubmissions(); - if (isTruthy(req.query.self)) { + if (apiScope.name === Scope.SELF) { // eslint-disable-next-line @typescript-eslint/no-explicit-any response = response.filter((x) => x?.submittedBy === (req.currentUser?.tokenPayload as any)?.idir_username); } @@ -399,10 +409,18 @@ const controller = { next: NextFunction ) => { try { - const response = await submissionService.searchSubmissions({ + const apiScope = req.currentUser?.apiScope as ApiScope; + + let response = await submissionService.searchSubmissions({ ...req.query, includeUser: isTruthy(req.query.includeUser) }); + + if (apiScope.name === Scope.SELF) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + response = response.filter((x) => x?.submittedBy === (req.currentUser?.tokenPayload as any)?.idir_username); + } + res.status(200).json(response); } catch (e: unknown) { next(e); diff --git a/app/src/interfaces/IExpress.ts b/app/src/interfaces/IExpress.ts index 4377289b..5cd4a880 100644 --- a/app/src/interfaces/IExpress.ts +++ b/app/src/interfaces/IExpress.ts @@ -1,6 +1,7 @@ import * as core from 'express-serve-static-core'; import type { CurrentUser } from '../types/CurrentUser'; +import { AuthType } from '../utils/enums/application'; interface Query extends core.Query {} diff --git a/app/src/middleware/authentication.ts b/app/src/middleware/authentication.ts index 7cda0c45..41e8e36a 100644 --- a/app/src/middleware/authentication.ts +++ b/app/src/middleware/authentication.ts @@ -67,7 +67,7 @@ export const currentUser = async (req: Request, res: Response, next: NextFunctio } // Inject currentUser data into request - req.currentUser = Object.freeze(currentUser); + req.currentUser = currentUser; // Continue middleware next(); diff --git a/app/src/middleware/authorization.ts b/app/src/middleware/authorization.ts index 0fd19dce..e7507a3d 100644 --- a/app/src/middleware/authorization.ts +++ b/app/src/middleware/authorization.ts @@ -1,32 +1,69 @@ // @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 { userService, yarsService } from '../services'; import type { NextFunction, Request, Response } from '../interfaces/IExpress'; +import { Scope } from '../utils/enums/application'; +import { getCurrentIdentity } from '../utils/utils'; +import { NIL } from 'uuid'; + +// Converts a primitive string to a Scope enum type +function convertStringToScope(value: string): Scope | undefined { + return (Object.values(Scope) as Array).includes(value) ? (value as Scope) : undefined; +} /** - * @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 + * Obtains the full permission mappings for the given resource/action pair for any of the users roles + * 403 if none are found + * Checks for highest priority scope and injects into the currentUser + * Defaults scope to self if none were found + * @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 { + if (req.currentUser) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const roles = await yarsService.getIdentityRoles((req.currentUser?.tokenPayload as any).preferred_username); + + const permissions = await Promise.all( + roles.map((x) => yarsService.getRolePermissionDetails(x.roleId, resource, action)) + ).then((x) => x.flat()); + + if (!permissions || permissions.length === 0) { + throw new Error('Invalid role authorization'); + } + + const userId = await userService.getCurrentUserId(getCurrentIdentity(req.currentUser, NIL), NIL); + + if (!userId) { + throw new Error('Invalid role user'); + } + + const scopes = permissions + .filter((x) => !!x.scopeName) + .map((x) => ({ scopeName: x.scopeName as string, scopePriority: x.scopePriority as number })) + .sort((a, b) => (a.scopePriority > b.scopePriority ? 1 : -1)); + + req.currentUser.apiScope = { + name: scopes.length ? convertStringToScope(scopes[0].scopeName) ?? Scope.SELF : Scope.SELF, + userId: userId + }; + } else { + throw new Error('No current user'); + } + // 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(); + }; }; diff --git a/app/src/routes/v1/document.ts b/app/src/routes/v1/document.ts index f327595f..5d441686 100644 --- a/app/src/routes/v1/document.ts +++ b/app/src/routes/v1/document.ts @@ -1,6 +1,9 @@ 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'; @@ -8,12 +11,18 @@ 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); @@ -22,6 +31,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); diff --git a/app/src/routes/v1/enquiry.ts b/app/src/routes/v1/enquiry.ts index 2b1fdae6..6b3e4312 100644 --- a/app/src/routes/v1/enquiry.ts +++ b/app/src/routes/v1/enquiry.ts @@ -1,6 +1,9 @@ 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'; @@ -23,23 +26,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); @@ -49,6 +65,7 @@ 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); @@ -56,13 +73,19 @@ router.put( ); /** 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); diff --git a/app/src/routes/v1/index.ts b/app/src/routes/v1/index.ts index 082fe123..95bca13a 100644 --- a/app/src/routes/v1/index.ts +++ b/app/src/routes/v1/index.ts @@ -1,7 +1,7 @@ -import { currentUser } from '../../middleware/authentication'; - import express from 'express'; +import { currentUser } from '../../middleware/authentication'; + import document from './document'; import enquiry from './enquiry'; import note from './note'; diff --git a/app/src/routes/v1/note.ts b/app/src/routes/v1/note.ts index b6deb95c..0ba5e2e2 100644 --- a/app/src/routes/v1/note.ts +++ b/app/src/routes/v1/note.ts @@ -1,6 +1,9 @@ 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'; @@ -9,26 +12,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); + } +); + +router.get( + '/bringForward', + hasPermission(Resource.NOTE, Action.READ), + (req: Request, res: Response, next: NextFunction): void => { + noteController.listBringForward(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( + '/list/:activityId', + hasPermission(Resource.NOTE, Action.READ), + noteValidator.listNotes, + (req: Request, res: Response, next: NextFunction): void => { + noteController.listNotes(req, res, next); + } +); export default router; diff --git a/app/src/routes/v1/permit.ts b/app/src/routes/v1/permit.ts index 0f1514db..87a7c400 100644 --- a/app/src/routes/v1/permit.ts +++ b/app/src/routes/v1/permit.ts @@ -1,6 +1,9 @@ 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'; @@ -9,23 +12,39 @@ 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); @@ -33,8 +52,12 @@ router.get( ); // 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; diff --git a/app/src/routes/v1/roadmap.ts b/app/src/routes/v1/roadmap.ts index 4522b09d..0d44bf4f 100644 --- a/app/src/routes/v1/roadmap.ts +++ b/app/src/routes/v1/roadmap.ts @@ -1,6 +1,9 @@ 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'; @@ -9,8 +12,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; diff --git a/app/src/routes/v1/sso.ts b/app/src/routes/v1/sso.ts index e9564e4e..215ba8ef 100644 --- a/app/src/routes/v1/sso.ts +++ b/app/src/routes/v1/sso.ts @@ -1,6 +1,9 @@ import express from 'express'; + import { ssoController } from '../../controllers'; +import { hasPermission } from '../../middleware/authorization'; import { requireSomeAuth } from '../../middleware/requireSomeAuth'; +import { Action, Resource } from '../../utils/enums/application'; import type { NextFunction, Request, Response } from '../../interfaces/IExpress'; @@ -11,16 +14,20 @@ router.post('/requestBasicAccess', (req: Request, res: Response, next: NextFunct ssoController.requestBasicAccess(req, res, next); }); -router.get('/idir/users', (req: Request, res: Response, next: NextFunction): void => { - ssoController.searchIdirUsers(req, res, next); -}); +router.get( + '/idir/users', + hasPermission(Resource.SSO, Action.READ), + (req: Request, res: Response, next: NextFunction): void => { + ssoController.searchIdirUsers(req, res, next); + } +); -router.get('/basic-bceid/users', (req: Request, res: Response, next: NextFunction): void => { - ssoController.searchBasicBceidUsers(req, res, next); -}); - -router.get('/roles', (req: Request, res: Response, next: NextFunction): void => { - ssoController.getRoles(req, res, next); -}); +router.get( + '/basic-bceid/users', + hasPermission(Resource.SSO, Action.READ), + (req: Request, res: Response, next: NextFunction): void => { + ssoController.searchBasicBceidUsers(req, res, next); + } +); export default router; diff --git a/app/src/routes/v1/submission.ts b/app/src/routes/v1/submission.ts index b20a8089..55b20847 100644 --- a/app/src/routes/v1/submission.ts +++ b/app/src/routes/v1/submission.ts @@ -4,18 +4,25 @@ import { requireSomeAuth } from '../../middleware/requireSomeAuth'; import { submissionValidator } from '../../validators'; import type { NextFunction, Request, Response } from '../../interfaces/IExpress'; +import { hasPermission } from '../../middleware/authorization'; +import { Action, Resource } from '../../utils/enums/application'; const router = express.Router(); router.use(requireSomeAuth); /** Gets a list of submissions */ -router.get('/', (req: Request, res: Response, next: NextFunction): void => { - submissionController.getSubmissions(req, res, next); -}); +router.get( + '/', + hasPermission(Resource.SUBMISSION, Action.READ), + (req: Request, res: Response, next: NextFunction): void => { + submissionController.getSubmissions(req, res, next); + } +); /** Search submissions */ router.get( '/search', + hasPermission(Resource.SUBMISSION, Action.READ), submissionValidator.searchSubmissions, (req: Request, res: Response, next: NextFunction): void => { submissionController.searchSubmissions(req, res, next); @@ -25,6 +32,7 @@ router.get( /** Gets submission statistics*/ router.get( '/statistics', + hasPermission(Resource.SUBMISSION, Action.READ), submissionValidator.getStatistics, (req: Request, res: Response, next: NextFunction): void => { submissionController.getStatistics(req, res, next); @@ -32,18 +40,27 @@ router.get( ); /** Creates a submission with Draft status */ -router.put('/draft', (req: Request, res: Response, next: NextFunction): void => { - submissionController.createDraft(req, res, next); -}); +router.put( + '/draft', + hasPermission(Resource.SUBMISSION, Action.CREATE), + (req: Request, res: Response, next: NextFunction): void => { + submissionController.createDraft(req, res, next); + } +); /** Updates a submission with Draft status */ -router.put('/draft/:submissionId', (req: Request, res: Response, next: NextFunction): void => { - submissionController.updateDraft(req, res, next); -}); +router.put( + '/draft/:submissionId', + hasPermission(Resource.SUBMISSION, Action.UPDATE), + (req: Request, res: Response, next: NextFunction): void => { + submissionController.updateDraft(req, res, next); + } +); // Send an email with the confirmation of submission router.put( '/emailConfirmation', + hasPermission(Resource.SUBMISSION, Action.CREATE), submissionValidator.emailConfirmation, (req: Request, res: Response, next: NextFunction): void => { submissionController.emailConfirmation(req, res, next); @@ -51,13 +68,19 @@ router.put( ); /** Creates a submission */ -router.put('/', submissionValidator.createSubmission, (req: Request, res: Response, next: NextFunction): void => { - submissionController.createSubmission(req, res, next); -}); +router.put( + '/', + hasPermission(Resource.SUBMISSION, Action.CREATE), + submissionValidator.createSubmission, + (req: Request, res: Response, next: NextFunction): void => { + submissionController.createSubmission(req, res, next); + } +); /** Deletes a submission */ router.delete( '/:submissionId', + hasPermission(Resource.SUBMISSION, Action.DELETE), submissionValidator.deleteSubmission, (req: Request, res: Response, next: NextFunction): void => { submissionController.deleteSubmission(req, res, next); @@ -65,11 +88,9 @@ router.delete( ); /** Gets a specific submission */ -router.get('/search', (req: Request, res: Response, next: NextFunction): void => { - submissionController.searchSubmissions(req, res, next); -}); router.get( '/:submissionId', + hasPermission(Resource.SUBMISSION, Action.READ), submissionValidator.getSubmission, (req: Request, res: Response, next: NextFunction): void => { submissionController.getSubmission(req, res, next); @@ -79,6 +100,7 @@ router.get( /** Updates a submission*/ router.put( '/:submissionId', + hasPermission(Resource.SUBMISSION, Action.UPDATE), submissionValidator.updateSubmission, (req: Request, res: Response, next: NextFunction): void => { submissionController.updateSubmission(req, res, next); @@ -88,6 +110,7 @@ router.put( /** Updates is_deleted flag for a submission */ router.patch( '/:submissionId/delete', + hasPermission(Resource.SUBMISSION, Action.DELETE), submissionValidator.updateIsDeletedFlag, (req: Request, res: Response, next: NextFunction): void => { submissionController.updateIsDeletedFlag(req, res, next); diff --git a/app/src/routes/v1/user.ts b/app/src/routes/v1/user.ts index b09b9c17..9303f9c6 100644 --- a/app/src/routes/v1/user.ts +++ b/app/src/routes/v1/user.ts @@ -1,6 +1,9 @@ import express from 'express'; + import { userController } from '../../controllers'; +import { hasPermission } from '../../middleware/authorization'; import { requireSomeAuth } from '../../middleware/requireSomeAuth'; +import { Action, Resource } from '../../utils/enums/application'; import { userValidator } from '../../validators'; import type { NextFunction, Request, Response } from 'express'; @@ -8,9 +11,14 @@ import type { NextFunction, Request, Response } from 'express'; const router = express.Router(); router.use(requireSomeAuth); -// Submission endpoint -router.get('/', userValidator.searchUsers, (req: Request, res: Response, next: NextFunction): void => { - userController.searchUsers(req, res, next); -}); +// Search users endpoint +router.get( + '/', + hasPermission(Resource.USER, Action.READ), + userValidator.searchUsers, + (req: Request, res: Response, next: NextFunction): void => { + userController.searchUsers(req, res, next); + } +); export default router; diff --git a/app/src/services/index.ts b/app/src/services/index.ts index 4264a3fd..d5c30876 100644 --- a/app/src/services/index.ts +++ b/app/src/services/index.ts @@ -8,3 +8,4 @@ export { default as permitService } from './permit'; export { default as ssoService } from './sso'; export { default as submissionService } from './submission'; export { default as userService } from './user'; +export { default as yarsService } from './yars'; diff --git a/app/src/services/submission.ts b/app/src/services/submission.ts index ae9637ca..bb50be92 100644 --- a/app/src/services/submission.ts +++ b/app/src/services/submission.ts @@ -9,7 +9,7 @@ import { ApplicationStatus } from '../utils/enums/housing'; import { getChefsApiKey } from '../utils/utils'; import type { AxiosInstance, AxiosRequestConfig } from 'axios'; -import type { Submission, SubmissionSearchParameters } from '../types'; +import type { ApiScope, Submission, SubmissionSearchParameters } from '../types'; /** * @function chefsAxios @@ -196,7 +196,7 @@ const service = { } }, - /** + /* * @function getSubmissions * Gets a list of submissions * @returns {Promise<(Submission | null)[]>} The result of running the findMany operation diff --git a/app/src/services/yars.ts b/app/src/services/yars.ts new file mode 100644 index 00000000..ed8a5293 --- /dev/null +++ b/app/src/services/yars.ts @@ -0,0 +1,51 @@ +/* eslint-disable no-useless-catch */ + +import prisma from '../db/dataConnection'; + +const service = { + /** + * @function getEnquiry + * Gets roles for the specified identity + * @param {string} identityId Identity ID to search + * @returns {Promise} The result of running the findMany operation + */ + getIdentityRoles: async (identityId: string) => { + try { + const result = await prisma.identity_role.findMany({ + where: { + identity_id: identityId + } + }); + + return result.map((x) => ({ roleId: x.role_id })); + } catch (e: unknown) { + throw e; + } + }, + + getRolePermissionDetails: async (roleId: number, resourceName: string, actionName: string) => { + try { + const result = await prisma.role_permission_vw.findMany({ + where: { + role_id: roleId, + resource_name: resourceName, + action_name: actionName + } + }); + + return result.map((x) => ({ + initiativeName: x.initiative_name, + userType: x.user_type, + policyName: x.policy_name, + scopeName: x.scope_name, + scopePriority: x.scope_priority, + resourceName: x.resource_name, + actionName: x.action_name + })); + } catch (e: unknown) { + throw e; + } + } +}; + +export default service; diff --git a/app/src/types/ApiScope.ts b/app/src/types/ApiScope.ts new file mode 100644 index 00000000..88e9e60a --- /dev/null +++ b/app/src/types/ApiScope.ts @@ -0,0 +1,4 @@ +export type ApiScope = { + name: string; + userId: string; +}; diff --git a/app/src/types/CurrentUser.ts b/app/src/types/CurrentUser.ts index f22d6f7c..d64371ac 100644 --- a/app/src/types/CurrentUser.ts +++ b/app/src/types/CurrentUser.ts @@ -1,6 +1,10 @@ import jwt from 'jsonwebtoken'; +import { ApiScope } from './ApiScope'; +import { AuthType } from '../utils/enums/application'; + export type CurrentUser = { - authType: string; + authType: AuthType; + apiScope: ApiScope; tokenPayload: string | jwt.JwtPayload | null; }; diff --git a/app/src/types/index.ts b/app/src/types/index.ts index 5690d27f..4e25cc06 100644 --- a/app/src/types/index.ts +++ b/app/src/types/index.ts @@ -1,4 +1,5 @@ export type { Activity } from './Activity'; +export type { ApiScope } from './ApiScope'; export type { BringForward } from './BringForward'; export type { ChefsFormConfig, ChefsFormConfigData } from './ChefsFormConfig'; export type { ChefsSubmissionExport } from './ChefsSubmissionExport'; diff --git a/app/src/utils/enums/application.ts b/app/src/utils/enums/application.ts index 4db04a52..28a2192b 100644 --- a/app/src/utils/enums/application.ts +++ b/app/src/utils/enums/application.ts @@ -6,6 +6,13 @@ export enum AccessRole { PCNS_SUPERVISOR = 'PCNS_SUPERVISOR' } +export enum Action { + CREATE = 'create', + READ = 'read', + UPDATE = 'update', + DELETE = 'delete' +} + /** Current user authentication type */ export enum AuthType { /** OIDC JWT Authentication header provided */ @@ -38,3 +45,19 @@ export enum Regex { EMAIL = '^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]{2,})+$', PHONE_NUMBER = '^(\\+\\d{1,2}\\s?)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$' } + +export enum Resource { + DOCUMENT = 'document', + ENQUIRY = 'enquiry', + NOTE = 'note', + PERMIT = 'permit', + ROADMAP = 'roadmap', + SSO = 'sso', + SUBMISSION = 'submission', + USER = 'user' +} + +export enum Scope { + ALL = 'all', + SELF = 'self' +}