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
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
FederationAuthGuard(CanActivate) for inbound federation API routes onapps/gateway1.3.6.1.4.1.99999.1(grantId) and1.3.6.1.4.1.99999.2(subjectUserId)getGrantWithPeer), assertsstatus === 'active', and validates cert serial against stored peer cert serial (defense-in-depth)FederationContexttorequest.federationContexton success@mosaicstack/types, not raw NestJS exceptionsFederationModuleproviders and exported for@UseGuards()in M3-05/06/07 verb controllersCloses #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
certSerialonfederation_peersis 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.sockettyped asPartial<tls.TLSSocket>-- presence ofgetPeerCertificateis checked at runtime, allowing safe fallback for plain HTTP connections in dev/test.OID helper location:
federation/oid.util.ts(not insideserver/) so it can be reused by future client-side verification without a circular dependency.Test plan
pnpm typecheck-- 38 package typecheck tasks passpnpm lint-- 21 lint tasks passpnpm format:check-- all files use Prettier code stylepnpm --filter @mosaicstack/gateway test-- 28 test files pass (450 tests), 5 skipped (DB integration)Generated with Claude Code
71c7b85026to0af3e218a1