Files
the_order/services/identity/src/credential-renewal.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

188 lines
6.1 KiB
TypeScript

/**
* Automated credential renewal system
* Uses background job queue to scan for expiring credentials and issue renewals
*/
import { getExpiringCredentials, createVerifiableCredential, revokeCredential } from '@the-order/database';
import { getJobQueue } from '@the-order/jobs';
import { getEventBus, CredentialEvents } from '@the-order/events';
import { KMSClient } from '@the-order/crypto';
import { randomUUID } from 'crypto';
export interface RenewalJobData {
credentialId: string;
subjectDid: string;
issuerDid: string;
credentialType: string[];
credentialSubject: unknown;
expirationDate: Date;
}
/**
* Initialize credential renewal system
*/
export async function initializeCredentialRenewal(kmsClient: KMSClient): Promise<void> {
const jobQueue = getJobQueue();
const eventBus = getEventBus();
// Create renewal queue
const renewalQueue = jobQueue.createQueue<RenewalJobData>('credential-renewal');
// Create worker for renewal jobs
jobQueue.createWorker<RenewalJobData>(
'credential-renewal',
async (job) => {
const { credentialId, subjectDid, issuerDid, credentialType, credentialSubject, expirationDate } = job.data;
try {
// Issue new credential with extended expiration
const newExpirationDate = new Date(expirationDate);
newExpirationDate.setFullYear(newExpirationDate.getFullYear() + 1); // Extend by 1 year
const newCredentialId = randomUUID();
const issuanceDate = new Date();
// Create credential data
const credentialData = {
id: newCredentialId,
type: credentialType,
issuer: issuerDid,
subject: subjectDid,
credentialSubject,
issuanceDate: issuanceDate.toISOString(),
expirationDate: newExpirationDate.toISOString(),
};
// Sign credential with KMS
const credentialJson = JSON.stringify(credentialData);
const signature = await kmsClient.sign(Buffer.from(credentialJson));
// Create proof
const proof = {
type: 'KmsSignature2024',
created: issuanceDate.toISOString(),
proofPurpose: 'assertionMethod',
verificationMethod: `${issuerDid}#kms-key`,
jws: signature.toString('base64'),
};
// Save new credential
await createVerifiableCredential({
credential_id: newCredentialId,
issuer_did: issuerDid,
subject_did: subjectDid,
credential_type: credentialType,
credential_subject: credentialSubject as Record<string, unknown>,
issuance_date: issuanceDate,
expiration_date: newExpirationDate,
proof,
});
// Revoke old credential
await revokeCredential({
credential_id: credentialId,
issuer_did: issuerDid,
revocation_reason: 'Renewed - replaced by new credential',
});
// Publish renewal event
await eventBus.publish(CredentialEvents.RENEWED, {
oldCredentialId: credentialId,
newCredentialId,
subjectDid,
renewedAt: issuanceDate.toISOString(),
});
return { success: true, newCredentialId };
} catch (error) {
console.error(`Failed to renew credential ${credentialId}:`, error);
throw error;
}
},
{ concurrency: 5 }
);
// Create worker for expiration scanner
jobQueue.createWorker<{ daysAhead: number }>(
'credential-expiration-scanner',
async (job) => {
const { daysAhead } = job.data;
// Get expiring credentials
const expiringCredentials = await getExpiringCredentials(daysAhead, 1000);
// Publish expiring event for each
for (const cred of expiringCredentials) {
await eventBus.publish(CredentialEvents.EXPIRING, {
credentialId: cred.credential_id,
subjectDid: cred.subject_did,
expirationDate: cred.expiration_date!.toISOString(),
daysUntilExpiration: Math.ceil(
(cred.expiration_date!.getTime() - Date.now()) / (1000 * 60 * 60 * 24)
),
});
// For credentials expiring in 30 days or less, queue for renewal
const daysUntilExpiration = Math.ceil(
(cred.expiration_date!.getTime() - Date.now()) / (1000 * 60 * 60 * 24)
);
if (daysUntilExpiration <= 30) {
// Queue renewal job with credential data
await renewalQueue.add('default', {
credentialId: cred.credential_id,
subjectDid: cred.subject_did,
issuerDid: cred.issuer_did,
credentialType: cred.credential_type,
credentialSubject: cred.credential_subject as Record<string, unknown>,
expirationDate: cred.expiration_date!,
} as RenewalJobData);
}
}
return { scanned: expiringCredentials.length };
}
);
// Schedule recurring job to scan for expiring credentials daily at 2 AM
await jobQueue.addRecurringJob<{ daysAhead: number }>(
'credential-expiration-scanner',
{ daysAhead: 90 }, // Check credentials expiring in next 90 days
'0 2 * * *' // Run daily at 2 AM
);
}
/**
* Manually trigger renewal for a specific credential
*/
export async function triggerRenewal(
credentialId: string,
_kmsClient: KMSClient
): Promise<void> {
const jobQueue = getJobQueue();
const renewalQueue = jobQueue.createQueue<RenewalJobData>('credential-renewal');
// Get credential details
const { getVerifiableCredentialById } = await import('@the-order/database');
const credential = await getVerifiableCredentialById(credentialId);
if (!credential) {
throw new Error(`Credential ${credentialId} not found`);
}
if (!credential.expiration_date) {
throw new Error(`Credential ${credentialId} has no expiration date`);
}
// Queue renewal job
await renewalQueue.add('default', {
credentialId: credential.credential_id,
subjectDid: credential.subject_did,
issuerDid: credential.issuer_did,
credentialType: credential.credential_type,
credentialSubject: credential.credential_subject as Record<string, unknown>,
expirationDate: credential.expiration_date,
} as RenewalJobData);
}