refactor(server): dedupe federation rate limiters and response signing (#10)

Phase C cleanup follow-up to the routes/federation split (#9). Behavior-
preserving; full server suite (790 tests) green.

A) rateLimits.ts: the four near-identical sliding-window limiters
   (accept/relay/lookup/ensure) and their duplicated prune loops collapse
   into one createLimiter(windowMs, max) factory. Per-call and periodic-
   sweep semantics are preserved exactly, including that lookup buckets are
   pruned per-call but never swept (unchanged from before). 177 -> 101 lines.

B) Extract sendSignedJson(reply, payload, hmacSecret) — the single
   definition of how this instance signs an S2S JSON response — and use it
   in the /epoch and /verify-attach-proof|reattach handlers, replacing two
   copies of the build-headers-and-send boilerplate.
This commit is contained in:
TheZwiss
2026-07-10 02:23:14 +02:00
committed by GitHub
parent 94fe73522d
commit c79bf91398
4 changed files with 101 additions and 168 deletions
@@ -3,7 +3,8 @@ import { config } from '../../../config.js';
import { getDb, getRawDb, schema } from '../../../db/index.js';
import { authenticate } from '../../../utils/auth.js';
import { fetchHomeProfileByHomeId, verifyAttachProofWithPeer } from '../../../utils/federationAttach.js';
import { buildFederationHeaders, getOurOrigin, parseFederationHeaders, verifyPeerSignature } from '../../../utils/federationAuth.js';
import { parseFederationHeaders, verifyPeerSignature } from '../../../utils/federationAuth.js';
import { sendSignedJson } from './signedResponse.js';
import { sanitizeUser } from '../../../utils/sanitize.js';
import { collectProfileBroadcastTargetIds } from '../../../utils/userDeletion.js';
import { connectionManager } from '../../../ws/handler.js';
@@ -72,17 +73,8 @@ export function registerAttachRoutes(app: FastifyInstance): void {
// 2. Sign every downstream response with the peer's shared secret so the
// caller can trust the identity (or the fail-closed verdict) it carries.
const sendSigned = (payload: { valid: false } | { valid: true; homeUserId: string; username: string }): FastifyReply => {
const responseBody = JSON.stringify(payload);
const sigHeaders = buildFederationHeaders(responseBody, peer.hmacSecret, getOurOrigin());
reply.headers({
'X-Federation-Signature': sigHeaders['X-Federation-Signature'],
'X-Federation-Timestamp': sigHeaders['X-Federation-Timestamp'],
'X-Federation-Nonce': sigHeaders['X-Federation-Nonce'],
'Content-Type': 'application/json',
});
return reply.code(200).send(responseBody);
};
const sendSigned = (payload: { valid: false } | { valid: true; homeUserId: string; username: string }): FastifyReply =>
sendSignedJson(reply, payload, peer.hmacSecret);
// 3. Validate the token shape (64 hex chars, as minted by attach-proof).
const rawToken = (request.body as { token?: unknown } | null)?.token;
@@ -1,7 +1,8 @@
import path from 'node:path';
import { config } from '../../../config.js';
import { getDb, getRawDb, schema } from '../../../db/index.js';
import { buildFederationHeaders, getOurOrigin, parseFederationHeaders, verifyPeerSignature } from '../../../utils/federationAuth.js';
import { getOurOrigin, parseFederationHeaders, verifyPeerSignature } from '../../../utils/federationAuth.js';
import { sendSignedJson } from './signedResponse.js';
import { getInstanceId } from '../../../utils/federationEpoch.js';
import { getDmParticipants } from '../../../utils/federationOutbox.js';
import { deleteAttachmentFiles } from '../../../utils/fileCleanup.js';
@@ -285,15 +286,7 @@ export function registerRelayRoutes(app: FastifyInstance): void {
}
// 4. Sign the response body with the peer's shared secret and return it.
const responseBody = JSON.stringify({ instanceId: getInstanceId() });
const sigHeaders = buildFederationHeaders(responseBody, peer.hmacSecret, getOurOrigin());
reply.headers({
'X-Federation-Signature': sigHeaders['X-Federation-Signature'],
'X-Federation-Timestamp': sigHeaders['X-Federation-Timestamp'],
'X-Federation-Nonce': sigHeaders['X-Federation-Nonce'],
'Content-Type': 'application/json',
});
return reply.code(200).send(responseBody);
return sendSignedJson(reply, { instanceId: getInstanceId() }, peer.hmacSecret);
},
);
@@ -0,0 +1,24 @@
import type { FastifyReply } from 'fastify';
import { buildFederationHeaders, getOurOrigin } from '../../../utils/federationAuth.js';
/**
* Serialize `payload` as JSON and send it as a `200` response signed with the
* peer's shared HMAC secret, so the receiving instance can verify authenticity
* (or trust a fail-closed verdict) of the body it carries.
*
* This is the single definition of how this instance signs S2S responses: the
* body is stringified once and the signature is computed over those exact bytes,
* which are the bytes sent (Content-Type is set explicitly so Fastify does not
* re-serialize and desync the signature).
*/
export function sendSignedJson(reply: FastifyReply, payload: unknown, hmacSecret: string): FastifyReply {
const responseBody = JSON.stringify(payload);
const sigHeaders = buildFederationHeaders(responseBody, hmacSecret, getOurOrigin());
reply.headers({
'X-Federation-Signature': sigHeaders['X-Federation-Signature'],
'X-Federation-Timestamp': sigHeaders['X-Federation-Timestamp'],
'X-Federation-Nonce': sigHeaders['X-Federation-Nonce'],
'Content-Type': 'application/json',
});
return reply.code(200).send(responseBody);
}