Files
the_order/packages/auth/src/eidas-entra-bridge.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

255 lines
7.4 KiB
TypeScript

/**
* eIDAS to Microsoft Entra VerifiedID Bridge
* Connects eIDAS verification to Microsoft Entra VerifiedID for credential issuance
*/
import { EIDASProvider, EIDASSignature } from './eidas';
import { EntraVerifiedIDClient, VerifiableCredentialRequest, ClaimValue } from './entra-verifiedid';
import { AzureLogicAppsClient } from './azure-logic-apps';
import { validateBase64File, FILE_SIZE_LIMITS, encodeFileToBase64, FileValidationOptions } from './file-utils';
export interface EIDASToEntraConfig {
entraVerifiedID: {
tenantId: string;
clientId: string;
clientSecret: string;
credentialManifestId: string;
};
eidas: {
providerUrl: string;
apiKey: string;
};
logicApps?: {
workflowUrl: string;
accessKey?: string;
managedIdentityClientId?: string;
};
}
export interface EIDASVerificationResult {
verified: boolean;
eidasSignature?: EIDASSignature;
certificateChain?: string[];
subject?: string;
issuer?: string;
validityPeriod?: {
notBefore: Date;
notAfter: Date;
};
}
/**
* Bridge between eIDAS verification and Microsoft Entra VerifiedID issuance
*/
export class EIDASToEntraBridge {
private eidasProvider: EIDASProvider;
private entraClient: EntraVerifiedIDClient;
private logicAppsClient?: AzureLogicAppsClient;
constructor(config: EIDASToEntraConfig) {
this.eidasProvider = new EIDASProvider({
providerUrl: config.eidas.providerUrl,
apiKey: config.eidas.apiKey,
});
this.entraClient = new EntraVerifiedIDClient({
tenantId: config.entraVerifiedID.tenantId,
clientId: config.entraVerifiedID.clientId,
clientSecret: config.entraVerifiedID.clientSecret,
credentialManifestId: config.entraVerifiedID.credentialManifestId,
});
if (config.logicApps) {
this.logicAppsClient = new AzureLogicAppsClient(config.logicApps);
}
}
/**
* Verify eIDAS signature and issue credential via Entra VerifiedID
*/
async verifyAndIssue(
document: string | Buffer,
userId: string,
userEmail: string,
pin?: string,
validationOptions?: FileValidationOptions
): Promise<{
verified: boolean;
credentialRequest?: {
requestId: string;
url: string;
qrCode?: string;
};
errors?: string[];
}> {
// Step 0: Validate and encode document if needed
let documentBase64: string;
if (document instanceof Buffer) {
// Encode buffer to base64
documentBase64 = encodeFileToBase64(document);
} else {
// Validate base64 string
const validation = validateBase64File(
document as string,
validationOptions || {
maxSize: FILE_SIZE_LIMITS.MEDIUM,
allowedMimeTypes: [
'application/pdf',
'image/png',
'image/jpeg',
'application/json',
'text/plain',
],
}
);
if (!validation.valid) {
return {
verified: false,
errors: validation.errors,
};
}
documentBase64 = document as string;
}
// Step 1: Request eIDAS signature
let eidasSignature: EIDASSignature;
try {
eidasSignature = await this.eidasProvider.requestSignature(documentBase64);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error('eIDAS signature request failed:', errorMessage);
return {
verified: false,
errors: [`eIDAS signature request failed: ${errorMessage}`],
};
}
// Step 2: Verify eIDAS signature
const verified = await this.eidasProvider.verifySignature(eidasSignature);
if (!verified) {
return {
verified: false,
errors: ['eIDAS signature verification failed'],
};
}
// Step 3: Trigger Logic App workflow if configured
if (this.logicAppsClient) {
try {
const documentId = document instanceof Buffer ? document.toString('base64').substring(0, 100) : (document as string).substring(0, 100);
await this.logicAppsClient.triggerEIDASVerification(
documentId,
userId,
this.eidasProvider['config'].providerUrl
);
} catch (error) {
console.warn('Logic App trigger failed (non-blocking):', error);
}
}
// Step 4: Issue credential via Entra VerifiedID
const credentialRequest: VerifiableCredentialRequest = {
claims: {
email: userEmail,
userId,
eidasVerified: true, // Boolean value (will be converted to string)
eidasCertificate: eidasSignature.certificate,
eidasSignatureTimestamp: eidasSignature.timestamp.toISOString(),
},
pin,
};
try {
const credentialResponse = await this.entraClient.issueCredential(credentialRequest);
return {
verified: true,
credentialRequest: {
requestId: credentialResponse.requestId,
url: credentialResponse.url,
qrCode: credentialResponse.qrCode,
},
};
} catch (error) {
console.error('Entra VerifiedID credential issuance failed:', error);
return { verified: true }; // eIDAS verified but credential issuance failed
}
}
/**
* Verify eIDAS signature only (without issuing credential)
*/
async verifyEIDAS(document: string): Promise<EIDASVerificationResult> {
try {
const signature = await this.eidasProvider.requestSignature(document);
const verified = await this.eidasProvider.verifySignature(signature);
if (!verified) {
return { verified: false };
}
// Extract certificate information (simplified - in production parse certificate)
return {
verified: true,
eidasSignature: signature,
subject: 'eIDAS Subject', // Would be extracted from certificate
issuer: 'eIDAS Issuer', // Would be extracted from certificate
validityPeriod: {
notBefore: signature.timestamp,
notAfter: new Date(signature.timestamp.getTime() + 365 * 24 * 60 * 60 * 1000), // 1 year default
},
};
} catch (error) {
console.error('eIDAS verification failed:', error);
return { verified: false };
}
}
/**
* Issue credential based on verified eIDAS signature
*/
async issueCredentialFromEIDAS(
eidasVerificationResult: EIDASVerificationResult,
userId: string,
userEmail: string,
additionalClaims?: Record<string, ClaimValue>,
pin?: string
): Promise<{
requestId: string;
url: string;
qrCode?: string;
}> {
if (!eidasVerificationResult.verified || !eidasVerificationResult.eidasSignature) {
throw new Error('eIDAS verification must be successful before issuing credential');
}
const claims: Record<string, ClaimValue> = {
email: userEmail,
userId,
eidasVerified: true, // Boolean value (will be converted to string)
eidasCertificate: eidasVerificationResult.eidasSignature.certificate,
eidasSignatureTimestamp: eidasVerificationResult.eidasSignature.timestamp.toISOString(),
...additionalClaims,
};
if (eidasVerificationResult.subject) {
claims.eidasSubject = eidasVerificationResult.subject;
}
if (eidasVerificationResult.issuer) {
claims.eidasIssuer = eidasVerificationResult.issuer;
}
const credentialRequest: VerifiableCredentialRequest = {
claims,
pin,
};
return await this.entraClient.issueCredential(credentialRequest);
}
}