import { BadRequestException, Body, Controller, Get, Headers, Param, Put } from '@nestjs/common'; import { SessionVerifier } from '../platform/session.verifier'; import { ActorResolver, type MessagePrincipal } from '../identity/actor.resolver'; import { ProviderCredentialService } from './provider-credential.service'; import { TwilioCredentialsDto } from './provider-credential.dto'; const SUPPORTED = new Set(['TWILIO_SMS']); /** * Per-scope BYO integration credentials. The caller's attested session decides the scope, so a * tenant can only read/write ITS OWN provider credentials. The secret is sealed by the service; * GET returns a masked status (never the token). be-crm gates this behind tenant-admin policy. */ @Controller('v1/providers') export class ProviderCredentialController { constructor( private readonly session: SessionVerifier, private readonly actors: ActorResolver, private readonly credentials: ProviderCredentialService, ) {} @Put(':providerType/credentials') async put( @Param('providerType') providerType: string, @Body() body: TwilioCredentialsDto, @Headers('authorization') authorization?: string, ) { this.assertSupported(providerType); const scope = await this.actors.resolveScope(this.principal(authorization)); await this.credentials.upsert(scope.id, providerType, { accountSid: body.accountSid, authToken: body.authToken, fromNumber: body.fromNumber, }); return this.credentials.status(scope.id, providerType); } @Get(':providerType/credentials') async get(@Param('providerType') providerType: string, @Headers('authorization') authorization?: string) { this.assertSupported(providerType); const scope = await this.actors.resolveScope(this.principal(authorization)); return this.credentials.status(scope.id, providerType); } private assertSupported(providerType: string): void { if (!SUPPORTED.has(providerType)) throw new BadRequestException(`unsupported providerType: ${providerType}`); } private principal(authorization?: string): MessagePrincipal { const token = (authorization ?? '').replace(/^Bearer\s+/i, ''); if (!token) throw new BadRequestException('Authorization bearer token is required'); return this.session.verify(token); } }