OPER-131
[OPER-QA-15] Product BFF service-token pattern: issuance, rotation, tenant-scope binding, revocation
Band
MEDIUM
Suggested
Justin Cooke
Body
feature_id: FEAT-studio-qa-projects
## Context
Second artifact of the [REDACTED-DOB] PDT integration-mechanism decision. Product BFFs (POR-QA-1 first, later Piper DX / Roster / Pillars / Fiesta) present a **product-scoped service token** to Studio — NOT a user-scoped bearer. This ticket ships the issuance/rotation/binding/revocation surface on the Studio side.
Model: one service token per (product, environment) pair. Token binds to a Studio team-of-teams grouping so it can proxy for any Portico tenant. Revocation invalidates in <60s across all Studio-side caches.
## Acceptance Criteria
- New Prisma model `QaProductServiceToken { id, productSlug, environment, tokenHash, boundTeamGroupId, createdBy, createdAt, revokedAt }` with unique on `(productSlug, environment) WHERE revokedAt IS NULL`
- Migration creates the table + RLS scoped to Studio operator team only
- `POST /api/studio/operant/qa/service-tokens` issues a new token; response contains the raw token exactly once (write-once value), stores only sha256 hash in DB
- `DELETE /api/studio/operant/qa/service-tokens/[id]` marks revoked, invalidates cache
- Middleware at `apps/web/src/middleware.ts` recognizes the `X-Operant-Service-Token` header, looks up by hash, sets `actor.kind = 'service'` + `actor.productSlug` + `actor.boundTeamGroupId` for downstream authz
- Cache TTL 30s; revocation propagates to all instances via existing Redis pub/sub
- Vitest + integration coverage: (a) valid token authenticates; (b) revoked token 401 within 60s; (c) rotation window (both old and new active for 5min grace) works; (d) token cannot access teams outside its bound group
Attachments
Loading attachments…
Comments
Loading comments…