177 lines
7.7 KiB
TypeScript
177 lines
7.7 KiB
TypeScript
import type { AuthInfo } from "../types";
|
|
|
|
const legacyToModuleScopes: Record<string, string> = {
|
|
"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<string, string[]> = {
|
|
"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"
|
|
];
|