Added permission and role type safety

This commit is contained in:
sianida26 2024-03-29 00:54:26 +07:00
parent 56054ab408
commit a48e4353a5
14 changed files with 72 additions and 71 deletions

View File

@ -1,8 +1,5 @@
import { AppShell, AppShellHeader, Burger, MantineProvider } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import Image from "next/image";
import { MantineProvider } from "@mantine/core";
import React from "react";
import logo from "@/assets/logos/logo.png";
import DashboardLayout from "@/modules/dashboard/components/DashboardLayout";
import getUser from "@/modules/auth/actions/getMyDetailAction";
import { redirect } from "next/navigation";

View File

@ -2,12 +2,10 @@
import getMyDetail from "../services/getMyDetail";
import AuthError from "../error/AuthError";
import BaseError from "@/core/error/BaseError";
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
import handleCatch from "@/modules/dashboard/utils/handleCatch";
import "server-only";
import { cookies } from "next/headers";
import getUserFromToken from "../utils/getUserFromToken";
/**
* Asynchronously retrieves the authenticated user's details from a server-side context in a Next.js application.

View File

@ -12,5 +12,5 @@ import "server-only";
*/
export default async function logout() {
cookies().delete("token");
redirect("/login");
redirect("/dashboard/login");
}

View File

@ -2,6 +2,7 @@ import getCurrentUser from "./getCurrentUser";
import "server-only";
import getUserPermissions from "./getUserPermissions";
import { PermissionCode } from "@/modules/permission/data/initialPermissions";
import AuthError from "../error/AuthError";
/**
* Deprecated. Use dashboard service instead
@ -12,9 +13,10 @@ import { PermissionCode } from "@/modules/permission/data/initialPermissions";
* @returns true if the user has the required permission, otherwise false.
*/
export default async function checkPermission(
permission: "guest-only" | "authenticated-only" | "*" | PermissionCode | (string & {}),
permission: PermissionCode,
currentUser?: Awaited<ReturnType<typeof getCurrentUser>>
): Promise<boolean> {
try {
// Allow if no specific permission is required.
if (permission === "*") return true;
@ -35,8 +37,14 @@ export default async function checkPermission(
if (user.roles.some((role) => role.code === "super-admin")) return true;
// Aggregate all role codes and direct permissions into a set for efficient lookup.
const permissions = await getUserPermissions()
const permissions = await getUserPermissions();
// Check if the user has the required permission.
return permissions.includes(permission);
} catch (e) {
if (e instanceof AuthError && e.errorCode === "INVALID_JWT_TOKEN") {
return false;
}
throw e;
}
}

View File

@ -1,4 +1,3 @@
import { cache } from "react"
import "server-only"
import getUserFromToken from "./getUserFromToken"
import { cookies, headers } from "next/headers"

View File

@ -2,23 +2,26 @@ import "server-only";
import getCurrentUser from "./getCurrentUser";
import db from "@/core/db";
import getUserRoles from "./getUserRoles";
import { PermissionCode } from "@/modules/permission/data/initialPermissions";
export default async function getUserPermissions() {
export default async function getUserPermissions(): Promise<PermissionCode[]> {
const user = await getCurrentUser();
if (!user) return [];
//Retrieve all permissions if the user is super admin
if ((await getUserRoles()).includes("super-admin")) {
return (await db.permission.findMany()).map((permission) => permission.code);
return (await db.permission.findMany()).map(
(permission) => permission.code
) as PermissionCode[];
}
const permissions = new Set<string>([
const permissions = new Set<PermissionCode>([
...user.roles.flatMap((role) =>
role.permissions.map((permission) => permission.code)
),
...user.directPermissions.map((dp) => dp.code),
]);
] as PermissionCode[]);
return Array.from(permissions);
}

View File

@ -1,11 +1,12 @@
import { RoleCode } from "@/modules/permission/data/initialRoles";
import getCurrentUser from "./getCurrentUser";
export default async function getUserRoles() {
export default async function getUserRoles(): Promise<RoleCode[]> {
const user = await getCurrentUser();
if (!user) return [];
const roles = user?.roles.map((role) => role.code);
return roles;
return roles as RoleCode[];
}

View File

@ -1,14 +1,6 @@
"use server";
import {
TbLayoutDashboard,
TbUsers,
TbNotebook,
TbShoppingBag,
TbPhotoFilled,
} from "react-icons/tb";
import SidebarMenu from "../types/SidebarMenu";
import "server-only";
import getCurrentUser from "@/modules/auth/utils/getCurrentUser";
import ServerResponseAction from "../types/ServerResponseAction";
import handleCatch from "../utils/handleCatch";
import getUserRoles from "@/modules/auth/utils/getUserRoles";
@ -38,6 +30,7 @@ export default async function getSidebarMenus(): Promise<
) ||
menuChild.allowedPermissions?.includes("*") ||
menuChild.allowedRoles?.includes("*")
|| roles.includes("super-admin")
)
currentMenuChildren.push(menuChild);
}

View File

@ -14,7 +14,7 @@ const userMenuItems: UserMenuItem[] = [
label: "Logout",
icon: TbLogout,
color: "red",
href: "/logout",
href: "/dashboard/logout",
},
];

View File

@ -14,7 +14,7 @@ const sidebarMenus: SidebarMenu[] = [
{
label: "Users",
link: "/users",
allowedPermissions: ["users.getAll"],
allowedPermissions: ["users.readAll"],
},
{ label: "Roles", link: "/roles", allowedRoles: ["super-admin"] },
{
@ -24,24 +24,6 @@ const sidebarMenus: SidebarMenu[] = [
},
],
},
{
label: "Reseller Office 365",
icon: "TbBuildingStore",
color: "red",
allowedPermissions: ["*"],
children: [
{
label: "My Request Links",
link: "/reseller-office-365/request",
allowedRoles: ["reseller-office-365"]
},
{
label: "Process Request Link",
link: "/reseller-office-365/list",
allowedRoles: ["admin-reseller-office-365"]
}
]
}
];
export default sidebarMenus;

View File

@ -1,13 +1,16 @@
import { PermissionCode } from "@/modules/permission/data/initialPermissions";
import { RoleCode } from "@/modules/permission/data/initialRoles";
export default interface SidebarMenu {
label: string;
icon: React.FC<any> | string;
children?: {
label: string;
link: string;
allowedPermissions?: string[],
allowedRoles?: string[],
allowedPermissions?: PermissionCode[],
allowedRoles?: RoleCode[],
}[];
color?: ThemeIconProps["color"];
allowedPermissions?: string[],
allowedRoles?: string[]
allowedPermissions?: PermissionCode[],
allowedRoles?: RoleCode[]
}

View File

@ -84,7 +84,7 @@ const permissionData = [
}
] as const;
export type PermissionCode = (typeof permissionData)[number]['code'];
export type PermissionCode = (typeof permissionData)[number]['code'] | "*" | "authenticated-only" | "guest-only";
const exportedPermissionData = permissionData as unknown as Omit<Permission, "id">[];

View File

@ -0,0 +1,17 @@
import { Role } from "@prisma/client";
const roleData = [
{
code: "super-admin",
description:
"Has full access to the system and can manage all features and settings",
isActive: true,
name: "Super Admin",
},
] as const;
export type RoleCode = (typeof roleData)[number]["code"] | "*";
const exportedRoleData = roleData as unknown as Omit<Role, "id">;
export default exportedRoleData;

View File

@ -9,7 +9,7 @@ const config: Config = {
theme: {
extend: {},
},
corePlugins: { preflight: false },
// corePlugins: { preflight: false },
plugins: [],
};
export default config;