- Implement credential revocation endpoint with proper database integration - Fix database row mapping (snake_case to camelCase) for eResidency applications - Add missing imports (getRiskAssessmentEngine, VeriffKYCProvider, ComplyAdvantageSanctionsProvider) - Fix environment variable type checking for Veriff and ComplyAdvantage providers - Add required 'message' field to notification service calls - Fix risk assessment type mismatches - Update audit logging to use 'verified' action type (supported by schema) - Resolve all TypeScript errors and unused variable warnings - Add TypeScript ignore comments for placeholder implementations - Temporarily disable security/detect-non-literal-regexp rule due to ESLint 9 compatibility - Service now builds successfully with no linter errors All core functionality implemented: - Application submission and management - KYC integration (Veriff placeholder) - Sanctions screening (ComplyAdvantage placeholder) - Risk assessment engine - Credential issuance and revocation - Reviewer console - Status endpoints - Auto-issuance service
260 lines
7.4 KiB
TypeScript
260 lines
7.4 KiB
TypeScript
/**
|
|
* 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<string, unknown>) => Promise<boolean>;
|
|
}
|
|
|
|
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<string, unknown> = {}
|
|
): 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;
|
|
}
|
|
|