diff --git a/gradesa/src/backend/content-permissions.js b/gradesa/src/backend/content-permissions.js index eb8a91d..e7346a7 100644 --- a/gradesa/src/backend/content-permissions.js +++ b/gradesa/src/backend/content-permissions.js @@ -1,3 +1,19 @@ +export function canAccessAdminView(user, actAsValue) { + if (!user?.id) { + return false; + } + + if (user.is_superadmin) { + return true; + } + + if (!user.is_admin) { + return false; + } + + return actAsValue === "admin" || actAsValue === "superadmin"; +} + export function canDeleteOwnedContent(user, ownerId) { if (user?.is_superadmin) { return true; diff --git a/gradesa/src/backend/content-permissions.test.js b/gradesa/src/backend/content-permissions.test.js index 9231ce7..c1d1223 100644 --- a/gradesa/src/backend/content-permissions.test.js +++ b/gradesa/src/backend/content-permissions.test.js @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; -import { canDeleteOwnedContent } from "./content-permissions"; +import { + canAccessAdminView, + canDeleteOwnedContent, +} from "./content-permissions"; describe("canDeleteOwnedContent", () => { it("allows superadmins to delete any content", () => { @@ -29,3 +32,28 @@ describe("canDeleteOwnedContent", () => { ).toBe(false); }); }); + +describe("canAccessAdminView", () => { + it("allows superadmins in any selected view", () => { + const user = { id: 1, is_admin: true, is_superadmin: true }; + + expect(canAccessAdminView(user, "user")).toBe(true); + expect(canAccessAdminView(user, "admin")).toBe(true); + expect(canAccessAdminView(user, "superadmin")).toBe(true); + }); + + it("requires an admin role for non-superadmin users", () => { + const user = { id: 2, is_admin: true, is_superadmin: false }; + + expect(canAccessAdminView(user, "user")).toBe(false); + expect(canAccessAdminView(user, "admin")).toBe(true); + expect(canAccessAdminView(user, "superadmin")).toBe(true); + }); + + it("blocks non-admin users even when they are on an admin view", () => { + const user = { id: 3, is_admin: false, is_superadmin: false }; + + expect(canAccessAdminView(user, "admin")).toBe(false); + expect(canAccessAdminView(user, "superadmin")).toBe(false); + }); +}); diff --git a/gradesa/src/context/user.context.js b/gradesa/src/context/user.context.js index 12ec1c1..7d4b81f 100644 --- a/gradesa/src/context/user.context.js +++ b/gradesa/src/context/user.context.js @@ -4,6 +4,7 @@ import { createContext, useContext, useState, useEffect } from "react"; import { useRouter, usePathname } from "next/navigation"; import { useRequest } from "../shared/hooks/useRequest"; import useLocalStorage from "@/shared/utils/useLocalStorage"; +import { canAccessAdminView } from "@/backend/content-permissions"; export const STUDENT_OPTION = { label: "Student", value: "user" }; export const TEACHER_OPTION = { label: "Lehrer", value: "admin" }; @@ -176,20 +177,17 @@ export function useIsAdmin() { useEffect(() => { if (!auth.isAuthResolved) return; - if (!auth.user?.id) return; + if (!auth.isLoggedIn || !auth.user?.id) return; - const isAllowedAdminView = - actAs.value === "admin" || actAs.value === "superadmin"; - - if (!auth.user?.is_admin || !auth.isLoggedIn || !isAllowedAdminView) { - console.debug("Not authorized to view admin page", auth); + if (!canAccessAdminView(auth.user, actAs.value)) { + console.debug("Not authorized to view admin page", auth, actAs); router.replace("/"); } - }, [auth, router, pathname]); + }, [auth, actAs, router, pathname]); if (!auth.isAuthResolved) return undefined; - return auth.isLoggedIn && !!auth.user?.is_admin; + return auth.isLoggedIn && canAccessAdminView(auth.user, actAs.value); } /** @@ -244,11 +242,9 @@ export function useIsSuperAdmin() { // eslint-disable-next-line react-hooks/rules-of-hooks export function checkIsAdmin() { // eslint-disable-next-line react-hooks/rules-of-hooks - const { auth } = useUser(); + const { auth, actAs } = useUser(); - if (!auth.user?.id) return false; - if (!auth.isLoggedIn) return false; - if (!auth.user?.is_admin) return false; + if (!auth.isLoggedIn || !auth.user?.id) return false; - return true; + return canAccessAdminView(auth.user, actAs?.value); }