/** * Authorization rules for credential issuance * Role-based issuance permissions, credential type restrictions, approval workflows */ import type { AuthUser } from './auth'; export interface CredentialIssuanceRule { credentialType: string | string[]; allowedRoles: string[]; requiresApproval?: boolean; approvalRoles?: string[]; requiresMultiSignature?: boolean; minSignatures?: number; maxIssuancesPerUser?: number; maxIssuancesPerDay?: number; conditions?: (user: AuthUser, context: Record) => Promise; } export interface ApprovalRequest { credentialId: string; requestedBy: string; requestedAt: Date; credentialType: string[]; subjectDid: string; status: 'pending' | 'approved' | 'rejected'; approvals: Array<{ approverId: string; approverRole: string; approvedAt: Date; signature?: string; }>; rejections: Array<{ rejectorId: string; rejectorRole: string; rejectedAt: Date; reason: string; }>; } /** * Default authorization rules */ export const DEFAULT_ISSUANCE_RULES: CredentialIssuanceRule[] = [ { credentialType: ['VerifiableCredential', 'IdentityCredential'], allowedRoles: ['admin', 'issuer', 'registrar'], requiresApproval: false, }, { credentialType: ['VerifiableCredential', 'JudicialCredential', 'RegistrarCredential'], allowedRoles: ['admin', 'judicial-admin'], requiresApproval: true, approvalRoles: ['chief-judge', 'judicial-council'], requiresMultiSignature: true, minSignatures: 2, }, { credentialType: ['VerifiableCredential', 'FinancialCredential', 'ComptrollerCredential'], allowedRoles: ['admin', 'financial-admin'], requiresApproval: true, approvalRoles: ['board', 'audit-committee'], requiresMultiSignature: true, minSignatures: 2, }, { credentialType: ['VerifiableCredential', 'DiplomaticCredential', 'LettersOfCredence'], allowedRoles: ['admin', 'diplomatic-admin'], requiresApproval: true, approvalRoles: ['grand-master', 'sovereign-council'], requiresMultiSignature: true, minSignatures: 3, }, ]; /** * Authorization service for credential issuance */ export class CredentialAuthorizationService { private rules: CredentialIssuanceRule[]; constructor(rules: CredentialIssuanceRule[] = DEFAULT_ISSUANCE_RULES) { this.rules = rules; } /** * Check if user can issue a credential type */ async canIssueCredential( user: AuthUser, credentialType: string | string[], context: Record = {} ): Promise<{ allowed: boolean; requiresApproval: boolean; reason?: string }> { const credentialTypes = Array.isArray(credentialType) ? credentialType : [credentialType]; // Find matching rule const rule = this.rules.find((r) => { const ruleTypes = Array.isArray(r.credentialType) ? r.credentialType : [r.credentialType]; return credentialTypes.some((type) => ruleTypes.includes(type)); }); if (!rule) { // Default: only admins can issue unknown credential types if (!user.roles?.includes('admin')) { return { allowed: false, requiresApproval: true, reason: 'No authorization rule found for credential type', }; } return { allowed: true, requiresApproval: false }; } // Check role permissions const hasRole = user.roles?.some((role) => rule.allowedRoles.includes(role)); if (!hasRole) { return { allowed: false, requiresApproval: false, reason: `User does not have required role. Required: ${rule.allowedRoles.join(' or ')}`, }; } // Check custom conditions if (rule.conditions) { const conditionResult = await rule.conditions(user, context); if (!conditionResult) { return { allowed: false, requiresApproval: false, reason: 'Custom authorization condition failed', }; } } return { allowed: true, requiresApproval: rule.requiresApproval || false, }; } /** * Get approval requirements for credential type */ getApprovalRequirements(credentialType: string | string[]): { requiresApproval: boolean; approvalRoles?: string[]; requiresMultiSignature?: boolean; minSignatures?: number; } { const credentialTypes = Array.isArray(credentialType) ? credentialType : [credentialType]; const rule = this.rules.find((r) => { const ruleTypes = Array.isArray(r.credentialType) ? r.credentialType : [r.credentialType]; return credentialTypes.some((type) => ruleTypes.includes(type)); }); if (!rule || !rule.requiresApproval) { return { requiresApproval: false }; } return { requiresApproval: true, approvalRoles: rule.approvalRoles, requiresMultiSignature: rule.requiresMultiSignature, minSignatures: rule.minSignatures, }; } /** * Validate approval request */ async validateApproval( approvalRequest: ApprovalRequest, approver: AuthUser ): Promise<{ valid: boolean; reason?: string }> { const requirements = this.getApprovalRequirements(approvalRequest.credentialType); if (!requirements.requiresApproval) { return { valid: false, reason: 'Credential type does not require approval' }; } // Check if approver has required role if (requirements.approvalRoles) { const hasApprovalRole = approver.roles?.some((role) => requirements.approvalRoles!.includes(role) ); if (!hasApprovalRole) { return { valid: false, reason: `Approver does not have required role. Required: ${requirements.approvalRoles.join(' or ')}`, }; } } // Check if already approved/rejected if (approvalRequest.status !== 'pending') { return { valid: false, reason: 'Approval request is not pending' }; } // Check for duplicate approval const alreadyApproved = approvalRequest.approvals.some( (a) => a.approverId === approver.id ); if (alreadyApproved) { return { valid: false, reason: 'Approver has already approved this request' }; } return { valid: true }; } /** * Check if approval request has sufficient approvals */ hasSufficientApprovals(approvalRequest: ApprovalRequest): boolean { const requirements = this.getApprovalRequirements(approvalRequest.credentialType); if (!requirements.requiresApproval) { return true; } if (requirements.requiresMultiSignature && requirements.minSignatures) { return approvalRequest.approvals.length >= requirements.minSignatures; } return approvalRequest.approvals.length > 0; } /** * Add custom rule */ addRule(rule: CredentialIssuanceRule): void { this.rules.push(rule); } /** * Remove rule */ removeRule(credentialType: string | string[]): void { const credentialTypes = Array.isArray(credentialType) ? credentialType : [credentialType]; this.rules = this.rules.filter((r) => { const ruleTypes = Array.isArray(r.credentialType) ? r.credentialType : [r.credentialType]; return !credentialTypes.some((type) => ruleTypes.includes(type)); }); } } /** * Get default authorization service */ let defaultAuthService: CredentialAuthorizationService | null = null; export function getAuthorizationService(): CredentialAuthorizationService { if (!defaultAuthService) { defaultAuthService = new CredentialAuthorizationService(); } return defaultAuthService; }