Files
the_order/packages/auth/src/entra-verifiedid.ts
defiQUG 2633de4d33 feat(eresidency): Complete eResidency service implementation
- Implement credential revocation endpoint with proper database integration
- Fix database row mapping (snake_case to camelCase) for eResidency applications
- Add missing imports (getRiskAssessmentEngine, VeriffKYCProvider, ComplyAdvantageSanctionsProvider)
- Fix environment variable type checking for Veriff and ComplyAdvantage providers
- Add required 'message' field to notification service calls
- Fix risk assessment type mismatches
- Update audit logging to use 'verified' action type (supported by schema)
- Resolve all TypeScript errors and unused variable warnings
- Add TypeScript ignore comments for placeholder implementations
- Temporarily disable security/detect-non-literal-regexp rule due to ESLint 9 compatibility
- Service now builds successfully with no linter errors

All core functionality implemented:
- Application submission and management
- KYC integration (Veriff placeholder)
- Sanctions screening (ComplyAdvantage placeholder)
- Risk assessment engine
- Credential issuance and revocation
- Reviewer console
- Status endpoints
- Auto-issuance service
2025-11-10 19:43:02 -08:00

286 lines
7.2 KiB
TypeScript

/**
* Microsoft Entra VerifiedID connector
* Provides integration with Microsoft Entra VerifiedID for verifiable credential issuance and verification
*/
import fetch from 'node-fetch';
export interface EntraVerifiedIDConfig {
tenantId: string;
clientId: string;
clientSecret: string;
credentialManifestId?: string;
apiVersion?: string;
}
export interface VerifiableCredentialRequest {
claims: Record<string, string>;
pin?: string;
callbackUrl?: string;
}
export interface VerifiableCredentialResponse {
requestId: string;
url: string;
expiry: number;
qrCode?: string;
}
export interface VerifiableCredentialStatus {
requestId: string;
state: 'request_created' | 'request_retrieved' | 'issuance_successful' | 'issuance_failed';
code?: string;
error?: {
code: string;
message: string;
};
}
export interface VerifiedCredential {
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;
};
}
/**
* Microsoft Entra VerifiedID client
*/
export class EntraVerifiedIDClient {
private accessToken: string | null = null;
private tokenExpiry: number = 0;
private baseUrl: string;
constructor(private config: EntraVerifiedIDConfig) {
this.baseUrl = `https://verifiedid.did.msidentity.com/v1.0/${config.tenantId}`;
}
/**
* Get access token for Microsoft Entra VerifiedID API
*/
private async getAccessToken(): Promise<string> {
// Check if we have a valid cached token
if (this.accessToken && Date.now() < this.tokenExpiry) {
return this.accessToken;
}
const tokenUrl = `https://login.microsoftonline.com/${this.config.tenantId}/oauth2/v2.0/token`;
const params = new URLSearchParams({
client_id: this.config.clientId,
client_secret: this.config.clientSecret,
scope: 'https://verifiedid.did.msidentity.com/.default',
grant_type: 'client_credentials',
});
const response = await fetch(tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString(),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to get access token: ${response.status} ${errorText}`);
}
const tokenData = (await response.json()) as {
access_token: string;
expires_in: number;
};
this.accessToken = tokenData.access_token;
// Set expiry 5 minutes before actual expiry for safety
this.tokenExpiry = Date.now() + (tokenData.expires_in - 300) * 1000;
return this.accessToken;
}
/**
* Issue a verifiable credential
*/
async issueCredential(
request: VerifiableCredentialRequest
): Promise<VerifiableCredentialResponse> {
const token = await this.getAccessToken();
const manifestId = this.config.credentialManifestId;
if (!manifestId) {
throw new Error('Credential manifest ID is required for issuance');
}
const issueUrl = `${this.baseUrl}/verifiableCredentials/createIssuanceRequest`;
const requestBody = {
includeQRCode: true,
callback: request.callbackUrl
? {
url: request.callbackUrl,
state: crypto.randomUUID(),
}
: undefined,
authority: `did:web:${this.config.tenantId}.verifiedid.msidentity.com`,
registration: {
clientName: 'The Order',
},
type: manifestId,
manifestId,
pin: request.pin
? {
value: request.pin,
length: request.pin.length,
}
: undefined,
claims: request.claims,
};
const response = await fetch(issueUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to issue credential: ${response.status} ${errorText}`);
}
const data = (await response.json()) as {
requestId: string;
url: string;
expiry: number;
qrCode?: string;
};
return {
requestId: data.requestId,
url: data.url,
expiry: data.expiry,
qrCode: data.qrCode,
};
}
/**
* Check issuance status
*/
async getIssuanceStatus(requestId: string): Promise<VerifiableCredentialStatus> {
const token = await this.getAccessToken();
const statusUrl = `${this.baseUrl}/verifiableCredentials/issuanceRequests/${requestId}`;
const response = await fetch(statusUrl, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to get issuance status: ${response.status} ${errorText}`);
}
return (await response.json()) as VerifiableCredentialStatus;
}
/**
* Verify a verifiable credential
*/
async verifyCredential(credential: VerifiedCredential): Promise<boolean> {
const token = await this.getAccessToken();
const verifyUrl = `${this.baseUrl}/verifiableCredentials/verify`;
const response = await fetch(verifyUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
verifiableCredential: credential,
}),
});
if (!response.ok) {
return false;
}
const result = (await response.json()) as { verified: boolean };
return result.verified ?? false;
}
/**
* Create a presentation request for credential verification
*/
async createPresentationRequest(
manifestId: string,
callbackUrl?: string
): Promise<VerifiableCredentialResponse> {
const token = await this.getAccessToken();
const requestUrl = `${this.baseUrl}/verifiableCredentials/createPresentationRequest`;
const requestBody = {
includeQRCode: true,
callback: callbackUrl
? {
url: callbackUrl,
state: crypto.randomUUID(),
}
: undefined,
authority: `did:web:${this.config.tenantId}.verifiedid.msidentity.com`,
registration: {
clientName: 'The Order',
},
requestedCredentials: [
{
type: manifestId,
manifestId,
acceptedIssuers: [`did:web:${this.config.tenantId}.verifiedid.msidentity.com`],
},
],
};
const response = await fetch(requestUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to create presentation request: ${response.status} ${errorText}`);
}
const data = (await response.json()) as {
requestId: string;
url: string;
expiry: number;
qrCode?: string;
};
return {
requestId: data.requestId,
url: data.url,
expiry: data.expiry,
qrCode: data.qrCode,
};
}
}