From 9b80c72ae34b8538f7894c7a2ee6715fc3fc8daa Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Tue, 15 Mar 2022 09:28:56 +0100 Subject: [PATCH] cleanup and finish route perms (untested) Signed-off-by: MiniDigger --- frontend-new/TODO.md | 4 +- .../src/composables/useErrorRedirect.ts | 4 +- frontend-new/src/composables/usePerm.ts | 31 +++++ frontend-new/src/modules/auth.ts | 120 +++++++++++------- 4 files changed, 106 insertions(+), 53 deletions(-) create mode 100644 frontend-new/src/composables/usePerm.ts diff --git a/frontend-new/TODO.md b/frontend-new/TODO.md index 7cc08c1b..bc1450a9 100644 --- a/frontend-new/TODO.md +++ b/frontend-new/TODO.md @@ -7,8 +7,8 @@ Stuff that needs to be done before I consider this a successful POC - [x] i18n - [x] api calls - [x] seo -- [ ] route perms (partly done, project and global perm missing) -- [x] error pages (status code is already done) +- [x] route perms (needs testing tho) +- [x] error pages (needs design but what doesnt?) - [ ] snackbar - [ ] some basic pages - [ ] maybe deployment alongside the existing frontend? (server is working now) diff --git a/frontend-new/src/composables/useErrorRedirect.ts b/frontend-new/src/composables/useErrorRedirect.ts index 33f60f5e..2ee2fcc5 100644 --- a/frontend-new/src/composables/useErrorRedirect.ts +++ b/frontend-new/src/composables/useErrorRedirect.ts @@ -1,6 +1,6 @@ -import { RouteLocationNormalized } from "vue-router"; +import { RouteLocationNormalized, RouteLocationRaw } from "vue-router"; -export function useErrorRedirect(currentRoute: RouteLocationNormalized, status: number, msg?: string) { +export function useErrorRedirect(currentRoute: RouteLocationNormalized, status: number, msg?: string): RouteLocationRaw { return { name: "error", params: { diff --git a/frontend-new/src/composables/usePerm.ts b/frontend-new/src/composables/usePerm.ts new file mode 100644 index 00000000..6e0bcf6e --- /dev/null +++ b/frontend-new/src/composables/usePerm.ts @@ -0,0 +1,31 @@ +/** + * Checks if the supplier permission has all named permissions. + * @param namedPermission perms required + */ +import { useAuthStore } from "~/store/auth"; +import { useBackendDataStore } from "~/store/backendData"; +import { NamedPermission } from "~/types/enums"; + +export function toNamedPermission(perms: string[]): NamedPermission[] { + return perms.map((p) => NamedPermission[p as keyof typeof NamedPermission]); +} + +export function hasPerms(...namedPermission: NamedPermission[]): boolean { + const perms = useAuthStore().routePermissions; + if (!perms) return false; + const _perms = BigInt("0b" + perms); + let result = true; + const registeredPerms = useBackendDataStore().permissions; + if (!registeredPerms) { + throw new Error("No perms from backend?"); + } + for (const np of namedPermission) { + const perm = registeredPerms.get(np); + if (!perm) { + throw new Error(namedPermission + " is not valid"); + } + const val = BigInt("0b" + perm.permission.toString(2)); + result = result && (_perms & val) === val; + } + return result; +} diff --git a/frontend-new/src/modules/auth.ts b/frontend-new/src/modules/auth.ts index f7eb9584..04bd7eff 100644 --- a/frontend-new/src/modules/auth.ts +++ b/frontend-new/src/modules/auth.ts @@ -4,11 +4,15 @@ import { useCookies } from "~/composables/useCookies"; import { set, unset } from "~/composables/useResReq"; import { authLog, routePermLog } from "~/composables/useLog"; import { useAuthStore } from "~/store/auth"; -import { RouteLocationNormalized } from "vue-router"; -import { Context } from "vite-ssr/vue"; +import { RouteLocationNormalized, RouteLocationRaw } from "vue-router"; +import { Context, useContext } from "vite-ssr/vue"; import { useApi } from "~/composables/useApi"; -import { UserPermissions } from "hangar-api"; +import { PermissionCheck, UserPermissions } from "hangar-api"; import { useErrorRedirect } from "~/composables/useErrorRedirect"; +import { hasPerms, toNamedPermission } from "~/composables/usePerm"; +import { NamedPermission, PermissionType } from "~/types/enums"; +import { handleRequestError } from "~/composables/useErrorHandling"; +import { useI18n } from "vue-i18n"; export const install: UserModule = async ({ request, response, router, redirect }) => { router.beforeEach(async (to, from, next) => { @@ -20,7 +24,7 @@ export const install: UserModule = async ({ request, response, router, redirect await handleLogin(request, response); await loadPerms(to); - const result = handleRoutePerms(to); + const result = await handleRoutePerms(to); if (result) { next(result); } else { @@ -72,62 +76,81 @@ async function loadPerms(to: RouteLocationNormalized) { authStore.setRoutePerms(null); } -function handleRoutePerms(to: RouteLocationNormalized) { +async function handleRoutePerms(to: RouteLocationNormalized) { const authStore = useAuthStore(); routePermLog("navigate to " + String(to.name) + ", meta:", to.meta); - if (to.meta.requireLoggedIn === true) { - routePermLog("route requireLoggedIn"); - const result = checkLogin(authStore, to, 401, "You must be logged in to perform this action"); + for (const key in handlers) { + if (!to.meta[key]) continue; + const handler = handlers[key]; + const result = await handler(authStore, to); if (result) return result; } +} - if (to.meta.requireNotLoggedIn === true) { - routePermLog("route requireNotLoggedIn"); - if (authStore.authenticated) { - return { - path: "/", - }; +type handlersType = { [key: string]: (authStore: ReturnType, to: RouteLocationNormalized) => Promise }; +const handlers: handlersType = { + requireLoggedIn: requireLoggedIn, + requireNotLoggedIn: requireNotLoggedIn, + requireCurrentUser: requireCurrentUser, + requireGlobalPerm: requireGlobalPerm, + requireProjectPerm: requireProjectPerm, +}; + +async function requireLoggedIn(authStore: ReturnType, to: RouteLocationNormalized) { + routePermLog("route requireLoggedIn"); + const result = checkLogin(authStore, to, 401, "You must be logged in to perform this action"); + if (result) return result; +} + +async function requireNotLoggedIn(authStore: ReturnType, to: RouteLocationNormalized) { + routePermLog("route requireNotLoggedIn"); + if (authStore.authenticated) { + return { + path: "/", + }; + } +} + +async function requireCurrentUser(authStore: ReturnType, to: RouteLocationNormalized) { + routePermLog("route requireCurrentUser"); + if (!to.params.user) { + throw new TypeError("Must have 'user' as a route param to use CurrentUser"); + } + const result = checkLogin(authStore, to, 404); + if (result) return result; + if (!hasPerms(NamedPermission.EDIT_ALL_USER_SETTINGS)) { + if (to.params.user !== authStore.user?.name) { + return useErrorRedirect(to, 403); } } +} - if (to.meta.requireCurrentUser === true) { - routePermLog("route requireCurrentUser"); - if (!to.params.user) { - throw new TypeError("Must have 'user' as a route param to use CurrentUser"); - } - const result = checkLogin(authStore, to, 404); - if (result) return result; - // TODO implement this - // if (!$perms.canEditAllUserSettings) { - // if (to.params.user !== authStore.user?.name) { - // return useErrorRedirect(to, 403); - // } - // } +async function requireGlobalPerm(authStore: ReturnType, to: RouteLocationNormalized) { + routePermLog("route requireGlobalPerm", to.meta.requireGlobalPerm); + const result = checkLogin(authStore, to, 403); + if (result) return result; + const check = await useApi("permissions/hasAll", true, "get", { + permissions: toNamedPermission(to.meta.requireGlobalPerm as string[]), + }).catch((e) => handleRequestError(e, useContext(), useI18n())); + if (check && (check.type !== PermissionType.GLOBAL || !check.result)) { + return useErrorRedirect(to, 404, "Not found"); } +} - if (to.meta.requireGlobalPerm) { - routePermLog("route requireGlobalPerm", to.meta.requireGlobalPerm); - const result = checkLogin(authStore, to, 403); - if (result) return result; - // TODO implement this +async function requireProjectPerm(authStore: ReturnType, to: RouteLocationNormalized) { + routePermLog("route requireProjectPerm", to.meta.requireProjectPerm); + if (!to.params.user || !to.params.project) { + throw new Error("Can't use this decorator on a route without `author` and `slug` path params"); } - - if (to.meta.requireProjectPerm) { - routePermLog("route requireProjectPerm", to.meta.requireProjectPerm); - if (!to.params.user || !to.params.project) { - throw new Error("Can't use this decorator on a route without `author` and `slug` path params"); - } - const result = checkLogin(authStore, to, 404); - if (result) return result; - if (!authStore.routePermissions) { - return useErrorRedirect(to, 404); - } - // TODO implement this - // if (!$util.hasPerms(...(to.meta.requireProjectPerm as string[]))) { - // return useErrorRedirect(to, 404); - // } + const result = checkLogin(authStore, to, 404); + if (result) return result; + if (!authStore.routePermissions) { + return useErrorRedirect(to, 404); + } + routePermLog("check has perms", to.meta.requireProjectPerm); + if (!hasPerms(...toNamedPermission(to.meta.requireProjectPerm as string[]))) { + return useErrorRedirect(to, 404); } - return; } function checkLogin(authStore: ReturnType, to: RouteLocationNormalized, status: number, msg?: string) { @@ -135,5 +158,4 @@ function checkLogin(authStore: ReturnType, to: RouteLocatio routePermLog("not logged in!"); return useErrorRedirect(to, status, msg); } - return; }