137 lines
8.3 KiB
Markdown
137 lines
8.3 KiB
Markdown
# 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.*
|