import { Global, Module, type OnModuleInit } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { ContextAttestationVerifier } from './context-attestation'; import { PrismaClientRegistry, PrismaNonceStore, JwksPublicKeyResolver } from './attestation-stores'; import { ContextAttestationGuard } from './context-attestation.guard'; /** * Wires the context-attestation trust layer into DI and exposes the guard globally so any * controller can enforce the three-proof gate. Also dev-seeds a registered client so locally * minted attestations verify (prod registers clients out-of-band). */ @Global() @Module({ providers: [ PrismaClientRegistry, PrismaNonceStore, JwksPublicKeyResolver, { provide: ContextAttestationVerifier, useFactory: (reg: PrismaClientRegistry, nonces: PrismaNonceStore, keys: JwksPublicKeyResolver) => new ContextAttestationVerifier(reg, nonces, { audience: process.env.IIOS_ATTESTATION_AUDIENCE ?? 'iios-core' }, keys), inject: [PrismaClientRegistry, PrismaNonceStore, JwksPublicKeyResolver], }, ContextAttestationGuard, ], exports: [ContextAttestationVerifier, ContextAttestationGuard], }) export class AttestationModule implements OnModuleInit { constructor(private readonly prisma: PrismaService) {} /** Dev seed: register the CRM support client so an attestation signed with the shared dev * secret verifies locally. Gated by IIOS_DEV_TOKENS + IIOS_ATTESTATION_DEV_SECRET; best-effort. */ async onModuleInit(): Promise { if (process.env.IIOS_DEV_TOKENS !== '1') return; const secret = process.env.IIOS_ATTESTATION_DEV_SECRET; if (!secret) return; await this.prisma.iiosClientRegistry .upsert({ where: { clientId: 'appshell-crm' }, create: { clientId: 'appshell-crm', clientType: 'APPSHELL', allowedAppIds: ['crm-web'], attestSecret: secret, status: 'ACTIVE' }, update: { clientType: 'APPSHELL', attestSecret: secret, allowedAppIds: ['crm-web'], status: 'ACTIVE' }, }) .catch(() => undefined); } }