import type { AuthInfo } from "../types"; const legacyToModuleScopes: Record = { "admin:users:read": "access:membership:read", "admin:users:create": "access:membership:create", "admin:users:update": "access:membership:update", "admin:users:suspend": "access:membership:update", "admin:groups:read": "access:group:read", "admin:groups:write": "access:group:write", "admin:groups:manage_members": "access:group:manage_members", "admin:roles:read": "access:role:read", "admin:roles:write": "access:role:write", "admin:roles:assign": "access:role:assign", "admin:api_keys:read": "access:api_key:read", "admin:api_keys:create": "access:api_key:create", "admin:api_keys:revoke": "access:api_key:revoke", "admin:settings:read": "access:setting:read", "admin:settings:write": "access:setting:write", "admin:policies:read": "access:policy:read", "admin:policies:write": "access:policy:write", "system:tenants:read": "access:tenant:read", "system:tenants:create": "access:tenant:create", "system:tenants:update": "access:tenant:update", "system:tenants:suspend": "access:tenant:suspend", "system:accounts:read": "access:account:read", "system:accounts:create": "access:account:create", "system:accounts:update": "access:account:update", "system:accounts:suspend": "access:account:suspend", "system:roles:read": "access:system_role:read", "system:roles:write": "access:system_role:write", "system:roles:assign": "access:system_role:assign", "system:access:read": "access:system_role:read", "system:access:assign": "access:system_role:assign", "system:audit:read": "access:audit:read", "system:audit:evidence:export": "audit:system_evidence:export", "system:settings:read": "access:system_setting:read", "system:settings:write": "access:system_setting:write", "system:maintenance:access": "access:maintenance:access", "system:governance:read": "access:governance:read", "system:governance:write": "access:governance:write", "campaign:read": "campaigns:campaign:read", "campaign:create": "campaigns:campaign:create", "campaign:update": "campaigns:campaign:update", "campaign:copy": "campaigns:campaign:copy", "campaign:archive": "campaigns:campaign:archive", "campaign:delete": "campaigns:campaign:delete", "campaign:share": "campaigns:campaign:share", "campaign:validate": "campaigns:campaign:validate", "campaign:build": "campaigns:campaign:build", "campaign:review": "campaigns:campaign:review", "campaign:send_test": "campaigns:campaign:send_test", "campaign:queue": "campaigns:campaign:queue", "campaign:control": "campaigns:campaign:control", "campaign:send": "campaigns:campaign:send", "campaign:retry": "campaigns:campaign:retry", "campaign:reconcile": "campaigns:campaign:reconcile", "recipients:read": "campaigns:recipient:read", "recipients:write": "campaigns:recipient:write", "recipients:import": "campaigns:recipient:import", "recipients:export": "campaigns:recipient:export", "reports:read": "campaigns:report:read", "reports:export": "campaigns:report:export", "reports:send": "campaigns:report:send", "files:read": "files:file:read", "files:download": "files:file:download", "files:upload": "files:file:upload", "files:organize": "files:file:organize", "files:share": "files:file:share", "files:delete": "files:file:delete", "files:admin": "files:file:admin", "mail_servers:read": "mail:profile:read", "mail_servers:use": "mail:profile:use", "mail_servers:test": "mail:profile:test", "mail_servers:mailbox_read": "mail:mailbox:read", "mail_servers:write": "mail:profile:write", "mail_servers:manage_credentials": "mail:secret:manage" }; const moduleToLegacyScopes = Object.fromEntries(Object.entries(legacyToModuleScopes).map(([legacy, module]) => [module, legacy])); const moduleSystemScopes = new Set( Object.entries(legacyToModuleScopes) .filter(([legacy]) => legacy.startsWith("system:")) .map(([, module]) => module) ); const moduleTenantScopes = new Set( Object.entries(legacyToModuleScopes) .filter(([legacy]) => !legacy.startsWith("system:")) .map(([, module]) => module) ); const legacyAliases: Record = { "campaign:write": ["campaign:create", "campaign:update", "campaign:copy", "campaign:archive", "campaign:delete", "campaign:share", "recipients:read", "recipients:write", "recipients:import"], "attachments:read": ["files:read", "files:download"], "attachments:write": ["files:upload", "files:organize", "files:share", "files:delete"], "admin:users": ["admin:users:read", "admin:users:create", "admin:users:update", "admin:users:suspend", "admin:groups:read", "admin:groups:write", "admin:groups:manage_members", "admin:roles:read", "admin:roles:write", "admin:roles:assign"], "admin:users:write": ["admin:users:create", "admin:users:update", "admin:users:suspend", "admin:roles:assign", "admin:groups:manage_members"], "admin:api_keys:write": ["admin:api_keys:create", "admin:api_keys:revoke"], "admin:settings": ["admin:settings:read", "admin:settings:write", "admin:api_keys:read", "admin:api_keys:create", "admin:api_keys:revoke"], "system:tenants:write": ["system:tenants:create", "system:tenants:update", "system:tenants:suspend"], "system:access:write": ["system:access:assign", "system:roles:assign", "system:accounts:create", "system:accounts:update", "system:accounts:suspend"] }; function compatibleScopes(scope: string): string[] { const mapped = legacyToModuleScopes[scope] ?? moduleToLegacyScopes[scope]; return mapped ? [scope, mapped] : [scope]; } function primitiveScopeGrants(granted: string, required: string): boolean { if (granted === required) return true; if (legacyAliases[granted]?.some((alias) => primitiveScopeGrants(alias, required))) return true; if (granted === "*") return true; if (granted === "tenant:*") { if (moduleSystemScopes.has(required)) return false; return required === "tenant:*" || moduleTenantScopes.has(required) || !required.startsWith("system:"); } if (granted === "system:*") return required.startsWith("system:") || moduleSystemScopes.has(required); if (granted.endsWith(":*") && required.startsWith(granted.slice(0, -1))) return true; return false; } export function scopeGrants(granted: string, required: string): boolean { for (const grantedCandidate of compatibleScopes(granted)) { for (const requiredCandidate of compatibleScopes(required)) { if (primitiveScopeGrants(grantedCandidate, requiredCandidate)) return true; } } return false; } export function hasTenantWildcard(scopes: string[]): boolean { return scopes.includes("*") || scopes.includes("tenant:*"); } export function hasScope(auth: AuthInfo | null | undefined, required: string): boolean { return Boolean(auth?.scopes?.some((scope) => scopeGrants(scope, required))); } export function hasAnyScope(auth: AuthInfo | null | undefined, required: string[]): boolean { return required.some((scope) => hasScope(auth, scope)); } export const adminReadScopes = [ "admin:users:read", "admin:groups:read", "admin:roles:read", "admin:api_keys:read", "admin:settings:read", "admin:policies:read", "admin:module:read", "admin:module:write", "mail:profile:read", "mail_servers:read", "audit:read", "system:tenants:read", "system:accounts:read", "system:access:read", "system:roles:read", "system:audit:read", "system:settings:read", "system:governance:read", "access:tenant:read", "access:account:read", "access:system_role:read", "access:audit:read", "access:system_setting:read", "access:system_credential:read", "access:credential:read", "access:service_account:read", "approvals:workspace:admin", "access:governance:read", "views:definition:read", "views:assignment:read", "views:system_definition:read", "views:system_assignment:read" ];