Added permission and role type safety
This commit is contained in:
parent
56054ab408
commit
a48e4353a5
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -12,5 +12,5 @@ import "server-only";
|
|||
*/
|
||||
export default async function logout() {
|
||||
cookies().delete("token");
|
||||
redirect("/login");
|
||||
redirect("/dashboard/login");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,31 +13,38 @@ 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> {
|
||||
// Allow if no specific permission is required.
|
||||
if (permission === "*") return true;
|
||||
try {
|
||||
// Allow if no specific permission is required.
|
||||
if (permission === "*") return true;
|
||||
|
||||
// Retrieve current user if not provided.
|
||||
const user = currentUser ?? (await getCurrentUser());
|
||||
// Retrieve current user if not provided.
|
||||
const user = currentUser ?? (await getCurrentUser());
|
||||
|
||||
// Handle non-authenticated users.
|
||||
if (!user) {
|
||||
return permission === "guest-only";
|
||||
// Handle non-authenticated users.
|
||||
if (!user) {
|
||||
return permission === "guest-only";
|
||||
}
|
||||
|
||||
// Allow authenticated users if the permission is 'authenticated-only'.
|
||||
if (permission === "authenticated-only") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Short-circuit for super-admin role to allow all permissions.
|
||||
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();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Allow authenticated users if the permission is 'authenticated-only'.
|
||||
if (permission === "authenticated-only") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Short-circuit for super-admin role to allow all permissions.
|
||||
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()
|
||||
|
||||
// Check if the user has the required permission.
|
||||
return permissions.includes(permission);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { cache } from "react"
|
||||
import "server-only"
|
||||
import getUserFromToken from "./getUserFromToken"
|
||||
import { cookies, headers } from "next/headers"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
//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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const userMenuItems: UserMenuItem[] = [
|
|||
label: "Logout",
|
||||
icon: TbLogout,
|
||||
color: "red",
|
||||
href: "/logout",
|
||||
href: "/dashboard/logout",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
11
src/modules/dashboard/types/SidebarMenu.d.ts
vendored
11
src/modules/dashboard/types/SidebarMenu.d.ts
vendored
|
|
@ -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[]
|
||||
}
|
||||
|
|
@ -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">[];
|
||||
|
||||
|
|
|
|||
17
src/modules/permission/data/initialRoles.ts
Normal file
17
src/modules/permission/data/initialRoles.ts
Normal 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;
|
||||
|
|
@ -9,7 +9,7 @@ const config: Config = {
|
|||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
corePlugins: { preflight: false },
|
||||
// corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
};
|
||||
export default config;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user