Files
support_backend/specs/002-saas-integration/plan.md
T
saqib mirandClaude Sonnet 5 a7216b7531 docs: plan and design artifacts for SaaS integration feature
/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>
2026-08-21 17:45:02 +05:30

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.