feat(federation): admin controller + CLI federation commands (FED-M2-08) #498

Merged
jason.woltje merged 2 commits from feat/federation-m2-cli into main 2026-04-22 04:39:47 +00:00
Owner

Summary

Implements FED-M2-08: two-part delivery of the federation admin surface.

Gateway — FederationController

  • apps/gateway/src/federation/federation-admin.dto.ts: CreatePeerKeypairDto, StorePeerCertDto, GenerateEnrollmentTokenDto, RevokeGrantBodyDto
  • apps/gateway/src/federation/federation.controller.ts: FederationController under /api/admin/federation with AdminGuard on all routes
    • Grant CRUD: POST /grants, GET /grants, GET /grants/:id, PATCH /grants/:id/revoke — delegate to GrantsService
    • Token generation: POST /grants/:id/tokens — delegates to EnrollmentService, returns { token, expiresAt, enrollmentUrl }
    • Peer listing: GET /peers — direct DB query
    • Peer keypair: POST /peers/keypair — webcrypto EC P-256 key gen + @peculiar/x509 CSR + sealClientKey + insert pending peer row
    • Peer cert storage: PATCH /peers/:id/cert — parses cert with X509Certificate, extracts serial/notAfter, sets state=active
  • apps/gateway/src/federation/federation.module.ts: registers FederationController

CLI — mosaic federation

  • packages/mosaic/src/commands/federation.ts: registerFederationCommand with full grant and peer subcommand trees
    • grant create/list/show/revoke/token
    • peer list/add (add runs full enrollment flow: generate keypair → submit CSR to remote → store cert)
    • Admin token resolved from -t flag or meta.json.adminToken
    • --json flag for machine-readable output
  • packages/mosaic/src/cli.ts: registers the new command

Tests

  • apps/gateway/src/federation/__tests__/federation.controller.spec.ts: unit tests covering listGrants, createGrant, generateToken (enrollmentUrl shape), listPeers

Closes #461

Test plan

  • pnpm typecheck — passes (38/38)
  • pnpm lint — passes (21/21)
  • pnpm format:check — passes (all files)
  • pnpm test — passes (439 tests, 4 skipped integration)
  • Integration smoke test in M2-09/M2-10

🤖 Generated with Claude Code

## Summary Implements FED-M2-08: two-part delivery of the federation admin surface. ### Gateway — FederationController - `apps/gateway/src/federation/federation-admin.dto.ts`: `CreatePeerKeypairDto`, `StorePeerCertDto`, `GenerateEnrollmentTokenDto`, `RevokeGrantBodyDto` - `apps/gateway/src/federation/federation.controller.ts`: `FederationController` under `/api/admin/federation` with `AdminGuard` on all routes - Grant CRUD: `POST /grants`, `GET /grants`, `GET /grants/:id`, `PATCH /grants/:id/revoke` — delegate to `GrantsService` - Token generation: `POST /grants/:id/tokens` — delegates to `EnrollmentService`, returns `{ token, expiresAt, enrollmentUrl }` - Peer listing: `GET /peers` — direct DB query - Peer keypair: `POST /peers/keypair` — webcrypto EC P-256 key gen + `@peculiar/x509` CSR + `sealClientKey` + insert pending peer row - Peer cert storage: `PATCH /peers/:id/cert` — parses cert with `X509Certificate`, extracts serial/notAfter, sets state=active - `apps/gateway/src/federation/federation.module.ts`: registers `FederationController` ### CLI — `mosaic federation` - `packages/mosaic/src/commands/federation.ts`: `registerFederationCommand` with full `grant` and `peer` subcommand trees - `grant create/list/show/revoke/token` - `peer list/add` (add runs full enrollment flow: generate keypair → submit CSR to remote → store cert) - Admin token resolved from `-t` flag or `meta.json.adminToken` - `--json` flag for machine-readable output - `packages/mosaic/src/cli.ts`: registers the new command ### Tests - `apps/gateway/src/federation/__tests__/federation.controller.spec.ts`: unit tests covering listGrants, createGrant, generateToken (enrollmentUrl shape), listPeers Closes #461 ## Test plan - [ ] `pnpm typecheck` — passes (38/38) - [ ] `pnpm lint` — passes (21/21) - [ ] `pnpm format:check` — passes (all files) - [ ] `pnpm test` — passes (439 tests, 4 skipped integration) - [ ] Integration smoke test in M2-09/M2-10 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jason.woltje added 1 commit 2026-04-22 04:33:22 +00:00
feat(federation): admin controller + mosaic CLI federation commands (FED-M2-08)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
8eff67a2bf
Implements the two halves of FED-M2-08:

Gateway (apps/gateway/src/federation/):
- federation-admin.dto.ts: CreatePeerKeypairDto, StorePeerCertDto,
  GenerateEnrollmentTokenDto, RevokeGrantBodyDto
- federation.controller.ts: FederationController under
  /api/admin/federation with AdminGuard on all routes.
  Grant CRUD (create, list, get, revoke) delegating to GrantsService.
  Token generation delegating to EnrollmentService + returning enrollmentUrl.
  Peer listing via direct DB query.
  Peer keypair generation via webcrypto + @peculiar/x509 CSR generation.
  Peer cert storage with X509Certificate serial/notAfter extraction.
