Files
support_backend/specs/002-saas-integration/plan.md
T

137 lines
8.3 KiB
Markdown
Raw Normal View History

# Implementation Plan: SaaS Product Integration & Inbound Request Trust
**Branch**: `002-saas-integration` | **Date**: 2026-08-21 | **Spec**: [spec.md](./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)
```text
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)
```text
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.*