Files
the_order/packages/auth/src/entra-verifiedid-enhanced.ts
defiQUG 92cc41d26d Add Legal Office seal and complete Azure CDN deployment
- Add Legal Office of the Master seal (SVG design with Maltese Cross, scales of justice, legal scroll)
- Create legal-office-manifest-template.json for Legal Office credentials
- Update SEAL_MAPPING.md and DESIGN_GUIDE.md with Legal Office seal documentation
- Complete Azure CDN infrastructure deployment:
  - Resource group, storage account, and container created
  - 17 PNG seal files uploaded to Azure Blob Storage
  - All manifest templates updated with Azure URLs
  - Configuration files generated (azure-cdn-config.env)
- Add comprehensive Azure CDN setup scripts and documentation
- Fix manifest URL generation to prevent double slashes
- Verify all seals accessible via HTTPS
2025-11-12 22:03:42 -08:00

181 lines
5.3 KiB
TypeScript

/**
* Enhanced Microsoft Entra VerifiedID connector
* Adds retry logic, multi-manifest support, and improved error handling
*/
import { EntraVerifiedIDClient, EntraVerifiedIDConfig, VerifiableCredentialRequest, VerifiableCredentialResponse, VerifiableCredentialStatus, VerifiedCredential } from './entra-verifiedid';
export interface RetryConfig {
maxRetries?: number;
initialDelayMs?: number;
maxDelayMs?: number;
backoffMultiplier?: number;
retryableStatusCodes?: number[];
}
export interface MultiManifestConfig extends EntraVerifiedIDConfig {
manifests?: Record<string, string>; // manifest name -> manifest ID mapping
}
const DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 10000,
backoffMultiplier: 2,
retryableStatusCodes: [429, 500, 502, 503, 504],
};
/**
* Sleep utility for retry delays
*/
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Check if an error is retryable
*/
function isRetryableError(statusCode: number, retryableStatusCodes: number[]): boolean {
return retryableStatusCodes.includes(statusCode);
}
/**
* Enhanced Entra VerifiedID client with retry logic and multi-manifest support
*/
export class EnhancedEntraVerifiedIDClient extends EntraVerifiedIDClient {
private retryConfig: Required<RetryConfig>;
private manifests: Record<string, string>;
constructor(config: MultiManifestConfig, retryConfig?: RetryConfig) {
super(config);
this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...retryConfig };
this.manifests = config.manifests || {};
// Add default manifest if provided
if (config.credentialManifestId) {
this.manifests['default'] = config.credentialManifestId;
}
}
/**
* Get manifest ID by name, fallback to default
*/
private getManifestId(manifestName?: string): string {
if (manifestName && this.manifests[manifestName]) {
return this.manifests[manifestName];
}
if (this.manifests['default']) {
return this.manifests['default'];
}
throw new Error('No credential manifest ID configured');
}
/**
* Execute a request with retry logic
*/
private async executeWithRetry<T>(
operation: () => Promise<T>,
operationName: string
): Promise<T> {
let lastError: Error | null = null;
let delay = this.retryConfig.initialDelayMs;
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
// Check if error is retryable
const statusCode = (error as any)?.statusCode || (error as any)?.response?.status;
const isRetryable = statusCode && isRetryableError(statusCode, this.retryConfig.retryableStatusCodes);
// Don't retry on last attempt or if error is not retryable
if (attempt === this.retryConfig.maxRetries || !isRetryable) {
throw lastError;
}
// Wait before retrying
await sleep(Math.min(delay, this.retryConfig.maxDelayMs));
delay *= this.retryConfig.backoffMultiplier;
}
}
throw lastError || new Error(`${operationName} failed after ${this.retryConfig.maxRetries} retries`);
}
/**
* Issue credential with retry logic and manifest selection
*/
async issueCredential(
request: VerifiableCredentialRequest & { manifestName?: string }
): Promise<VerifiableCredentialResponse> {
const manifestId = this.getManifestId(request.manifestName);
// Create a modified request without manifestName
const { manifestName, ...credentialRequest } = request;
// Temporarily set manifest ID for this request
const originalManifestId = (this as any).config.credentialManifestId;
(this as any).config.credentialManifestId = manifestId;
try {
return await this.executeWithRetry(
() => super.issueCredential(credentialRequest),
'issueCredential'
);
} finally {
// Restore original manifest ID
(this as any).config.credentialManifestId = originalManifestId;
}
}
/**
* Get issuance status with retry logic
*/
async getIssuanceStatus(requestId: string): Promise<VerifiableCredentialStatus> {
return this.executeWithRetry(
() => super.getIssuanceStatus(requestId),
'getIssuanceStatus'
);
}
/**
* Verify credential with retry logic
*/
async verifyCredential(credential: VerifiedCredential): Promise<boolean> {
return this.executeWithRetry(
() => super.verifyCredential(credential),
'verifyCredential'
);
}
/**
* Create presentation request with retry logic and manifest selection
*/
async createPresentationRequest(
manifestName?: string,
callbackUrl?: string
): Promise<VerifiableCredentialResponse> {
const manifestId = this.getManifestId(manifestName);
return this.executeWithRetry(
() => super.createPresentationRequest(manifestId, callbackUrl),
'createPresentationRequest'
);
}
/**
* Register a new manifest
*/
registerManifest(name: string, manifestId: string): void {
this.manifests[name] = manifestId;
}
/**
* Get all registered manifests
*/
getManifests(): Record<string, string> {
return { ...this.manifests };
}
}