- federation.module.ts: register FederationController

CLI (packages/mosaic/src/commands/federation.ts):
- mosaic federation (alias: fed) command group
- grant create/list/show/revoke/token subcommands
- peer list/add subcommands (add runs full enrollment flow)
- Admin token resolved from -t flag or meta.json adminToken
- packages/mosaic/src/cli.ts: register registerFederationCommand

Tests (apps/gateway/src/federation/__tests__/federation.controller.spec.ts):
- listGrants, createGrant, generateToken, listPeers coverage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jason.woltje added 1 commit 2026-04-22 04:38:23 +00:00
fix(federation): use value imports for DTO classes to preserve ValidationPipe metatypes (FED-M2-08)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
c5ba5411b7
Author
Owner

Independent Code Review — REQUEST_CHANGES

Reviewed by Opus 4.7 (independent agent, no shared context with author).

HIGH (must fix before merge)

H1 — import type on DTO classes breaks ValidationPipe (federation.controller.ts:45-51)

All six Body/Query DTO imports use import type, which TypeScript erases at compile time. NestJS's global ValidationPipe({ whitelist:true, forbidNonWhitelisted:true }) relies on reflect-metadata to inspect the class constructor's parameter types. With import type, the metatype resolves to Object, and ValidationPipe rejects every property as non-whitelisted (HTTP 400 at runtime). Unit tests miss this because they bypass Nest's DI/metatype lookup. This is the exact pattern that caused bug #436.

Fix: Change import type { CreateGrantDto, ListGrantsDto } and import type { CreatePeerKeypairDto, ... } to value imports.

MEDIUM (defer ok)

  • M1: CLI peer add posts { csrPem } — field name matches RedeemEnrollmentTokenDto.csrPem exactly ✓
  • M2: Route ordering (POST /peers/keypair vs PATCH /peers/:id/cert) — different methods, no conflict ✓
  • M3: privatePem briefly in heap after sealing — no logging, acceptable
  • M4: CLI only restricts to http/https, no private-IP SSRF block — acceptable for admin CLI

Confirmed OK

  • AdminGuard at class level — all 8 routes protected ✓
  • EnrollmentController's POST /api/federation/enrollment/:token not duplicated ✓
  • enrollmentUrl reads BETTER_AUTH_URL from env ✓
  • sealClientKey(privatePem) single-arg matches util ✓
  • No any, no hardcoded secrets, ESM .js imports, stack file untouched ✓
  • 439 tests pass ✓

Verdict: REQUEST_CHANGES — fix import type → value imports on DTO lines, then APPROVE.

## Independent Code Review — REQUEST_CHANGES Reviewed by Opus 4.7 (independent agent, no shared context with author). ### HIGH (must fix before merge) **H1 — `import type` on DTO classes breaks ValidationPipe** (`federation.controller.ts:45-51`) All six Body/Query DTO imports use `import type`, which TypeScript erases at compile time. NestJS's global `ValidationPipe({ whitelist:true, forbidNonWhitelisted:true })` relies on reflect-metadata to inspect the class constructor's parameter types. With `import type`, the metatype resolves to `Object`, and ValidationPipe rejects every property as non-whitelisted (HTTP 400 at runtime). Unit tests miss this because they bypass Nest's DI/metatype lookup. This is the exact pattern that caused bug #436. **Fix**: Change `import type { CreateGrantDto, ListGrantsDto }` and `import type { CreatePeerKeypairDto, ... }` to value imports. ### MEDIUM (defer ok) - **M1**: CLI `peer add` posts `{ csrPem }` — field name matches `RedeemEnrollmentTokenDto.csrPem` exactly ✓ - **M2**: Route ordering (POST /peers/keypair vs PATCH /peers/:id/cert) — different methods, no conflict ✓ - **M3**: privatePem briefly in heap after sealing — no logging, acceptable - **M4**: CLI only restricts to http/https, no private-IP SSRF block — acceptable for admin CLI ### Confirmed OK - AdminGuard at class level — all 8 routes protected ✓ - EnrollmentController's `POST /api/federation/enrollment/:token` not duplicated ✓ - `enrollmentUrl` reads `BETTER_AUTH_URL` from env ✓ - `sealClientKey(privatePem)` single-arg matches util ✓ - No `any`, no hardcoded secrets, ESM `.js` imports, stack file untouched ✓ - 439 tests pass ✓ ### Verdict: **REQUEST_CHANGES** — fix import type → value imports on DTO lines, then APPROVE.
Author
Owner

Re-Review — APPROVE

H1 (import type → value imports): RESOLVED

All six DTO class imports now use plain value imports (no type keyword). ValidationPipe metatype lookup will work correctly at runtime.

Gates: typecheck 38/38, test 439/439 passed.

APPROVE — ready to merge.

## Re-Review — APPROVE **H1 (import type → value imports): RESOLVED** All six DTO class imports now use plain value imports (no `type` keyword). ValidationPipe metatype lookup will work correctly at runtime. Gates: typecheck 38/38, test 439/439 passed. **APPROVE — ready to merge.**
jason.woltje merged commit 74fe60d8d6 into main 2026-04-22 04:39:47 +00:00
jason.woltje deleted branch feat/federation-m2-cli 2026-04-22 04:39:47 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#498