e39caa3c80
Proof #3 now supports the production asymmetric path, not just the dev shared secret. - context-attestation.ts: PublicKeyResolverPort seam; verify() picks ES256 (JWKS by kid) when the client has a jwksUri, else HS256 (dev). signDevAttestationES256 helper. - attestation-stores.ts: JwksPublicKeyResolver (jwks-rsa, one cached client per URI, refetch on rotation, fail-closed to NO_KEY). - attestation.module.ts: inject the resolver into the verifier; dev-seed the client as clientType APPSHELL (it is the CRM browser-flow parent per the July-12 notes). - rename the registered client crm-support-widget -> appshell-crm (the name reflects the attesting parent, not a widget). - specs: ES256 (valid via JWKS, wrong key -> BAD_SIGNATURE, unknown kid -> NO_KEY, no resolver -> NO_KEY) + two stolen-token gate cases (wrong app binding -> APP_MISMATCH, wrong audience -> WRONG_AUDIENCE). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
46 lines
2.0 KiB
TypeScript
46 lines
2.0 KiB
TypeScript
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<void> {
|
|
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);
|
|
}
|
|
}
|