From 0827fc0a8de266fc3578961a59ea96902b26498d Mon Sep 17 00:00:00 2001 From: Kyle Morel Date: Tue, 17 Sep 2024 23:04:11 -0700 Subject: [PATCH] Add beforeEnter accessHandler Checks user access to a route and redirects if necessary --- frontend/src/router/index.ts | 106 ++++++++++-------- frontend/src/utils/enums/application.ts | 1 - .../src/views/user/UserManagementView.vue | 6 +- 3 files changed, 64 insertions(+), 49 deletions(-) diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index e463bc25..20e0cfbc 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -6,11 +6,45 @@ import { useAppStore, useAuthNStore, useAuthZStore } from '@/store'; import { NavigationPermission } from '@/store/authzStore'; import { RouteName, StorageKey } from '@/utils/enums/application'; -import type { RouteRecordRaw } from 'vue-router'; +import type { RouteLocationNormalizedGeneric, RouteRecordRaw } from 'vue-router'; + +/** + * @function accessHandler + * Checks for user access to the requested route and redirect if necessary + * @param {object} to The route to navigate to + * @returns {object} a Vue route + */ +function accessHandler(to: RouteLocationNormalizedGeneric) { + const access = to.meta.access as NavigationPermission[]; + + if (access && access.length) { + const authzStore = useAuthZStore(); + if (!authzStore.canNavigate(access)) { + return { name: RouteName.NOT_FOUND }; + } + } +} + +/** + * @function bootstrap + * Obtains user permissions if authenticated before hard navigation + * @returns {object} a Vue route + */ +async function bootstrap() { + const authnStore = useAuthNStore(); + const authzStore = useAuthZStore(); + + const { getIsAuthenticated } = storeToRefs(authnStore); + + if (getIsAuthenticated.value && !authzStore.getGroups.length) { + const permissions = await yarsService.getPermissions(); + authzStore.setPermissions(permissions.data); + } +} /** * @function createProps - * Parses the route query and params to generate vue props + * Parses the route query and params to generate Vue props * @param {object} route The route object * @returns {object} a Vue props object */ @@ -22,40 +56,19 @@ const routes: Array = [ { path: '/', component: () => import('@/views/GenericView.vue'), - beforeEnter: async (to) => { - // Get user groups if we haven't before any routing - const authnStore = useAuthNStore(); - const { getIsAuthenticated } = storeToRefs(authnStore); - const authzStore = useAuthZStore(); - if (getIsAuthenticated.value && !authzStore.getGroups.length) { - const permissions = await yarsService.getPermissions(); - authzStore.setPermissions(permissions.data); - } - - // Check access - if (to.meta.access) { - if (!authzStore.canNavigate(Array.isArray(to.meta.access) ? to.meta.access : [to.meta.access])) { - useRouter().replace({ name: RouteName.NOT_FOUND }); - return; - } - } - }, + beforeEnter: [bootstrap, accessHandler], children: [ { path: '/', name: RouteName.HOME, component: () => import('@/views/HomeView.vue') }, - { - path: '/soon', - name: RouteName.COMING_SOON, - component: () => import('@/views/ComingSoon.vue') - }, { path: '/developer', name: RouteName.DEVELOPER, component: () => import('@/views/DeveloperView.vue'), - meta: { requiresAuth: true, access: NavigationPermission.DEVELOPER } + beforeEnter: accessHandler, + meta: { requiresAuth: true, access: [NavigationPermission.DEVELOPER] } }, { path: '/forbidden', @@ -64,16 +77,20 @@ const routes: Array = [ }, { path: '/housing', - component: () => import('@/views/GenericView.vue'), - meta: { requiresAuth: true }, children: [ { path: '', name: RouteName.HOUSING, component: () => import('../views/housing/HousingView.vue'), + beforeEnter: () => { + const authzStore = useAuthZStore(); + if (!authzStore.canNavigate(NavigationPermission.HOUSING)) { + useRouter().replace({ name: RouteName.HOUSING_SUBMISSIONS }); + } + }, meta: { - access: NavigationPermission.HOUSING + access: [NavigationPermission.HOUSING] } }, { @@ -83,18 +100,20 @@ const routes: Array = [ path: '', name: RouteName.HOUSING_ENQUIRY, component: () => import('../views/housing/enquiry/EnquiryView.vue'), + beforeEnter: accessHandler, props: createProps, meta: { - access: NavigationPermission.HOUSING_ENQUIRY + access: [NavigationPermission.HOUSING_ENQUIRY] } }, { path: 'intake', name: RouteName.HOUSING_ENQUIRY_INTAKE, component: () => import('@/views/housing/enquiry/EnquiryIntakeView.vue'), + beforeEnter: accessHandler, props: createProps, meta: { - access: NavigationPermission.HOUSING_INTAKE + access: [NavigationPermission.HOUSING_INTAKE] } } ] @@ -106,18 +125,20 @@ const routes: Array = [ path: '', name: RouteName.HOUSING_SUBMISSION, component: () => import('@/views/housing/submission/SubmissionView.vue'), + beforeEnter: accessHandler, props: createProps, meta: { - access: NavigationPermission.HOUSING_SUBMISSION + access: [NavigationPermission.HOUSING_SUBMISSION] } }, { path: 'intake', name: RouteName.HOUSING_SUBMISSION_INTAKE, component: () => import('@/views/housing/submission/SubmissionIntakeView.vue'), + beforeEnter: accessHandler, props: createProps, meta: { - access: NavigationPermission.HOUSING_INTAKE + access: [NavigationPermission.HOUSING_INTAKE] } } ] @@ -131,6 +152,7 @@ const routes: Array = [ { path: '', component: () => import('@/views/housing/project/ProjectListView.vue'), + beforeEnter: accessHandler, meta: { access: [NavigationPermission.HOUSING_STATUS_TRACKER] }, @@ -139,6 +161,7 @@ const routes: Array = [ { path: ':submissionId', component: () => import('@/views/housing/project/ProjectView.vue'), + beforeEnter: accessHandler, meta: { access: [NavigationPermission.HOUSING_STATUS_TRACKER] }, @@ -151,6 +174,7 @@ const routes: Array = [ path: 'submissions', name: RouteName.HOUSING_SUBMISSIONS, component: () => import('@/views/housing/SubmissionsView.vue'), + beforeEnter: accessHandler, meta: { access: [NavigationPermission.HOUSING_SUBMISSIONS, NavigationPermission.HOUSING_SUBMISSIONS_SUB] } @@ -159,13 +183,12 @@ const routes: Array = [ path: '/guide', name: RouteName.HOUSING_GUIDE, component: () => import('@/views/ComingSoon.vue'), - meta: { access: NavigationPermission.HOUSING } + meta: { access: [NavigationPermission.HOUSING] } } ] }, { path: '/oidc', - component: () => import('@/views/GenericView.vue'), children: [ { path: 'callback', @@ -190,7 +213,9 @@ const routes: Array = [ { path: '/user', name: RouteName.USER_MANAGEMENT, - component: () => import('@/views/user/UserManagementView.vue') + component: () => import('@/views/user/UserManagementView.vue'), + beforeEnter: accessHandler, + meta: { requiresAuth: true, access: [NavigationPermission.HOUSING_USER_MANAGEMENT] } }, { path: '/:pathMatch(.*)*', @@ -204,7 +229,6 @@ const routes: Array = [ export default function getRouter() { const appStore = useAppStore(); const authService = new AuthService(); - const authzStore = useAuthZStore(); const router = createRouter({ history: createWebHistory(), @@ -245,14 +269,6 @@ export default function getRouter() { return; } } - - // Check for reroutes - if (to.name === RouteName.HOUSING) { - if (!authzStore.canNavigate(NavigationPermission.HOUSING)) { - router.replace({ name: RouteName.HOUSING_SUBMISSIONS }); - return; - } - } }); router.afterEach(() => { diff --git a/frontend/src/utils/enums/application.ts b/frontend/src/utils/enums/application.ts index a04f7c26..47952a3d 100644 --- a/frontend/src/utils/enums/application.ts +++ b/frontend/src/utils/enums/application.ts @@ -55,7 +55,6 @@ export enum Regex { } export enum RouteName { - COMING_SOON = 'coming_soon', DEVELOPER = 'developer', FORBIDDEN = 'forbidden', HOME = 'home', diff --git a/frontend/src/views/user/UserManagementView.vue b/frontend/src/views/user/UserManagementView.vue index bcf8b8c8..c4a2a860 100644 --- a/frontend/src/views/user/UserManagementView.vue +++ b/frontend/src/views/user/UserManagementView.vue @@ -64,7 +64,8 @@ function assignUserStatus(request: UserAccessRequest) { return request; } -function onDenyRevocation(user: UserAccessRequest) { +//function onDenyRevocation(user: UserAccessRequest) { +function onDenyRevocation() { confirm.require({ message: 'This user’s revocation request will be denied, and they will remain an authorized user.', header: 'Deny revocation request', @@ -73,9 +74,8 @@ function onDenyRevocation(user: UserAccessRequest) { rejectLabel: 'Cancel', accept: async () => { try { - console.log('TODO'); + // TODO // const response = await accessRequestService.denyAccessRequest(user.accessRequest?.accessRequestId as string); - // if (response) { // usersAndAccessRequests.value = usersAndAccessRequests.value.filter( // (userAccessRequest) => userAccessRequest.user.userId !== response.data.userId