feat(federation): mTLS AuthGuard with OID-based grant resolution (FED-M3-03)
Adds FederationAuthGuard that validates inbound mTLS client certs on federation API routes. Extracts custom OIDs (grantId, subjectUserId), loads the grant+peer from DB in one query, asserts active status, and validates cert serial as defense-in-depth. Attaches FederationContext to requests on success and uses federation wire-format error envelopes (not raw NestJS exceptions) for 401/403 responses. New files: - apps/gateway/src/federation/oid.util.ts — shared OID extraction (no dupe ASN.1 logic) - apps/gateway/src/federation/server/federation-auth.guard.ts — guard impl - apps/gateway/src/federation/server/federation-context.ts — FederationContext type + module augment - apps/gateway/src/federation/server/index.ts — barrel export - apps/gateway/src/federation/server/__tests__/federation-auth.guard.spec.ts — 11 unit tests Modified: - apps/gateway/src/federation/grants.service.ts — adds getGrantWithPeer() with join - apps/gateway/src/federation/federation.module.ts — registers FederationAuthGuard as provider Closes #462 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,12 +10,14 @@
|
||||
*/
|
||||
|
||||
import { ConflictException, Inject, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { type Db, and, eq, federationGrants } from '@mosaicstack/db';
|
||||
import { type Db, and, eq, federationGrants, federationPeers } from '@mosaicstack/db';
|
||||
import { DB } from '../database/database.module.js';
|
||||
import { parseFederationScope } from './scope-schema.js';
|
||||
import type { CreateGrantDto, ListGrantsDto } from './grants.dto.js';
|
||||
|
||||
export type Grant = typeof federationGrants.$inferSelect;
|
||||
export type Peer = typeof federationPeers.$inferSelect;
|
||||
export type GrantWithPeer = Grant & { peer: Peer };
|
||||
|
||||
@Injectable()
|
||||
export class GrantsService {
|
||||
@@ -60,6 +62,33 @@ export class GrantsService {
|
||||
return grant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a single grant by ID, joined with its associated peer row.
|
||||
* Used by FederationAuthGuard to perform grant status + cert serial checks
|
||||
* in a single DB round-trip.
|
||||
*
|
||||
* Throws NotFoundException if the grant does not exist.
|
||||
* Throws NotFoundException if the associated peer row is missing (data integrity issue).
|
||||
*/
|
||||
async getGrantWithPeer(id: string): Promise<GrantWithPeer> {
|
||||
const rows = await this.db
|
||||
.select()
|
||||
.from(federationGrants)
|
||||
.innerJoin(federationPeers, eq(federationGrants.peerId, federationPeers.id))
|
||||
.where(eq(federationGrants.id, id))
|
||||
.limit(1);
|
||||
|
||||
const row = rows[0];
|
||||
if (!row) {
|
||||
throw new NotFoundException(`Grant ${id} not found`);
|
||||
}
|
||||
|
||||
return {
|
||||
...row.federation_grants,
|
||||
peer: row.federation_peers,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* List grants with optional filters for peerId, subjectUserId, and status.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user