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 { MantineProvider } from "@mantine/core";
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
|
||||||
import Image from "next/image";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import logo from "@/assets/logos/logo.png";
|
|
||||||
import DashboardLayout from "@/modules/dashboard/components/DashboardLayout";
|
import DashboardLayout from "@/modules/dashboard/components/DashboardLayout";
|
||||||
import getUser from "@/modules/auth/actions/getMyDetailAction";
|
import getUser from "@/modules/auth/actions/getMyDetailAction";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
import getMyDetail from "../services/getMyDetail";
|
import getMyDetail from "../services/getMyDetail";
|
||||||
import AuthError from "../error/AuthError";
|
import AuthError from "../error/AuthError";
|
||||||
import BaseError from "@/core/error/BaseError";
|
|
||||||
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
|
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
|
||||||
import handleCatch from "@/modules/dashboard/utils/handleCatch";
|
import handleCatch from "@/modules/dashboard/utils/handleCatch";
|
||||||
import "server-only";
|
import "server-only";
|
||||||
import { cookies } from "next/headers";
|
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.
|
* 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() {
|
export default async function logout() {
|
||||||
cookies().delete("token");
|
cookies().delete("token");
|
||||||
redirect("/login");
|
redirect("/dashboard/login");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import getCurrentUser from "./getCurrentUser";
|
||||||
import "server-only";
|
import "server-only";
|
||||||
import getUserPermissions from "./getUserPermissions";
|
import getUserPermissions from "./getUserPermissions";
|
||||||
import { PermissionCode } from "@/modules/permission/data/initialPermissions";
|
import { PermissionCode } from "@/modules/permission/data/initialPermissions";
|
||||||
|
import AuthError from "../error/AuthError";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deprecated. Use dashboard service instead
|
* 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.
|
* @returns true if the user has the required permission, otherwise false.
|
||||||
*/
|
*/
|
||||||
export default async function checkPermission(
|
export default async function checkPermission(
|
||||||
permission: "guest-only" | "authenticated-only" | "*" | PermissionCode | (string & {}),
|
permission: PermissionCode,
|
||||||
currentUser?: Awaited<ReturnType<typeof getCurrentUser>>
|
currentUser?: Awaited<ReturnType<typeof getCurrentUser>>
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
// Allow if no specific permission is required.
|
// Allow if no specific permission is required.
|
||||||
if (permission === "*") return true;
|
if (permission === "*") return true;
|
||||||
|
|
||||||
|
|
@ -35,8 +37,14 @@ export default async function checkPermission(
|
||||||
if (user.roles.some((role) => role.code === "super-admin")) return true;
|
if (user.roles.some((role) => role.code === "super-admin")) return true;
|
||||||
|
|
||||||
// Aggregate all role codes and direct permissions into a set for efficient lookup.
|
// 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.
|
// Check if the user has the required permission.
|
||||||
return permissions.includes(permission);
|
return permissions.includes(permission);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof AuthError && e.errorCode === "INVALID_JWT_TOKEN") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { cache } from "react"
|
|
||||||
import "server-only"
|
import "server-only"
|
||||||
import getUserFromToken from "./getUserFromToken"
|
import getUserFromToken from "./getUserFromToken"
|
||||||
import { cookies, headers } from "next/headers"
|
import { cookies, headers } from "next/headers"
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,26 @@ import "server-only";
|
||||||
import getCurrentUser from "./getCurrentUser";
|
import getCurrentUser from "./getCurrentUser";
|
||||||
import db from "@/core/db";
|
import db from "@/core/db";
|
||||||
import getUserRoles from "./getUserRoles";
|
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();
|
const user = await getCurrentUser();
|
||||||
|
|
||||||
if (!user) return [];
|
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")) {
|
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) =>
|
...user.roles.flatMap((role) =>
|
||||||
role.permissions.map((permission) => permission.code)
|
role.permissions.map((permission) => permission.code)
|
||||||
),
|
),
|
||||||
...user.directPermissions.map((dp) => dp.code),
|
...user.directPermissions.map((dp) => dp.code),
|
||||||
]);
|
] as PermissionCode[]);
|
||||||
|
|
||||||
return Array.from(permissions);
|
return Array.from(permissions);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
|
import { RoleCode } from "@/modules/permission/data/initialRoles";
|
||||||
import getCurrentUser from "./getCurrentUser";
|
import getCurrentUser from "./getCurrentUser";
|
||||||
|
|
||||||
export default async function getUserRoles() {
|
export default async function getUserRoles(): Promise<RoleCode[]> {
|
||||||
const user = await getCurrentUser();
|
const user = await getCurrentUser();
|
||||||
|
|
||||||
if (!user) return [];
|
if (!user) return [];
|
||||||
|
|
||||||
const roles = user?.roles.map((role) => role.code);
|
const roles = user?.roles.map((role) => role.code);
|
||||||
|
|
||||||
return roles;
|
return roles as RoleCode[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,6 @@
|
||||||
"use server";
|
"use server";
|
||||||
import {
|
|
||||||
TbLayoutDashboard,
|
|
||||||
TbUsers,
|
|
||||||
TbNotebook,
|
|
||||||
TbShoppingBag,
|
|
||||||
TbPhotoFilled,
|
|
||||||
} from "react-icons/tb";
|
|
||||||
import SidebarMenu from "../types/SidebarMenu";
|
import SidebarMenu from "../types/SidebarMenu";
|
||||||
import "server-only";
|
import "server-only";
|
||||||
import getCurrentUser from "@/modules/auth/utils/getCurrentUser";
|
|
||||||
import ServerResponseAction from "../types/ServerResponseAction";
|
import ServerResponseAction from "../types/ServerResponseAction";
|
||||||
import handleCatch from "../utils/handleCatch";
|
import handleCatch from "../utils/handleCatch";
|
||||||
import getUserRoles from "@/modules/auth/utils/getUserRoles";
|
import getUserRoles from "@/modules/auth/utils/getUserRoles";
|
||||||
|
|
@ -38,6 +30,7 @@ export default async function getSidebarMenus(): Promise<
|
||||||
) ||
|
) ||
|
||||||
menuChild.allowedPermissions?.includes("*") ||
|
menuChild.allowedPermissions?.includes("*") ||
|
||||||
menuChild.allowedRoles?.includes("*")
|
menuChild.allowedRoles?.includes("*")
|
||||||
|
|| roles.includes("super-admin")
|
||||||
)
|
)
|
||||||
currentMenuChildren.push(menuChild);
|
currentMenuChildren.push(menuChild);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ const userMenuItems: UserMenuItem[] = [
|
||||||
label: "Logout",
|
label: "Logout",
|
||||||
icon: TbLogout,
|
icon: TbLogout,
|
||||||
color: "red",
|
color: "red",
|
||||||
href: "/logout",
|
href: "/dashboard/logout",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ const sidebarMenus: SidebarMenu[] = [
|
||||||
{
|
{
|
||||||
label: "Users",
|
label: "Users",
|
||||||
link: "/users",
|
link: "/users",
|
||||||
allowedPermissions: ["users.getAll"],
|
allowedPermissions: ["users.readAll"],
|
||||||
},
|
},
|
||||||
{ label: "Roles", link: "/roles", allowedRoles: ["super-admin"] },
|
{ 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;
|
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 {
|
export default interface SidebarMenu {
|
||||||
label: string;
|
label: string;
|
||||||
icon: React.FC<any> | string;
|
icon: React.FC<any> | string;
|
||||||
children?: {
|
children?: {
|
||||||
label: string;
|
label: string;
|
||||||
link: string;
|
link: string;
|
||||||
allowedPermissions?: string[],
|
allowedPermissions?: PermissionCode[],
|
||||||
allowedRoles?: string[],
|
allowedRoles?: RoleCode[],
|
||||||
}[];
|
}[];
|
||||||
color?: ThemeIconProps["color"];
|
color?: ThemeIconProps["color"];
|
||||||
allowedPermissions?: string[],
|
allowedPermissions?: PermissionCode[],
|
||||||
allowedRoles?: string[]
|
allowedRoles?: RoleCode[]
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +84,7 @@ const permissionData = [
|
||||||
}
|
}
|
||||||
] as const;
|
] 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">[];
|
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: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
corePlugins: { preflight: false },
|
// corePlugins: { preflight: false },
|
||||||
plugins: [],
|
plugins: [],
|
||||||
};
|
};
|
||||||
export default config;
|
export default config;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user