Introduce permission catalog, PermissionsGuard, JWT/me payload with role+permissions, and gate all module controllers. Admins CRUD accepts role/permissions with safety rules; migrate existing users to super_admin. Co-authored-by: Cursor <cursoragent@cursor.com>
103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
/** Permission codes for SuperAdmin panel RBAC */
|
|
export const PERMISSIONS = [
|
|
'sms:read',
|
|
'sms:write',
|
|
'servers:read',
|
|
'servers:write',
|
|
'plugins:read',
|
|
'plugins:write',
|
|
'support:read',
|
|
'support:write',
|
|
'hamkar:read',
|
|
'hamkar:write',
|
|
'khanegi:read',
|
|
'khanegi:write',
|
|
'admins:read',
|
|
'admins:write',
|
|
'audit:read',
|
|
'settings:write',
|
|
] as const;
|
|
|
|
export type PermissionCode = (typeof PERMISSIONS)[number];
|
|
|
|
export const PERMISSION_SET = new Set<string>(PERMISSIONS);
|
|
|
|
export type PermissionModule = {
|
|
key: string;
|
|
label: string;
|
|
read?: PermissionCode;
|
|
write?: PermissionCode;
|
|
};
|
|
|
|
export const PERMISSION_MODULES: PermissionModule[] = [
|
|
{ key: 'sms', label: 'پیامک', read: 'sms:read', write: 'sms:write' },
|
|
{ key: 'servers', label: 'سرور', read: 'servers:read', write: 'servers:write' },
|
|
{ key: 'plugins', label: 'افزونه', read: 'plugins:read', write: 'plugins:write' },
|
|
{ key: 'support', label: 'پشتیبانی', read: 'support:read', write: 'support:write' },
|
|
{ key: 'hamkar', label: 'همکار', read: 'hamkar:read', write: 'hamkar:write' },
|
|
{ key: 'khanegi', label: 'خانگی', read: 'khanegi:read', write: 'khanegi:write' },
|
|
{ key: 'admins', label: 'مدیران', read: 'admins:read', write: 'admins:write' },
|
|
{ key: 'audit', label: 'لاگ', read: 'audit:read' },
|
|
{ key: 'settings', label: 'تنظیمات حساس', write: 'settings:write' },
|
|
];
|
|
|
|
export function parsePermissions(raw: unknown): PermissionCode[] {
|
|
if (!Array.isArray(raw)) return [];
|
|
const out: PermissionCode[] = [];
|
|
for (const item of raw) {
|
|
const code = String(item || '').trim();
|
|
if (PERMISSION_SET.has(code)) out.push(code as PermissionCode);
|
|
}
|
|
return [...new Set(out)];
|
|
}
|
|
|
|
/** write implies matching read for the same module */
|
|
export function normalizePermissionList(codes: string[]): PermissionCode[] {
|
|
const set = new Set<PermissionCode>();
|
|
for (const code of parsePermissions(codes)) {
|
|
set.add(code);
|
|
if (code.endsWith(':write')) {
|
|
const read = code.replace(/:write$/, ':read');
|
|
if (PERMISSION_SET.has(read)) set.add(read as PermissionCode);
|
|
}
|
|
}
|
|
return [...set];
|
|
}
|
|
|
|
export function isFullAccessRole(role: string | null | undefined) {
|
|
return role === 'super_admin';
|
|
}
|
|
|
|
export function userHasPermission(
|
|
user: { role?: string | null; permissions?: unknown },
|
|
code: PermissionCode | string,
|
|
): boolean {
|
|
if (isFullAccessRole(user.role)) return true;
|
|
const list = parsePermissions(user.permissions);
|
|
return list.includes(code as PermissionCode);
|
|
}
|
|
|
|
export function userHasAnyPermission(
|
|
user: { role?: string | null; permissions?: unknown },
|
|
codes: Array<PermissionCode | string>,
|
|
): boolean {
|
|
if (isFullAccessRole(user.role)) return true;
|
|
return codes.some((code) => userHasPermission(user, code));
|
|
}
|
|
|
|
export function userHasAllPermissions(
|
|
user: { role?: string | null; permissions?: unknown },
|
|
codes: Array<PermissionCode | string>,
|
|
): boolean {
|
|
if (isFullAccessRole(user.role)) return true;
|
|
return codes.every((code) => userHasPermission(user, code));
|
|
}
|
|
|
|
export function productReadPermission(product: string): PermissionCode {
|
|
return product === 'hamkar' ? 'hamkar:read' : 'khanegi:read';
|
|
}
|
|
|
|
export function productWritePermission(product: string): PermissionCode {
|
|
return product === 'hamkar' ? 'hamkar:write' : 'khanegi:write';
|
|
}
|