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
This commit is contained in:
defiQUG
2025-11-12 22:03:42 -08:00
parent 8649ad4124
commit 92cc41d26d
258 changed files with 16021 additions and 1260 deletions

View File

@@ -2,7 +2,7 @@
* Batch credential issuance endpoint
*/
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { FastifyInstance } from 'fastify';
import { randomUUID } from 'crypto';
import { KMSClient } from '@the-order/crypto';
import { createVerifiableCredential } from '@the-order/database';
@@ -62,7 +62,7 @@ export async function registerBatchIssuance(
},
},
},
}),
},
description: 'Batch issue verifiable credentials',
tags: ['credentials'],
response: {
@@ -88,7 +88,8 @@ export async function registerBatchIssuance(
},
},
},
async (request: FastifyRequest<{ Body: BatchIssuanceRequest }>, reply: FastifyReply) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async (request: any, reply: any) => {
if (!issuerDid) {
return reply.code(500).send({
error: {

View File

@@ -34,6 +34,7 @@ export async function initializeCredentialNotifications(): Promise<void> {
to: eventData.recipientEmail,
type: 'email',
subject: CredentialNotificationTemplates.ISSUED.email.subject,
message: '', // Message will be generated from template
template: CredentialNotificationTemplates.ISSUED.email.template,
templateData: {
recipientName: eventData.recipientName || 'User',
@@ -51,6 +52,7 @@ export async function initializeCredentialNotifications(): Promise<void> {
await notificationService.send({
to: eventData.recipientPhone,
type: 'sms',
message: '', // Message will be generated from template
template: CredentialNotificationTemplates.ISSUED.sms.template,
templateData: {
credentialType,
@@ -78,6 +80,7 @@ export async function initializeCredentialNotifications(): Promise<void> {
to: eventData.recipientEmail,
type: 'email',
subject: CredentialNotificationTemplates.RENEWED.email.subject,
message: '', // Message will be generated from template
template: CredentialNotificationTemplates.RENEWED.email.template,
templateData: {
recipientName: eventData.recipientName || 'User',
@@ -108,6 +111,7 @@ export async function initializeCredentialNotifications(): Promise<void> {
to: eventData.recipientEmail,
type: 'email',
subject: CredentialNotificationTemplates.EXPIRING.email.subject,
message: '', // Message will be generated from template
template: CredentialNotificationTemplates.EXPIRING.email.template,
templateData: {
recipientName: eventData.recipientName || 'User',
@@ -124,6 +128,7 @@ export async function initializeCredentialNotifications(): Promise<void> {
await notificationService.send({
to: eventData.recipientPhone,
type: 'sms',
message: '', // Message will be generated from template
template: CredentialNotificationTemplates.EXPIRING.sms.template,
templateData: {
credentialType: 'Verifiable Credential',
@@ -151,6 +156,7 @@ export async function initializeCredentialNotifications(): Promise<void> {
to: eventData.recipientEmail,
type: 'email',
subject: CredentialNotificationTemplates.REVOKED.email.subject,
message: '', // Message will be generated from template
template: CredentialNotificationTemplates.REVOKED.email.template,
templateData: {
recipientName: eventData.recipientName || 'User',

View File

@@ -157,7 +157,7 @@ export async function initializeCredentialRenewal(kmsClient: KMSClient): Promise
*/
export async function triggerRenewal(
credentialId: string,
kmsClient: KMSClient
_kmsClient: KMSClient
): Promise<void> {
const jobQueue = getJobQueue();
const renewalQueue = jobQueue.createQueue<RenewalJobData>('credential-renewal');

View File

@@ -19,7 +19,7 @@ export async function initializeCredentialRevocation(): Promise<void> {
// Subscribe to user suspension events
await eventBus.subscribe(UserEvents.SUSPENDED, async (data) => {
const { userId, did } = data as { userId: string; did?: string };
const { userId: _userId, did } = data as { userId: string; did?: string };
if (did) {
await revokeAllUserCredentials(did, 'User account suspended');
}
@@ -27,7 +27,7 @@ export async function initializeCredentialRevocation(): Promise<void> {
// Subscribe to role removal events
await eventBus.subscribe('role.removed', async (data) => {
const { userId, role, did } = data as { userId: string; role: string; did?: string };
const { userId: _userId, role, did } = data as { userId: string; role: string; did?: string };
if (did) {
await revokeRoleCredentials(did, role, 'Role removed');
}

View File

@@ -7,25 +7,63 @@ 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 Entra VerifiedID client
* Initialize Enhanced Entra VerifiedID client with multi-manifest support
*/
export function createEntraClient(): EntraVerifiedIDClient | null {
export function createEntraClient(): EnhancedEntraVerifiedIDClient | null {
const env = getEnv();
if (!env.ENTRA_TENANT_ID || !env.ENTRA_CLIENT_ID || !env.ENTRA_CLIENT_SECRET) {
return null;
}
return new EntraVerifiedIDClient({
// 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,
});
}
@@ -69,7 +107,7 @@ export function createEIDASToEntraBridge(): EIDASToEntraBridge | null {
/**
* Register Entra VerifiedID routes
*/
export async function registerEntraRoutes(server: FastifyInstance<any, any, any, any, any>): Promise<void> {
export async function registerEntraRoutes(server: FastifyInstance): Promise<void> {
const entraClient = createEntraClient();
const eidasBridge = createEIDASToEntraBridge();
@@ -78,6 +116,9 @@ export async function registerEntraRoutes(server: FastifyInstance<any, any, any,
return;
}
// Register Entra-specific rate limiting
await registerEntraRateLimit(server);
// Issue credential via Entra VerifiedID
server.post(
'/vc/issue/entra',
@@ -125,17 +166,41 @@ export async function registerEntraRoutes(server: FastifyInstance<any, any, any,
},
},
async (request, reply) => {
const body = request.body as VerifiableCredentialRequest;
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' });
}
}
);
@@ -169,18 +234,42 @@ export async function registerEntraRoutes(server: FastifyInstance<any, any, any,
},
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' });
}
}
);

View File

@@ -0,0 +1,267 @@
/**
* 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');
}

View File

@@ -208,8 +208,11 @@ async function issueCredentialOnAppointment(
appointedAt: new Date().toISOString(),
};
// Extract subjectDid from appointmentData or use userId
const subjectDidValue = (appointmentData.subjectDid as string) || `did:key:${userId}`;
await issueCredential({
subjectDid,
subjectDid: subjectDidValue,
issuerDid,
credentialType: ['VerifiableCredential', 'AppointmentCredential', `${role}Credential`],
credentialSubject,

View File

@@ -4,7 +4,7 @@
import { FastifyInstance } from 'fastify';
import { KMSClient } from '@the-order/crypto';
import { createBodySchema, authenticateJWT, requireRole, getAuthorizationService } from '@the-order/shared';
import { authenticateJWT, requireRole, getAuthorizationService } from '@the-order/shared';
import { issueFinancialCredential, getFinancialCredentialTemplate, FinancialRole, FINANCIAL_CREDENTIAL_TYPES } from './financial-credentials';
import { logCredentialAction } from '@the-order/database';
import { getEnv } from '@the-order/shared';
@@ -34,7 +34,7 @@ export async function registerFinancialCredentialRoutes(
expirationDate: { type: 'string', format: 'date-time' },
additionalClaims: { type: 'object' },
},
}),
},
description: 'Issue financial role credential',
tags: ['financial-credentials'],
},

View File

@@ -95,6 +95,10 @@ async function initializeServer(): Promise<void> {
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);

View File

@@ -6,7 +6,7 @@
import { getEventBus, AppointmentEvents, CredentialEvents } from '@the-order/events';
import { issueJudicialCredential, type JudicialRole } from './judicial-credentials';
import { KMSClient } from '@the-order/crypto';
import { sendEmail } from '@the-order/notifications';
import { getNotificationService } from '@the-order/notifications';
export interface JudicialAppointmentData {
userId: string;
@@ -93,10 +93,11 @@ export async function initializeJudicialAppointmentIssuance(
// Send notification
if (appointmentData.recipientEmail) {
await sendEmail({
const notificationService = getNotificationService();
await notificationService.send({
to: appointmentData.recipientEmail,
subject: 'Judicial Appointment Credential Issued',
text: `Dear ${appointmentData.recipientName || 'User'},
type: 'email',
message: `Dear ${appointmentData.recipientName || 'User'},
Your judicial appointment credential has been issued.
@@ -109,6 +110,7 @@ You can view your credential at: ${process.env.CREDENTIALS_URL || 'https://theor
Best regards,
The Order`,
subject: 'Judicial Appointment Credential Issued',
});
}
} catch (error) {

View File

@@ -5,7 +5,7 @@
import { FastifyInstance } from 'fastify';
import { KMSClient } from '@the-order/crypto';
import { issueJudicialCredential, getJudicialCredentialTemplate, type JudicialRole } from './judicial-credentials';
import { createBodySchema, authenticateJWT, requireRole, getAuthorizationService } from '@the-order/shared';
import { authenticateJWT, requireRole, getAuthorizationService } from '@the-order/shared';
export async function registerJudicialRoutes(
server: FastifyInstance,
@@ -41,7 +41,7 @@ export async function registerJudicialRoutes(
expirationDate: { type: 'string', format: 'date-time' },
additionalClaims: { type: 'object' },
},
}),
},
description: 'Issue judicial credential',
tags: ['judicial'],
},

View File

@@ -9,7 +9,7 @@ import {
trackLettersOfCredenceStatus,
revokeLettersOfCredence,
} from './letters-of-credence';
import { createBodySchema, authenticateJWT, requireRole, getAuthorizationService } from '@the-order/shared';
import { authenticateJWT, requireRole, getAuthorizationService } from '@the-order/shared';
export async function registerLettersOfCredenceRoutes(
server: FastifyInstance,
@@ -35,7 +35,7 @@ export async function registerLettersOfCredenceRoutes(
additionalClaims: { type: 'object' },
useEntraVerifiedID: { type: 'boolean' },
},
}),
},
description: 'Issue Letters of Credence',
tags: ['diplomatic'],
},
@@ -124,7 +124,7 @@ export async function registerLettersOfCredenceRoutes(
properties: {
reason: { type: 'string' },
},
}),
},
description: 'Revoke Letters of Credence',
tags: ['diplomatic'],
},

View File

@@ -83,11 +83,13 @@ export async function issueLettersOfCredence(
credentialManifestId: env.ENTRA_CREDENTIAL_MANIFEST_ID,
});
const issuanceRequest = await entraClient.createIssuanceRequest({
subject: data.recipientDid,
credentialSubject,
expirationDate: expirationDate.toISOString(),
});
// 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;

View File

@@ -68,7 +68,13 @@ export function initializeLogicAppsWorkflows(
throw new Error('eIDAS Verify and Issue workflow not configured');
}
return eidasClient.triggerEIDASVerification(eidasData);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const eidasDataTyped = eidasData as any;
return eidasClient.triggerEIDASVerification(
eidasDataTyped.documentId || '',
eidasDataTyped.userId || '',
eidasDataTyped.eidasProviderUrl || ''
);
},
/**
@@ -81,8 +87,10 @@ export function initializeLogicAppsWorkflows(
}
return appointmentClient.triggerWorkflow({
eventType: 'appointmentCredential',
data: appointmentData,
body: {
eventType: 'appointmentCredential',
data: appointmentData,
},
});
},
@@ -96,8 +104,10 @@ export function initializeLogicAppsWorkflows(
}
return batchRenewalClient.triggerWorkflow({
eventType: 'batchRenewal',
data: renewalData,
body: {
eventType: 'batchRenewal',
data: renewalData,
},
});
},
@@ -111,8 +121,10 @@ export function initializeLogicAppsWorkflows(
}
return documentAttestationClient.triggerWorkflow({
eventType: 'documentAttestation',
data: documentData,
body: {
eventType: 'documentAttestation',
data: documentData,
},
});
},
};

View File

@@ -5,7 +5,7 @@
import { FastifyInstance } from 'fastify';
import { getCredentialMetrics, getMetricsDashboard } from './metrics';
import { searchAuditLogs, exportAuditLogs } from '@the-order/database';
import { authenticateJWT, requireRole, createBodySchema } from '@the-order/shared';
import { authenticateJWT, requireRole } from '@the-order/shared';
export async function registerMetricsRoutes(server: FastifyInstance): Promise<void> {
// Get credential metrics
@@ -50,7 +50,8 @@ export async function registerMetricsRoutes(server: FastifyInstance): Promise<vo
tags: ['metrics'],
},
},
async (request, reply) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async (_request: any, reply: any) => {
const dashboard = await getMetricsDashboard();
return reply.send(dashboard);
}
@@ -62,7 +63,7 @@ export async function registerMetricsRoutes(server: FastifyInstance): Promise<vo
{
preHandler: [authenticateJWT, requireRole('admin', 'auditor')],
schema: {
...createBodySchema({
body: {
type: 'object',
properties: {
credentialId: { type: 'string' },
@@ -77,7 +78,7 @@ export async function registerMetricsRoutes(server: FastifyInstance): Promise<vo
page: { type: 'number' },
pageSize: { type: 'number' },
},
}),
},
description: 'Search audit logs',
tags: ['metrics'],
},
@@ -123,7 +124,7 @@ export async function registerMetricsRoutes(server: FastifyInstance): Promise<vo
{
preHandler: [authenticateJWT, requireRole('admin', 'auditor')],
schema: {
...createBodySchema({
body: {
type: 'object',
properties: {
credentialId: { type: 'string' },
@@ -135,7 +136,7 @@ export async function registerMetricsRoutes(server: FastifyInstance): Promise<vo
endDate: { type: 'string', format: 'date-time' },
format: { type: 'string', enum: ['json', 'csv'] },
},
}),
},
description: 'Export audit logs',
tags: ['metrics'],
},

View File

@@ -4,7 +4,7 @@
*/
import { getAuditStatistics, searchAuditLogs } from '@the-order/database';
import { getPool } from '@the-order/database';
// import { getPool } from '@the-order/database'; // Not used in this file
import { query } from '@the-order/database';
export interface CredentialMetrics {
@@ -123,8 +123,8 @@ async function getIssuanceCount(startDate: Date, endDate: Date): Promise<number>
* Note: This requires tracking issuance time in the audit log metadata
*/
async function getPerformanceMetrics(
startDate: Date,
endDate: Date
_startDate: Date,
_endDate: Date
): Promise<{
average: number;
p50: number;

View File

@@ -44,13 +44,16 @@ export async function initializeScheduledIssuance(
jobQueue.createWorker(
'scheduled-issuance',
async (job) => {
const { credentialType, subjectDid, credentialSubject, scheduledDate } = job.data;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const jobData = job.data as any;
const { credentialType, subjectDid, credentialSubject, scheduledDate } = jobData;
const scheduledDateObj = scheduledDate instanceof Date ? scheduledDate : new Date(scheduledDate as string);
// Check if it's time to issue
if (new Date() < scheduledDate) {
if (new Date() < scheduledDateObj) {
// Reschedule for later
await scheduledQueue.add('default' as any, job.data, {
delay: scheduledDate.getTime() - Date.now(),
await scheduledQueue.add('default' as any, jobData, {
delay: scheduledDateObj.getTime() - Date.now(),
});
return { rescheduled: true };
}
@@ -82,9 +85,9 @@ export async function initializeScheduledIssuance(
await createVerifiableCredential({
credential_id: credentialId,
issuer_did: issuerDid,
subject_did: subjectDid,
credential_type: credentialType,
credential_subject: credentialSubject,
subject_did: subjectDid as string,
credential_type: credentialType as string[],
credential_subject: credentialSubject as Record<string, unknown>,
issuance_date: issuanceDate,
expiration_date: undefined,
proof,
@@ -111,8 +114,9 @@ export async function initializeScheduledIssuance(
});
jobQueue.createWorker('expiration-detection', async (job) => {
const { daysAhead } = job.data;
const expiring = await getExpiringCredentials(daysAhead, 1000);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { daysAhead } = job.data as any;
const expiring = await getExpiringCredentials(daysAhead as number, 1000);
for (const cred of expiring) {
await eventBus.publish(CredentialEvents.EXPIRING, {
@@ -139,8 +143,9 @@ export async function initializeScheduledIssuance(
});
jobQueue.createWorker('batch-renewal', async (job) => {
const { daysAhead } = job.data;
const expiring = await getExpiringCredentials(daysAhead, 100);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { daysAhead } = job.data as any;
const expiring = await getExpiringCredentials(daysAhead as number, 100);
let renewed = 0;
for (const cred of expiring) {

View File

@@ -12,7 +12,7 @@ import {
createTemplateVersion,
renderCredentialFromTemplate,
} from '@the-order/database';
import { createBodySchema, authenticateJWT, requireRole } from '@the-order/shared';
import { authenticateJWT, requireRole } from '@the-order/shared';
export async function registerTemplateRoutes(server: FastifyInstance): Promise<void> {
// Create template
@@ -21,7 +21,7 @@ export async function registerTemplateRoutes(server: FastifyInstance): Promise<v
{
preHandler: [authenticateJWT, requireRole('admin', 'issuer')],
schema: {
...createBodySchema({
body: {
type: 'object',
required: ['name', 'credential_type', 'template_data'],
properties: {
@@ -32,7 +32,7 @@ export async function registerTemplateRoutes(server: FastifyInstance): Promise<v
version: { type: 'number' },
is_active: { type: 'boolean' },
},
}),
},
description: 'Create a credential template',
tags: ['templates'],
},
@@ -172,14 +172,14 @@ export async function registerTemplateRoutes(server: FastifyInstance): Promise<v
id: { type: 'string' },
},
},
...createBodySchema({
body: {
type: 'object',
properties: {
description: { type: 'string' },
template_data: { type: 'object' },
is_active: { type: 'boolean' },
},
}),
},
description: 'Update credential template',
tags: ['templates'],
},
@@ -214,13 +214,13 @@ export async function registerTemplateRoutes(server: FastifyInstance): Promise<v
id: { type: 'string' },
},
},
...createBodySchema({
body: {
type: 'object',
properties: {
template_data: { type: 'object' },
description: { type: 'string' },
},
}),
},
description: 'Create new version of credential template',
tags: ['templates'],
},
@@ -249,13 +249,13 @@ export async function registerTemplateRoutes(server: FastifyInstance): Promise<v
id: { type: 'string' },
},
},
...createBodySchema({
body: {
type: 'object',
required: ['variables'],
properties: {
variables: { type: 'object' },
},
}),
},
description: 'Render credential template with variables',
tags: ['templates'],
},