export const ADMIN_ROLE = 'admin' export const ADMIN_PANEL_PERMISSIONS = [ 'users.view', 'users.manage', 'platform.configure', 'platform.view_logs', 'plugins.configure', 'plugins.install', 'notices.publish', ] as const export type AccessAudience = 'all' | 'admin' | 'user' export type AccessAudienceInput = AccessAudience | readonly AccessAudience[] export interface RoleInfo { id?: number slug: string name?: string color?: string permissions?: readonly string[] | null project_scope?: 'all' | 'assigned' | string plugin_access?: readonly string[] | 'all' | null } export interface PermissionUser { role?: string | null role_obj?: RoleInfo | null roleObj?: RoleInfo | null permissions?: readonly string[] | null } export interface AccessPolicy { audience?: AccessAudienceInput visibleFor?: AccessAudienceInput requiresAuth?: boolean requiresAdmin?: boolean permissions?: readonly string[] anyPermissions?: readonly string[] plugin?: string } /** An object that carries an access rule in its nested `access` policy. */ export interface AccessControlled { access?: AccessPolicy } export function getRoleInfo(user: PermissionUser | null | undefined): RoleInfo | null { return user?.roleObj ?? user?.role_obj ?? null } export function isAdminRole(role: string | null | undefined): boolean { return role === ADMIN_ROLE } export function isAdminUser(user: PermissionUser | null | undefined): boolean { return isAdminRole(user?.role) || getRoleInfo(user)?.slug === ADMIN_ROLE } export function getAccessAudience(user: PermissionUser | null | undefined): Exclude { return isAdminUser(user) ? 'admin' : 'user' } export function getUserPermissions(user: PermissionUser | null | undefined): string[] { const rolePermissions = getRoleInfo(user)?.permissions if (rolePermissions?.length) return [...rolePermissions] return [...(user?.permissions ?? [])] } export function hasAllPermissions( user: PermissionUser | null | undefined, permissions: readonly string[] = [], ): boolean { if (permissions.length === 0) return true if (isAdminUser(user)) return true const granted = new Set(getUserPermissions(user)) return permissions.every(permission => granted.has(permission)) } export function hasAnyPermission( user: PermissionUser | null | undefined, permissions: readonly string[] = [], ): boolean { if (permissions.length === 0) return true if (isAdminUser(user)) return true const granted = new Set(getUserPermissions(user)) return permissions.some(permission => granted.has(permission)) } export function canAccessAdmin(user: PermissionUser | null | undefined): boolean { return isAdminUser(user) || hasAnyPermission(user, ADMIN_PANEL_PERMISSIONS) } export function canAccessPlugin( user: PermissionUser | null | undefined, pluginName: string | null | undefined, ): boolean { if (!pluginName || isAdminUser(user)) return true const access = getRoleInfo(user)?.plugin_access // As on the platform: a value that is not null, 'all' or a list allows no plugin. if (access == null || access === 'all') return true return Array.isArray(access) && access.includes(pluginName) } export function canAccessByPolicy( user: PermissionUser | null | undefined, policy: AccessPolicy = {}, isAuthenticated = user !== null && user !== undefined, ): boolean { if (policy.requiresAuth && !isAuthenticated) return false if (policy.requiresAdmin && !isAdminUser(user)) return false if (!audienceAllowsUser(policy.visibleFor ?? policy.audience, user)) return false if (!hasAllPermissions(user, policy.permissions)) return false if (!hasAnyPermission(user, policy.anyPermissions)) return false if (policy.plugin && !canAccessPlugin(user, policy.plugin)) return false return true } function audienceAllowsUser( audience: AccessAudienceInput | undefined, user: PermissionUser | null | undefined, ): boolean { if (audience === undefined) return true const allowed = Array.isArray(audience) ? audience : [audience] if (allowed.includes('all')) return true return allowed.includes(getAccessAudience(user)) }