feat: verify context attestations via ES256/JWKS + rename client to appshell-crm
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>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Global, Module, type OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { ContextAttestationVerifier } from './context-attestation';
|
||||
import { PrismaClientRegistry, PrismaNonceStore } from './attestation-stores';
|
||||
import { PrismaClientRegistry, PrismaNonceStore, JwksPublicKeyResolver } from './attestation-stores';
|
||||
import { ContextAttestationGuard } from './context-attestation.guard';
|
||||
|
||||
/**
|
||||
@@ -14,11 +14,12 @@ import { ContextAttestationGuard } from './context-attestation.guard';
|
||||
providers: [
|
||||
PrismaClientRegistry,
|
||||
PrismaNonceStore,
|
||||
JwksPublicKeyResolver,
|
||||
{
|
||||
provide: ContextAttestationVerifier,
|
||||
useFactory: (reg: PrismaClientRegistry, nonces: PrismaNonceStore) =>
|
||||
new ContextAttestationVerifier(reg, nonces, { audience: process.env.IIOS_ATTESTATION_AUDIENCE ?? 'iios-core' }),
|
||||
inject: [PrismaClientRegistry, PrismaNonceStore],
|
||||
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,
|
||||
],
|
||||
@@ -35,9 +36,9 @@ export class AttestationModule implements OnModuleInit {
|
||||
if (!secret) return;
|
||||
await this.prisma.iiosClientRegistry
|
||||
.upsert({
|
||||
where: { clientId: 'crm-support-widget' },
|
||||
create: { clientId: 'crm-support-widget', clientType: 'SUPPORT_BFF', allowedAppIds: ['crm-web'], attestSecret: secret, status: 'ACTIVE' },
|
||||
update: { attestSecret: secret, allowedAppIds: ['crm-web'], status: 'ACTIVE' },
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user