Implements tasks T026-T032 from specs/002-saas-integration/tasks.md
(User Story 3, P3 - the final piece of this feature) plus Polish.
- New bespoke Redis fixed-window counter (checkRateLimit,
src/infrastructure/cache/rate-limiter.ts) rather than
@fastify/rate-limit's default onRequest-stage hook -- that hook
runs before this feature's preHandler-based auth resolves the
integration/user identity the limit needs to key on. A second
preHandler (checkIntegrationRateLimit) runs after
authenticateProductIntegration on the inbound route, checking the
integration-level limit then the per-user limit independently,
each throwing the existing RateLimitError (429
RATE_LIMIT_EXCEEDED) on breach.
- New integration test (inbound-rate-limit.test.ts) verifies both
limits are enforced independently against a real Postgres/Redis:
a throttled user doesn't affect others, and the integration cap
throttles even when no individual user has hit their own limit.
- Docs: contracts/quickstart updated from the placeholder
"RATE_LIMITED" code to the actual reused RATE_LIMIT_EXCEEDED code;
cleaned up a duplicated paragraph in the admin endpoints section;
added a "SaaS Integration" section to README.md documenting the
inbound contract, admin routes (and their known auth-stub
limitation), and how rate limits are configured.
All 32 tasks in tasks.md are now complete -- all three user stories
(P1 trust boundary, P2 admin lifecycle, P3 rate limiting) are
implemented and covered by integration tests verified against a
live database, in addition to unit tests for the crypto/token
primitives. Full quality gate (typecheck/lint/format/architecture/
unit tests) passes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements tasks T021-T025 from specs/002-saas-integration/tasks.md
(User Story 2, P2): an admin can register, rotate, revoke, and change
the status of a ProductIntegration, and retrieve its audit trail.
- ProductIntegrationsService: register (finds-or-creates the Product
by external id), rotate (dual-credential transition window per
research.md), revoke, updateStatus, getAuditTrail -- each writes
its own AuditLog entry via a new shared
integration-audit-log.repository.ts (extracted from the auth
plugin, which now reuses it instead of writing to Prisma directly).
- Routes: POST /admin/products/:externalProductId/integration,
POST/admin/integrations/:id/rotate|revoke, PATCH .../status,
GET .../audit-trail -- gated by the existing fastify.authenticate
(human/admin JWT) decorator.
- New integration test (product-integrations-admin.test.ts) covers
Quickstart Scenarios 5-7 end-to-end against a real Postgres/Redis:
register+rotate+audit-trail, and revoke-takes-effect-immediately.
Verified passing against a live database.
Known, pre-existing limitation flagged (not fixed here, out of
scope): fastify.authenticate is currently a no-op stub with no real
JWT verification, so these admin endpoints aren't actually
access-controlled yet -- that depends on the unimplemented
identity/auth module. Documented in the contract and checklist notes
so it isn't mistaken for done.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements tasks T001-T020 from specs/002-saas-integration/tasks.md
(Setup, Foundational, and User Story 1 - the P1 MVP: every inbound
request is authenticated and trusted before anything happens).
User Story 2 (admin onboarding/rotation/revocation) and User Story 3
(rate limiting) are not yet implemented (T021-T032 remain).
Schema (prisma/schema.prisma + initial migration):
- Replace the placeholder Product model (leftover starter-template
scaffolding: code/description/ProductStatus enum) with the real
docs/06-database-schema.md shape (externalProductId,
supportEnabled, status).
- Add ProductIntegration (credential ref, rotation/revocation state,
allowed scope, per-integration/per-user rate limits) and
CustomerReference models.
- Align AuditLog to docs/06's shape (actor/actorType/entityType/
entityId/reason/metadata) -- the placeholder shape had no fields
to satisfy this feature's audit requirements.
Auth:
- HMAC-signed short-lived tokens (issue/verify) with jti-based replay
defense via Redis and a bounded clock-skew tolerance.
- Credential secrets are AES-256-GCM encrypted at rest (new required
INTEGRATION_CREDENTIAL_ENCRYPTION_KEY env var) since no secret
manager exists in this stack yet -- see research.md "Credential
storage".
- New product-integration-auth.plugin.ts Fastify plugin runs the
validation order in contracts/inbound-request-contract.md and
populates request.reqContext only on full success; every attempt
(success or failure) is audit-logged without ever persisting the
raw token/credential. Unregistered product and invalid credential
return an identical response (FR-010).
- New POST /v1/support/requests endpoint exercises the boundary
end-to-end (ticket creation itself is a future feature).
Also:
- Fix docker-compose.test.yml's container_name collisions --
discovered while testing this change concurrently is now covered
by an app-level regression test (separate commit).
- Fix test:unit to scope to tests/unit only (it was running the
entire tests/** glob including integration tests) -- this feature's
new integration test makes real Prisma/Redis calls, unlike the
prior instantiation-only checks, so the existing glob-scoping gap
became actually harmful.
- Update Jenkinsfile with the new required credential.
Verified: full quality gate (typecheck/lint/format/architecture/
unit tests) passes; all of User Story 1's quickstart scenarios
manually verified end-to-end against a live server + Postgres +
Redis; the new integration test suite verified against a live
database (not run as part of `npm test`, matches existing
test:integration convention).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>