/speckit-plan output for 002-saas-integration: technical context and constitution gate check (all PASS), Phase 0 research (9 decisions: signed-token format, replay resistance via Redis jti tracking, clock skew tolerance, rotation via previous-credential transition window, per-integration/per-user rate limiting, strict unknown-field rejection, non-leaking error responses, and reconciling the placeholder Prisma schema with docs/06's real Product shape), Phase 1 data model (Product revision, ProductIntegration, CustomerReference, AuditLog reuse for auth events), the inbound request validation contract (9 fixed steps + admin lifecycle endpoints), and an 8-scenario quickstart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
8.3 KiB
Implementation Plan: SaaS Product Integration & Inbound Request Trust
Branch: 002-saas-integration | Date: 2026-08-21 | Spec: spec.md
Input: Feature specification from specs/002-saas-integration/spec.md
Summary
Add the trust boundary between an integrating SaaS product and SupportHub: a ProductIntegration
record per product (credential reference, allowed scope, rotation/revocation state), a signed
short-lived-token service-to-service auth mechanism validated on every inbound request via a new
Fastify plugin, per-integration/per-user rate limiting, and admin CRUD for onboarding/rotating/
revoking an integration. Populates the existing RequestContext (productId/customerId/
tenantId) so every later module can trust that context without re-validating it.
Technical Context
Language/Version: TypeScript 5.4 / Node.js 20+, matching the rest of the repo.
Primary Dependencies: Fastify (new plugin), @fastify/rate-limit (already a dependency,
currently registered with a single global limit — extended with a per-route keyGenerator),
Zod (inbound contract schema), Prisma (new models), Node's built-in crypto (HMAC signing/
verification for the signed-token mechanism — no new signing library needed).
Storage: PostgreSQL via Prisma — adds ProductIntegration and CustomerReference models,
and aligns the existing placeholder Product model with docs/06-database-schema.md's real
shape (see research.md "Reconciling the placeholder Prisma schema").
Testing: Vitest — unit tests for token verification/scope-checking logic, integration tests
for the full inbound-request preHandler against a real Postgres (per existing
docker-compose.test.yml, wired up by the 001-ci-pipeline feature).
Target Platform: Same Fastify modular monolith; this feature adds one new Fastify plugin and one module's worth of admin endpoints — no new service, no new deployable unit.
Project Type: Backend service — single project, no frontend changes in this feature (an admin UI for onboarding/rotating integrations is Phase 10 per the roadmap; this feature only needs the API surface admin tooling will eventually call).
Performance Goals: Credential/token validation must not add meaningfully to request latency — target under 10ms added overhead per request for the signed-token verification path (in-process HMAC check, no external call).
Constraints: MUST NOT log or persist raw credential/token values (FR-007, FR-010 from spec.md); MUST reject unknown fields on the inbound contract (FR-008); rate limiting MUST be adjustable without a deploy (Constitution Principle II).
Scale/Scope: One inbound endpoint contract (the ProductToSupportHubRequest shape from
doc 02 §3, extended with the reserved idempotencyKey field from spec.md FR-012), plus admin
endpoints for integration lifecycle (register, rotate, revoke, list, get). Does not include
ticket creation itself — this feature validates and trusts the request; acting on it (creating a
ticket) is the ticketing feature.
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
| Principle / Section | Check | Result |
|---|---|---|
| I. SaaS Is the Sole Identity & Access Authority | CustomerReference stores only externalUserId/externalTenantId as references, never a copy of SaaS user/tenant data; SupportHub never authenticates the end customer itself, only the product's service-to-service credential. |
PASS |
| II. Configuration Over Hardcoding | Rate limits, integration status, and credential scope are all admin-editable data (ProductIntegration.allowedScope, plus a new rate-limit config), not hardcoded. |
PASS |
| III. Layered Architecture With Enforced Module Boundaries | New Fastify plugin is infrastructure (like auth.plugin.ts), not a module; it only reads validated data via the repository layer of the integration-management module — no controller touches Prisma directly. |
PASS |
| IV. AI Recommends, Deterministic Policy Decides | Not applicable — no AI involved in this feature. | PASS — N/A |
| V. Evidence-Based Verification | Not applicable — no resolution/verification concept in this feature. | PASS — N/A |
| VI. Durable Audit & History | Every auth attempt (success/failure) is written to the existing AuditLog model (FR-007) — reused, not duplicated. |
PASS |
| VII. Concurrency-Safe, Durable Job Handling | Credential rotation must not race with an in-flight validation using the old credential — handled by checking both old/new credential validity within the transition window rather than an atomic cutover (see research.md). No setTimeout-based expiry. |
PASS |
| VIII. Problem and Ticket Are Separate, Related Entities | Not applicable — this feature predates both entities. | PASS — N/A |
| Technology & Platform Constraints | Uses Fastify/Zod/Prisma/Node crypto only — no new runtime dependency added. | PASS |
No violations requiring Complexity Tracking justification.
Post-Design Constitution Re-check
All gates above remain PASS after Phase 1 design (data-model.md, contracts/, quickstart.md).
One design detail worth calling out explicitly: ProductIntegration.authMechanism is stored as
free text, not an enum restricted to signed_token — this is deliberate so a future integration
requiring OAuth2 or mTLS (both still valid per docs/02 §4) doesn't require a schema migration,
keeping this decision genuinely configuration-driven (Principle II) rather than a hardcoded
assumption that every integration uses the same mechanism forever.
Project Structure
Documentation (this feature)
specs/002-saas-integration/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
├── contracts/ # Phase 1 output (inbound request contract + admin endpoints)
└── tasks.md # Phase 2 output (/speckit-tasks — not created here)
Source Code (repository root)
supporthub-api/
├── prisma/
│ └── schema.prisma # MODIFIED — align Product with docs/06, add
│ ProductIntegration, CustomerReference
├── src/
│ ├── plugins/
│ │ ├── product-integration-auth.plugin.ts # NEW — validates inbound signed tokens,
│ │ │ scope, product status; populates reqContext
│ │ └── rate-limit.plugin.ts # MODIFIED — per-route keyGenerator support
│ ├── common/
│ │ └── types/
│ │ └── request-context.types.ts # UNCHANGED — productId/customerId/tenantId
│ │ already present, this feature just populates them
│ └── modules/
│ └── catalog/
│ └── products/ # EXTENDED (existing scaffold) — adds
│ ├── controller/ integration lifecycle endpoints alongside
│ ├── service/ existing product endpoints, since
│ ├── repository/ ProductIntegration is 1:1 with Product
│ ├── schema/ per docs/06
│ ├── mapper/
│ └── types/
└── tests/
├── unit/ # token verification, scope-check logic
└── integration/ # full preHandler against real Postgres
Structure Decision: Single project, extending the existing catalog/products module rather
than introducing a new top-level module — docs/07-backend-architecture.md's module list has no
separate "product-integrations" module, and docs/06-database-schema.md nests
ProductIntegration directly under Product's own domain grouping (1:1 relation). The inbound
auth validation itself is cross-cutting request-handling infrastructure, so it lives in
src/plugins/, matching the existing auth.plugin.ts pattern for human/admin JWT auth — these
are two distinct auth concerns (product-to-SupportHub vs. person-to-SupportHub) and stay in
separate plugins rather than merged into one.
Complexity Tracking
No constitution violations — table intentionally omitted.