From 7d8583775af7fc8bb5f3e212fe9da26ffbfe2b71 Mon Sep 17 00:00:00 2001 From: georgelgeback Date: Sat, 11 Apr 2026 16:46:18 +0000 Subject: [PATCH 1/7] Update permission system and permission wall to the new system without JWT embedded permissions --- src/app/admin/AdminSidebar.tsx | 51 +- src/app/admin/members/MemberEditForm.tsx | 5 +- src/app/admin/members/page.tsx | 4 +- src/components/PermissionWall.tsx | 45 +- src/lib/auth.ts | 85 +- src/locales/en/main.json | 1000 +++++++++++----------- src/locales/sv/main.json | 1000 +++++++++++----------- 7 files changed, 1118 insertions(+), 1072 deletions(-) diff --git a/src/app/admin/AdminSidebar.tsx b/src/app/admin/AdminSidebar.tsx index ba247631..20dc17c0 100644 --- a/src/app/admin/AdminSidebar.tsx +++ b/src/app/admin/AdminSidebar.tsx @@ -44,8 +44,9 @@ import { import { useTranslation } from "react-i18next"; import Link from "next/link"; import type { ForwardRefExoticComponent, RefAttributes } from "react"; -import { useAuthState, type RequiredPermission } from "@/lib/auth"; +import { usePermissionsState, type RequiredPermission } from "@/lib/auth"; import { ActionEnum, TargetEnum } from "@/api"; +import { Skeleton } from "@/components/ui/skeleton"; type AdminSidebarEntry = { title: string; @@ -229,20 +230,42 @@ const groups: AdminGroup[] = [ export function AdminSidebar() { const { t } = useTranslation(); - const permissions = useAuthState().getPermissions(); + const { permissions, isLoading, isError } = usePermissionsState(); - // Filter out groups and entries the user doesn't have permission to see - const visibleGroups = groups - .map((group) => { - return { - ...group, - // Also filter out entries - entries: group.entries.filter((item) => - permissions.hasRequiredPermissions(item.permissions ?? []), - ), - }; - }) - .filter((group) => group.entries.length > 0); + if (isLoading) { + return ( + + +

+ {t("admin:title")} +

+
+ +
+ + + + +
+
+
+ ); + } + + // Filter out groups and entries the user doesn't have permission to see. + // On permission fetch errors we fail closed and show no admin entries. + const visibleGroups = isError + ? [] + : groups + .map((group) => { + return { + ...group, + entries: group.entries.filter((item) => + permissions.hasRequiredPermissions(item.permissions ?? []), + ), + }; + }) + .filter((group) => group.entries.length > 0); return ( diff --git a/src/app/admin/members/MemberEditForm.tsx b/src/app/admin/members/MemberEditForm.tsx index 446730f3..039cdc0e 100644 --- a/src/app/admin/members/MemberEditForm.tsx +++ b/src/app/admin/members/MemberEditForm.tsx @@ -21,7 +21,7 @@ import { toast } from "sonner"; import { AdminChooseMultPosts } from "@/widgets/AdminChooseMultPosts"; import { Pen, Save } from "lucide-react"; import UserDetailsCard from "@/components/UserDetailsCard"; -import { useAuthState } from "@/lib/auth"; +import { usePermissions } from "@/lib/auth"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; @@ -73,8 +73,7 @@ export default function UserPostsEditForm({ const { t } = useTranslation("admin"); const [confirmOpen, setConfirmOpen] = useState(false); const [isEditing, setIsEditing] = useState(false); - const auth = useAuthState(); - const permissions = auth.getPermissions(); + const permissions = usePermissions(); const queryClient = useQueryClient(); const form = useForm({ diff --git a/src/app/admin/members/page.tsx b/src/app/admin/members/page.tsx index 0a780e99..4ecc4c33 100644 --- a/src/app/admin/members/page.tsx +++ b/src/app/admin/members/page.tsx @@ -37,7 +37,7 @@ import { useState, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { LoadingErrorCard } from "@/components/LoadingErrorCard"; -import { useAuthState, type RequiredPermission } from "@/lib/auth"; +import { usePermissions, type RequiredPermission } from "@/lib/auth"; import { ActionEnum, TargetEnum } from "@/api"; import MemberEditForm from "./MemberEditForm"; @@ -53,7 +53,7 @@ export default function MembersPage() { } = useQuery({ ...adminGetAllUsersOptions(), }); - const permissions = useAuthState().getPermissions(); + const permissions = usePermissions(); const hasManageUserPerms = permissions.hasRequiredPermissions([ [ActionEnum.MANAGE, TargetEnum.USER], ] as RequiredPermission[]); diff --git a/src/components/PermissionWall.tsx b/src/components/PermissionWall.tsx index 68c1a053..24485743 100644 --- a/src/components/PermissionWall.tsx +++ b/src/components/PermissionWall.tsx @@ -1,37 +1,39 @@ "use client"; -import { useAuthState, type RequiredPermission } from "@/lib/auth"; +import { usePermissionsState, type RequiredPermission } from "@/lib/auth"; import { useRouter } from "next/navigation"; import type { ReactNode } from "react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; +import { LoadingErrorCard } from "@/components/LoadingErrorCard"; import Obfuscate from "react-obfuscate"; function PermissionDenied() { const { t } = useTranslation("main"); const router = useRouter(); + return (
-

+
{t("permission-wall.stop")} -

-

+

+
{t("permission-wall.subtitle")} -

-

+

+
{t("permission-wall.message")} -

+

{t("permission-wall.contact")} -

+
. -

-

+

+
{t("permission-wall.quote")} -{" "} {t("permission-wall.quote_author")} -

+