feat(federation): mTLS AuthGuard with OID-based grant resolution (FED-M3-03) #509

Merged
jason.woltje merged 2 commits from feat/federation-m3-auth-guard into main 2026-04-25 13:27:21 +00:00
Owner

Summary

  • Implements FederationAuthGuard (CanActivate) for inbound federation API routes on apps/gateway
  • Validates mTLS client certificates: extracts OIDs 1.3.6.1.4.1.99999.1 (grantId) and 1.3.6.1.4.1.99999.2 (subjectUserId)
  • Loads grant + peer in a single DB join query (getGrantWithPeer), asserts status === 'active', and validates cert serial against stored peer cert serial (defense-in-depth)
  • Attaches FederationContext to request.federationContext on success
  • All rejection paths use the federation wire-format error envelope from @mosaicstack/types, not raw NestJS exceptions
  • Guard is registered in FederationModule providers and exported for @UseGuards() in M3-05/06/07 verb controllers

Closes #462

PRD AC-3 (cross-user isolation prerequisite): checked off -- the guard binds cert to grant to subjectUserId at the transport layer, preventing any cross-user data access by a peer that does not hold an active grant for the target user.

Key decisions

Cert-serial check: Kept. The stored certSerial on federation_peers is matched against the inbound cert serial. This is defense-in-depth: even if the mTLS CA is compromised or a TLS terminator forwards an arbitrary cert, the serial must match what was registered at enrollment. No additional DB round-trip needed (peer is loaded in the same join as the grant).

FastifyRequest typing: request.raw.socket typed as Partial<tls.TLSSocket> -- presence of getPeerCertificate is checked at runtime, allowing safe fallback for plain HTTP connections in dev/test.

OID helper location: federation/oid.util.ts (not inside server/) so it can be reused by future client-side verification without a circular dependency.

Test plan

  • pnpm typecheck -- 38 package typecheck tasks pass
  • pnpm lint -- 21 lint tasks pass
  • pnpm format:check -- all files use Prettier code style
  • pnpm --filter @mosaicstack/gateway test -- 28 test files pass (450 tests), 5 skipped (DB integration)
  • New guard tests: 11/11 pass (missing cert 401, parse failure 401, missing OIDs 401, grant not found 403, pending/revoked/expired status 403, serial mismatch 403, happy path context attach)

Generated with Claude Code

## Summary - Implements `FederationAuthGuard` (`CanActivate`) for inbound federation API routes on `apps/gateway` - Validates mTLS client certificates: extracts OIDs `1.3.6.1.4.1.99999.1` (grantId) and `1.3.6.1.4.1.99999.2` (subjectUserId) - Loads grant + peer in a single DB join query (`getGrantWithPeer`), asserts `status === 'active'`, and validates cert serial against stored peer cert serial (defense-in-depth) - Attaches `FederationContext` to `request.federationContext` on success - All rejection paths use the federation wire-format error envelope from `@mosaicstack/types`, not raw NestJS exceptions - Guard is registered in `FederationModule` providers and exported for `@UseGuards()` in M3-05/06/07 verb controllers Closes #462 PRD AC-3 (cross-user isolation prerequisite): checked off -- the guard binds cert to grant to subjectUserId at the transport layer, preventing any cross-user data access by a peer that does not hold an active grant for the target user. ## Key decisions **Cert-serial check**: Kept. The stored `certSerial` on `federation_peers` is matched against the inbound cert serial. This is defense-in-depth: even if the mTLS CA is compromised or a TLS terminator forwards an arbitrary cert, the serial must match what was registered at enrollment. No additional DB round-trip needed (peer is loaded in the same join as the grant). **FastifyRequest typing**: `request.raw.socket` typed as `Partial<tls.TLSSocket>` -- presence of `getPeerCertificate` is checked at runtime, allowing safe fallback for plain HTTP connections in dev/test. **OID helper location**: `federation/oid.util.ts` (not inside `server/`) so it can be reused by future client-side verification without a circular dependency. ## Test plan - [x] `pnpm typecheck` -- 38 package typecheck tasks pass - [x] `pnpm lint` -- 21 lint tasks pass - [x] `pnpm format:check` -- all files use Prettier code style - [x] `pnpm --filter @mosaicstack/gateway test` -- 28 test files pass (450 tests), 5 skipped (DB integration) - [x] New guard tests: 11/11 pass (missing cert 401, parse failure 401, missing OIDs 401, grant not found 403, pending/revoked/expired status 403, serial mismatch 403, happy path context attach) Generated with Claude Code
jason.woltje force-pushed feat/federation-m3-auth-guard from 71c7b85026 to 0af3e218a1 2026-04-25 11:34:59 +00:00 Compare
jason.woltje merged commit bd83f86740 into main 2026-04-25 13:27:21 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#509