refactor(server): split federation routes into cohesive modules (#9)

routes/federation.ts had grown to 7.6k lines, spanning HTTP route
registration, federated identity resolution, ~30 inbound relay event
processors, DM reconciliation, and rate-limiting internals — too large
to review or hold in context, and awkward to change safely.

Split the implementation into 18 focused modules under routes/federation/
(helpers, events/, handlers/) and keep routes/federation.ts as a thin
barrel that re-exports the public API and composes the HTTP registrars
into federationRoutes(). No import paths change anywhere else.

Pure move, no behavior change:
- 61/61 named functions byte-identical; only deltas are 2 dynamic-import
  paths adjusted for the new directory depth
- public export surface unchanged (barrel re-exports all 22 symbols)
- all 30 endpoints preserved (identical verb+path set)
- typecheck, build, and full server suite (790 tests) green

Docs: update federation.md source-file map; add split design doc.
This commit is contained in:
TheZwiss
2026-07-10 02:08:03 +02:00
committed by GitHub
parent 180228f2d1
commit 94fe73522d
21 changed files with 8063 additions and 7620 deletions
@@ -0,0 +1,72 @@
import { schema } from '../../db/index.js';
import { getOurOrigin } from '../../utils/federationAuth.js';
import { or } from 'drizzle-orm';
/** Fields safe to expose to admin callers (everything except hmacSecret). */
export interface SanitizedPeer {
id: string;
origin: string;
instanceName: string | null;
status: string;
lastSeenAt: number | null;
lastFailureAt: number | null;
consecutiveFailures: number;
lastSyncedAt: number | null;
createdAt: number;
rotationInProgress: boolean;
secretRotatedAt: number | null;
autoRotateIntervalDays: number;
needsAttentionReason: 'auth_failures' | 'peer_reset_detected' | 'repeer_incomplete' | null;
}
export function sanitizePeer(row: typeof schema.federationPeers.$inferSelect): SanitizedPeer {
return {
id: row.id,
origin: row.origin,
instanceName: row.instanceName,
status: row.status,
lastSeenAt: row.lastSeenAt,
lastFailureAt: row.lastFailureAt,
consecutiveFailures: row.consecutiveFailures,
lastSyncedAt: row.lastSyncedAt,
createdAt: row.createdAt,
rotationInProgress: row.pendingHmacSecret !== null,
secretRotatedAt: row.secretRotatedAt,
autoRotateIntervalDays: row.autoRotateIntervalDays,
needsAttentionReason: row.needsAttentionReason as SanitizedPeer['needsAttentionReason'],
};
}
/**
* Determine this instance's public origin for the peering handshake.
*
* Delegates to `getOurOrigin()` so the handshake `sourceOrigin` is IDENTICAL to
* the `X-Federation-Origin` value used for authenticated S2S requests. This
* honors `PUBLIC_ORIGIN` (getOurOrigin's precedence: PUBLIC_ORIGIN →
* `https://${DOMAIN}` → `http://localhost:${PORT}`). Using DOMAIN directly here
* previously desynced the responder's peer-row key from the auth origin,
* causing permanent `403 Not peered` whenever PUBLIC_ORIGIN != https://DOMAIN.
*/
export function resolveLocalOrigin(): string {
return getOurOrigin();
}
/**
* Validate that a string is a well-formed HTTP(S) URL origin.
* Returns the normalized origin (no trailing slash) or null if invalid.
*/
export function validateOrigin(raw: string): string | null {
try {
const url = new URL(raw);
if (url.protocol !== 'http:' && url.protocol !== 'https:') return null;
if (url.protocol === 'http:' && !['localhost', '127.0.0.1'].includes(url.hostname)) {
return null;
}
return url.origin;
} catch {
return null;
}
}