docs: archive entra materials and simplify deployment docs

This commit is contained in:
defiQUG
2026-04-18 12:05:55 -07:00
parent 0f600e6a31
commit bbb6ce6a6c
256 changed files with 4188 additions and 3881 deletions
+1 -2
View File
@@ -10,7 +10,7 @@ This directory contains all backend microservices for The Order platform. Each s
## Available Services
### Identity Service (`identity/`)
- **Purpose**: Digital identity, verifiable credentials, Entra VerifiedID
- **Purpose**: Digital identity, verifiable credentials, identity management
- **Port**: 4002
- **Features**: eIDAS/DID, credential issuance, identity verification
- **Documentation**: [Identity Service README](identity/README.md)
@@ -150,4 +150,3 @@ Services communicate via:
---
**Last Updated**: 2025-01-27
-369
View File
@@ -1,369 +0,0 @@
/**
* Microsoft Entra VerifiedID integration for Identity Service
*/
import { FastifyInstance } from 'fastify';
import {
EntraVerifiedIDClient,
VerifiableCredentialRequest,
} from '@the-order/auth';
import { EnhancedEntraVerifiedIDClient } from '@the-order/auth';
import { EIDASToEntraBridge } from '@the-order/auth';
import { getEnv } from '@the-order/shared';
import { createVerifiableCredential } from '@the-order/database';
import {
entraApiRequests,
entraApiRequestDuration,
entraApiErrors,
entraCredentialsIssued,
entraIssuanceDuration,
entraIssuanceRetries,
entraCredentialsVerified,
entraVerificationDuration,
entraActiveRequests,
} from '@the-order/monitoring';
import { registerEntraRateLimit } from '@the-order/shared';
/**
* Initialize Enhanced Entra VerifiedID client with multi-manifest support
*/
export function createEntraClient(): EnhancedEntraVerifiedIDClient | null {
const env = getEnv();
if (!env.ENTRA_TENANT_ID || !env.ENTRA_CLIENT_ID || !env.ENTRA_CLIENT_SECRET) {
return null;
}
// Parse manifests from environment variable if provided
let manifests: Record<string, string> | undefined;
if (env.ENTRA_MANIFESTS) {
try {
manifests = JSON.parse(env.ENTRA_MANIFESTS);
} catch (error) {
console.warn('Failed to parse ENTRA_MANIFESTS, using default manifest only', error);
}
}
// Add default manifest if provided
if (env.ENTRA_CREDENTIAL_MANIFEST_ID) {
manifests = manifests || {};
manifests['default'] = env.ENTRA_CREDENTIAL_MANIFEST_ID;
}
return new EnhancedEntraVerifiedIDClient({
tenantId: env.ENTRA_TENANT_ID,
clientId: env.ENTRA_CLIENT_ID,
clientSecret: env.ENTRA_CLIENT_SECRET,
credentialManifestId: env.ENTRA_CREDENTIAL_MANIFEST_ID,
manifests,
logoUri: env.ENTRA_CREDENTIAL_LOGO_URI,
backgroundColor: env.ENTRA_CREDENTIAL_BG_COLOR,
textColor: env.ENTRA_CREDENTIAL_TEXT_COLOR,
}, {
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 10000,
backoffMultiplier: 2,
});
}
/**
* Initialize eIDAS to Entra bridge
*/
export function createEIDASToEntraBridge(): EIDASToEntraBridge | null {
const env = getEnv();
if (
!env.ENTRA_TENANT_ID ||
!env.ENTRA_CLIENT_ID ||
!env.ENTRA_CLIENT_SECRET ||
!env.EIDAS_PROVIDER_URL ||
!env.EIDAS_API_KEY
) {
return null;
}
return new EIDASToEntraBridge({
entraVerifiedID: {
tenantId: env.ENTRA_TENANT_ID,
clientId: env.ENTRA_CLIENT_ID,
clientSecret: env.ENTRA_CLIENT_SECRET,
credentialManifestId: env.ENTRA_CREDENTIAL_MANIFEST_ID || '',
},
eidas: {
providerUrl: env.EIDAS_PROVIDER_URL,
apiKey: env.EIDAS_API_KEY,
},
logicApps: env.AZURE_LOGIC_APPS_WORKFLOW_URL
? {
workflowUrl: env.AZURE_LOGIC_APPS_WORKFLOW_URL,
accessKey: env.AZURE_LOGIC_APPS_ACCESS_KEY,
managedIdentityClientId: env.AZURE_LOGIC_APPS_MANAGED_IDENTITY_CLIENT_ID,
}
: undefined,
});
}
/**
* Register Entra VerifiedID routes
*/
export async function registerEntraRoutes(server: FastifyInstance): Promise<void> {
const entraClient = createEntraClient();
const eidasBridge = createEIDASToEntraBridge();
if (!entraClient) {
server.log.warn('Microsoft Entra VerifiedID not configured - routes will not be available');
return;
}
// Register Entra-specific rate limiting
await registerEntraRateLimit(server);
// Issue credential via Entra VerifiedID
server.post(
'/vc/issue/entra',
{
schema: {
description: 'Issue verifiable credential via Microsoft Entra VerifiedID',
tags: ['credentials', 'entra'],
body: {
type: 'object',
required: ['claims'],
properties: {
claims: {
type: 'object',
description: 'Credential claims (values can be string, number, boolean, or null)',
additionalProperties: {
oneOf: [
{ type: 'string' },
{ type: 'number' },
{ type: 'boolean' },
{ type: 'null' },
],
},
},
pin: {
type: 'string',
description: 'Optional PIN for credential issuance',
},
callbackUrl: {
type: 'string',
format: 'uri',
description: 'Optional callback URL for issuance status',
},
},
},
response: {
200: {
type: 'object',
properties: {
requestId: { type: 'string' },
url: { type: 'string' },
qrCode: { type: 'string' },
},
},
},
},
},
async (request, reply) => {
const body = request.body as VerifiableCredentialRequest & { manifestName?: string };
const startTime = Date.now();
entraActiveRequests.inc({ operation: 'issueCredential' });
try {
entraApiRequests.inc({ operation: 'issueCredential', status: 'attempt' });
const credentialResponse = await entraClient.issueCredential(body);
// Record success metrics
const duration = (Date.now() - startTime) / 1000;
entraIssuanceDuration.observe({ manifest_name: body.manifestName || 'default' }, duration);
entraApiRequestDuration.observe({ operation: 'issueCredential' }, duration);
entraCredentialsIssued.inc({ manifest_name: body.manifestName || 'default', status: 'success' });
entraApiRequests.inc({ operation: 'issueCredential', status: 'success' });
return reply.status(200).send(credentialResponse);
} catch (error) {
// Record error metrics
const duration = (Date.now() - startTime) / 1000;
entraApiRequestDuration.observe({ operation: 'issueCredential' }, duration);
entraApiErrors.inc({
operation: 'issueCredential',
error_type: error instanceof Error ? error.constructor.name : 'Unknown',
status_code: (error as any)?.statusCode || 0,
});
entraCredentialsIssued.inc({ manifest_name: body.manifestName || 'default', status: 'error' });
entraApiRequests.inc({ operation: 'issueCredential', status: 'error' });
return reply.status(500).send({
error: 'Failed to issue credential',
message: error instanceof Error ? error.message : String(error),
});
} finally {
entraActiveRequests.dec({ operation: 'issueCredential' });
}
}
);
// Verify credential via Entra VerifiedID
server.post(
'/vc/verify/entra',
{
schema: {
description: 'Verify verifiable credential via Microsoft Entra VerifiedID',
tags: ['credentials', 'entra'],
body: {
type: 'object',
required: ['credential'],
properties: {
credential: {
type: 'object',
description: 'Verifiable credential to verify',
},
},
},
response: {
200: {
type: 'object',
properties: {
verified: { type: 'boolean' },
},
},
},
},
},
async (request, reply) => {
const body = request.body as { credential: unknown };
const startTime = Date.now();
entraActiveRequests.inc({ operation: 'verifyCredential' });
try {
entraApiRequests.inc({ operation: 'verifyCredential', status: 'attempt' });
const verified = await entraClient.verifyCredential(
body.credential as Parameters<typeof entraClient.verifyCredential>[0]
);
// Record success metrics
const duration = (Date.now() - startTime) / 1000;
entraVerificationDuration.observe(duration);
entraApiRequestDuration.observe({ operation: 'verifyCredential' }, duration);
entraCredentialsVerified.inc({ result: verified ? 'verified' : 'not_verified' });
entraApiRequests.inc({ operation: 'verifyCredential', status: 'success' });
return reply.status(200).send({ verified });
} catch (error) {
// Record error metrics
const duration = (Date.now() - startTime) / 1000;
entraApiRequestDuration.observe({ operation: 'verifyCredential' }, duration);
entraApiErrors.inc({
operation: 'verifyCredential',
error_type: error instanceof Error ? error.constructor.name : 'Unknown',
status_code: (error as any)?.statusCode || 0,
});
entraCredentialsVerified.inc({ result: 'error' });
entraApiRequests.inc({ operation: 'verifyCredential', status: 'error' });
return reply.status(500).send({
error: 'Failed to verify credential',
message: error instanceof Error ? error.message : String(error),
});
} finally {
entraActiveRequests.dec({ operation: 'verifyCredential' });
}
}
);
// eIDAS verification with Entra VerifiedID issuance
if (eidasBridge) {
server.post(
'/eidas/verify-and-issue',
{
schema: {
description: 'Verify eIDAS signature and issue credential via Entra VerifiedID',
tags: ['eidas', 'entra'],
body: {
type: 'object',
required: ['document', 'userId', 'userEmail'],
properties: {
document: {
type: 'string',
description: 'Document to verify and sign (base64-encoded string or buffer will be auto-encoded)',
},
userId: {
type: 'string',
description: 'User ID',
},
userEmail: {
type: 'string',
format: 'email',
description: 'User email',
},
pin: {
type: 'string',
description: 'Optional PIN for credential issuance',
},
},
},
response: {
200: {
type: 'object',
properties: {
verified: { type: 'boolean' },
credentialRequest: {
type: 'object',
properties: {
requestId: { type: 'string' },
url: { type: 'string' },
qrCode: { type: 'string' },
},
},
},
},
},
},
},
async (request, reply) => {
const body = request.body as {
document: string;
userId: string;
userEmail: string;
pin?: string;
};
try {
const result = await eidasBridge.verifyAndIssue(
body.document,
body.userId,
body.userEmail,
body.pin
);
if (result.verified && result.credentialRequest) {
// Save credential request to database
await createVerifiableCredential({
credential_id: result.credentialRequest.requestId,
issuer_did: `did:web:${getEnv().ENTRA_TENANT_ID}.verifiedid.msidentity.com`,
subject_did: body.userId,
credential_type: ['VerifiableCredential', 'EntraVerifiedIDCredential'],
credential_subject: {
email: body.userEmail,
userId: body.userId,
eidasVerified: true,
},
issuance_date: new Date(),
});
}
return reply.status(200).send(result);
} catch (error) {
return reply.status(500).send({
error: 'Failed to verify eIDAS and issue credential',
message: error instanceof Error ? error.message : String(error),
});
}
}
);
}
}
-267
View File
@@ -1,267 +0,0 @@
/**
* Microsoft Entra VerifiedID webhook/callback handler
* Handles status updates from Entra VerifiedID service
*/
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { getEnv } from '@the-order/shared';
import { updateVerifiableCredential, getVerifiableCredentialById } from '@the-order/database';
import { getEventBus, CredentialEvents } from '@the-order/events';
import {
entraWebhooksReceived,
entraWebhookProcessingDuration,
entraWebhookErrors,
} from '@the-order/monitoring';
import { createLogger } from '@the-order/shared';
const logger = createLogger('entra-webhooks');
export interface EntraWebhookPayload {
requestId: string;
requestStatus: 'request_created' | 'request_retrieved' | 'issuance_successful' | 'issuance_failed';
state?: string;
code?: string;
error?: {
code: string;
message: string;
};
credential?: {
id: string;
type: string[];
issuer: string;
issuanceDate: string;
expirationDate?: string;
credentialSubject: Record<string, unknown>;
proof: {
type: string;
created: string;
proofPurpose: string;
verificationMethod: string;
jws: string;
};
};
}
/**
* Validate webhook signature (if Entra provides one)
* Note: Entra VerifiedID may not sign webhooks, so this is a placeholder
*/
function validateWebhookSignature(_payload: EntraWebhookPayload, _signature?: string): boolean {
// TODO: Implement signature validation if Entra provides webhook signing
// For now, we rely on HTTPS and callback URL validation
return true;
}
/**
* Process Entra webhook payload
*/
async function processWebhook(payload: EntraWebhookPayload): Promise<void> {
const startTime = Date.now();
const { requestId, requestStatus, error, credential } = payload;
try {
logger.info('Processing Entra webhook', { requestId, requestStatus });
// Update metrics
entraWebhooksReceived.inc({ event_type: requestStatus, status: 'received' });
// Find credential in database
const dbCredential = await getVerifiableCredentialById(requestId);
if (!dbCredential) {
logger.warn('Credential not found in database', { requestId });
entraWebhookErrors.inc({ event_type: requestStatus, error_type: 'not_found' });
return;
}
// Update credential status based on webhook
if (requestStatus === 'issuance_successful' && credential) {
// Update credential with full credential data
await updateVerifiableCredential(requestId, {
status: 'issued',
credential_data: credential,
issued_at: new Date(credential.issuanceDate),
expires_at: credential.expirationDate ? new Date(credential.expirationDate) : undefined,
});
// Publish credential issued event
await getEventBus().publish(CredentialEvents.ISSUED, {
credentialId: requestId,
issuerDid: credential.issuer,
subjectDid: dbCredential.subject_did,
credentialType: credential.type,
issuedAt: credential.issuanceDate,
});
logger.info('Credential issued successfully', { requestId });
entraWebhooksReceived.inc({ event_type: requestStatus, status: 'processed' });
} else if (requestStatus === 'issuance_failed') {
// Update credential status to failed
await updateVerifiableCredential(requestId, {
status: 'failed',
error: error?.message || 'Issuance failed',
});
// Publish credential issuance failed event
await getEventBus().publish('credential.issuance.failed', {
credentialId: requestId,
error: error?.message || 'Unknown error',
});
logger.error('Credential issuance failed', { requestId, error });
entraWebhookErrors.inc({ event_type: requestStatus, error_type: 'issuance_failed' });
} else if (requestStatus === 'request_retrieved') {
// User has retrieved the issuance request
await updateVerifiableCredential(requestId, {
status: 'pending',
});
logger.info('Issuance request retrieved by user', { requestId });
entraWebhooksReceived.inc({ event_type: requestStatus, status: 'processed' });
}
// Record processing duration
const duration = (Date.now() - startTime) / 1000;
entraWebhookProcessingDuration.observe({ event_type: requestStatus }, duration);
} catch (error) {
logger.error('Error processing Entra webhook', { requestId, error });
entraWebhookErrors.inc({ event_type: requestStatus, error_type: 'processing_error' });
entraWebhookProcessingDuration.observe({ event_type: requestStatus }, (Date.now() - startTime) / 1000);
throw error;
}
}
/**
* Register Entra webhook routes
*/
export async function registerEntraWebhookRoutes(server: FastifyInstance): Promise<void> {
const env = getEnv();
// Webhook endpoint for Entra VerifiedID callbacks
server.post(
'/vc/entra/webhook',
{
schema: {
description: 'Webhook endpoint for Entra VerifiedID status updates',
tags: ['credentials', 'entra', 'webhooks'],
body: {
type: 'object',
required: ['requestId', 'requestStatus'],
properties: {
requestId: { type: 'string' },
requestStatus: {
type: 'string',
enum: ['request_created', 'request_retrieved', 'issuance_successful', 'issuance_failed'],
},
state: { type: 'string' },
code: { type: 'string' },
error: {
type: 'object',
properties: {
code: { type: 'string' },
message: { type: 'string' },
},
},
credential: {
type: 'object',
properties: {
id: { type: 'string' },
type: { type: 'array', items: { type: 'string' } },
issuer: { type: 'string' },
issuanceDate: { type: 'string' },
expirationDate: { type: 'string' },
credentialSubject: { type: 'object' },
proof: { type: 'object' },
},
},
},
},
response: {
200: {
type: 'object',
properties: {
received: { type: 'boolean' },
},
},
},
},
},
async (request: FastifyRequest, reply: FastifyReply) => {
const payload = request.body as EntraWebhookPayload;
try {
// Optional: Validate webhook signature if provided
const signature = request.headers['x-entra-signature'] as string | undefined;
if (!validateWebhookSignature(payload, signature)) {
logger.warn('Invalid webhook signature', { requestId: payload.requestId });
return reply.status(401).send({ error: 'Invalid signature' });
}
// Process webhook asynchronously
processWebhook(payload).catch((error) => {
logger.error('Async webhook processing failed', { error, requestId: payload.requestId });
});
// Return immediately to acknowledge receipt
return reply.status(200).send({ received: true });
} catch (error) {
logger.error('Error handling Entra webhook', { error });
return reply.status(500).send({
error: 'Failed to process webhook',
message: error instanceof Error ? error.message : String(error),
});
}
}
);
// Manual status check endpoint (for polling fallback)
server.get(
'/vc/entra/status/:requestId',
{
schema: {
description: 'Check Entra credential issuance status',
tags: ['credentials', 'entra'],
params: {
type: 'object',
properties: {
requestId: { type: 'string' },
},
},
response: {
200: {
type: 'object',
properties: {
requestId: { type: 'string' },
status: { type: 'string' },
credential: { type: 'object' },
},
},
},
},
},
async (request: FastifyRequest, reply: FastifyReply) => {
const { requestId } = request.params as { requestId: string };
try {
const credential = await getVerifiableCredentialById(requestId);
if (!credential) {
return reply.status(404).send({ error: 'Credential not found' });
}
return reply.status(200).send({
requestId,
status: credential.status || 'pending',
credential: credential.credential_data || null,
});
} catch (error) {
logger.error('Error checking credential status', { error, requestId });
return reply.status(500).send({
error: 'Failed to check status',
message: error instanceof Error ? error.message : String(error),
});
}
}
);
logger.info('Entra webhook routes registered');
}
-9
View File
@@ -93,14 +93,6 @@ async function initializeServer(): Promise<void> {
// Set error handler
server.setErrorHandler(errorHandler);
// Register Microsoft Entra VerifiedID routes
const { registerEntraRoutes } = await import('./entra-integration');
await registerEntraRoutes(server);
// Register Entra webhook routes
const { registerEntraWebhookRoutes } = await import('./entra-webhooks');
await registerEntraWebhookRoutes(server);
// Register batch issuance endpoint
const { registerBatchIssuance } = await import('./batch-issuance');
await registerBatchIssuance(server, kmsClient);
@@ -444,4 +436,3 @@ const start = async () => {
};
start();
@@ -33,7 +33,6 @@ export async function registerLettersOfCredenceRoutes(
appointmentDate: { type: 'string', format: 'date-time' },
expirationDate: { type: 'string', format: 'date-time' },
additionalClaims: { type: 'object' },
useEntraVerifiedID: { type: 'boolean' },
},
},
description: 'Issue Letters of Credence',
@@ -50,7 +49,6 @@ export async function registerLettersOfCredenceRoutes(
appointmentDate: string;
expirationDate?: string;
additionalClaims?: Record<string, unknown>;
useEntraVerifiedID?: boolean;
};
const user = (request as any).user;
@@ -75,8 +73,7 @@ export async function registerLettersOfCredenceRoutes(
expirationDate: body.expirationDate ? new Date(body.expirationDate) : undefined,
additionalClaims: body.additionalClaims,
},
kmsClient,
body.useEntraVerifiedID || false
kmsClient
);
return reply.send({ credentialId });
@@ -138,4 +135,3 @@ export async function registerLettersOfCredenceRoutes(
}
);
}
+2 -26
View File
@@ -1,10 +1,9 @@
/**
* Letters of Credence issuance automation
* Template-based generation, digital signatures, Entra VerifiedID integration, status tracking
* Template-based generation, digital signatures, and status tracking
*/
import { createVerifiableCredential } from '@the-order/database';
import { EntraVerifiedIDClient } from '@the-order/auth';
import { KMSClient } from '@the-order/crypto';
import { getEnv } from '@the-order/shared';
import { getCredentialTemplateByName, renderCredentialFromTemplate } from '@the-order/database';
@@ -34,8 +33,7 @@ export interface LettersOfCredenceStatus {
*/
export async function issueLettersOfCredence(
data: LettersOfCredenceData,
kmsClient: KMSClient,
useEntraVerifiedID = false
kmsClient: KMSClient
): Promise<string> {
const env = getEnv();
const issuerDid = env.VC_ISSUER_DID || (env.VC_ISSUER_DOMAIN ? `did:web:${env.VC_ISSUER_DOMAIN}` : undefined);
@@ -74,27 +72,6 @@ export async function issueLettersOfCredence(
const credentialType = ['VerifiableCredential', 'DiplomaticCredential', 'LettersOfCredence'];
// Use Entra VerifiedID if requested and configured
if (useEntraVerifiedID && env.ENTRA_TENANT_ID && env.ENTRA_CLIENT_ID && env.ENTRA_CLIENT_SECRET) {
const entraClient = new EntraVerifiedIDClient({
tenantId: env.ENTRA_TENANT_ID,
clientId: env.ENTRA_CLIENT_ID,
clientSecret: env.ENTRA_CLIENT_SECRET,
credentialManifestId: env.ENTRA_CREDENTIAL_MANIFEST_ID,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const issuanceRequest = await entraClient.issueCredential({
claims: credentialSubject as any,
subjectDid: data.recipientDid,
pin: undefined,
callbackUrl: undefined,
} as any);
// Store the issuance request reference
credentialSubject.entraIssuanceRequest = issuanceRequest;
}
// Sign with KMS
const credentialData = {
id: credentialId,
@@ -167,4 +144,3 @@ export async function revokeLettersOfCredence(
revocation_reason: reason,
});
}
@@ -1,146 +0,0 @@
/**
* Azure Logic Apps workflow integration
* Pre-built workflows for eIDAS-Verify-And-Issue, Appointment-Credential, Batch-Renewal, Document-Attestation
*/
import { AzureLogicAppsClient } from '@the-order/auth';
import { getEnv } from '@the-order/shared';
export interface LogicAppsWorkflowConfig {
eidasVerifyAndIssueUrl?: string;
appointmentCredentialUrl?: string;
batchRenewalUrl?: string;
documentAttestationUrl?: string;
}
/**
* Initialize Azure Logic Apps workflows
*/
export function initializeLogicAppsWorkflows(
config?: LogicAppsWorkflowConfig
): {
eidasVerifyAndIssue: (eidasData: unknown) => Promise<unknown>;
appointmentCredential: (appointmentData: unknown) => Promise<unknown>;
batchRenewal: (renewalData: unknown) => Promise<unknown>;
documentAttestation: (documentData: unknown) => Promise<unknown>;
} {
const env = getEnv();
const eidasClient = config?.eidasVerifyAndIssueUrl
? new AzureLogicAppsClient({
workflowUrl: config.eidasVerifyAndIssueUrl,
accessKey: env.AZURE_LOGIC_APPS_ACCESS_KEY,
managedIdentityClientId: env.AZURE_LOGIC_APPS_MANAGED_IDENTITY_CLIENT_ID,
})
: null;
const appointmentClient = config?.appointmentCredentialUrl
? new AzureLogicAppsClient({
workflowUrl: config.appointmentCredentialUrl,
accessKey: env.AZURE_LOGIC_APPS_ACCESS_KEY,
managedIdentityClientId: env.AZURE_LOGIC_APPS_MANAGED_IDENTITY_CLIENT_ID,
})
: null;
const batchRenewalClient = config?.batchRenewalUrl
? new AzureLogicAppsClient({
workflowUrl: config.batchRenewalUrl,
accessKey: env.AZURE_LOGIC_APPS_ACCESS_KEY,
managedIdentityClientId: env.AZURE_LOGIC_APPS_MANAGED_IDENTITY_CLIENT_ID,
})
: null;
const documentAttestationClient = config?.documentAttestationUrl
? new AzureLogicAppsClient({
workflowUrl: config.documentAttestationUrl,
accessKey: env.AZURE_LOGIC_APPS_ACCESS_KEY,
managedIdentityClientId: env.AZURE_LOGIC_APPS_MANAGED_IDENTITY_CLIENT_ID,
})
: null;
return {
/**
* eIDAS Verify and Issue workflow
* Verifies eIDAS signature and issues corresponding credential
*/
async eidasVerifyAndIssue(eidasData: unknown): Promise<unknown> {
if (!eidasClient) {
throw new Error('eIDAS Verify and Issue workflow not configured');
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const eidasDataTyped = eidasData as any;
return eidasClient.triggerEIDASVerification(
eidasDataTyped.documentId || '',
eidasDataTyped.userId || '',
eidasDataTyped.eidasProviderUrl || ''
);
},
/**
* Appointment Credential workflow
* Issues credential based on appointment data
*/
async appointmentCredential(appointmentData: unknown): Promise<unknown> {
if (!appointmentClient) {
throw new Error('Appointment Credential workflow not configured');
}
return appointmentClient.triggerWorkflow({
body: {
eventType: 'appointmentCredential',
data: appointmentData,
},
});
},
/**
* Batch Renewal workflow
* Renews multiple credentials in batch
*/
async batchRenewal(renewalData: unknown): Promise<unknown> {
if (!batchRenewalClient) {
throw new Error('Batch Renewal workflow not configured');
}
return batchRenewalClient.triggerWorkflow({
body: {
eventType: 'batchRenewal',
data: renewalData,
},
});
},
/**
* Document Attestation workflow
* Attests to document authenticity and issues credential
*/
async documentAttestation(documentData: unknown): Promise<unknown> {
if (!documentAttestationClient) {
throw new Error('Document Attestation workflow not configured');
}
return documentAttestationClient.triggerWorkflow({
body: {
eventType: 'documentAttestation',
data: documentData,
},
});
},
};
}
/**
* Get default Logic Apps workflows
*/
let defaultWorkflows: ReturnType<typeof initializeLogicAppsWorkflows> | null = null;
export function getLogicAppsWorkflows(
config?: LogicAppsWorkflowConfig
): ReturnType<typeof initializeLogicAppsWorkflows> {
if (!defaultWorkflows) {
defaultWorkflows = initializeLogicAppsWorkflows(config);
}
return defaultWorkflows;
}