fix(federation): persist peer instance_name on /peer/accept activation paths

Previously /peer/accept read body.instanceName only when queueing for admin approval. The four paths that mutate federation_peers (rejected→active override, awaiting_approval→active, pending→active, new-peer create) all wrote status='active' without persisting instance_name. Result: every peer established via direct handshake had instance_name = NULL forever. Anywhere peerLabel was rendered fell back to origin hostname.

Active/needs_attention idempotent early-return path deliberately left alone — same security posture that already refuses to overwrite hmac_secret from unauthenticated requests on already-active peers.

Existing live NULL rows are repaired post-deploy via manual UPDATE statements (see plan).
This commit is contained in:
Jannis Braun
2026-04-25 00:35:42 +02:00
parent 940a0f7ba9
commit 7fe9476e57
2 changed files with 235 additions and 3 deletions
+6 -3
View File
@@ -394,7 +394,7 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
// ─── POST /api/federation/peer/accept ──────────────────────────────────────
// Server-to-server: accept a peering request from a remote instance.
// No JWT auth — this is first contact. Rate-limited by IP.
app.post<{ Body: { sourceOrigin: string; challenge?: string; hmacSecret: string } }>(
app.post<{ Body: { sourceOrigin: string; challenge?: string; hmacSecret: string; instanceName?: string } }>(
'/api/federation/peer/accept',
async (request, reply) => {
const clientIp = request.ip;
@@ -405,7 +405,7 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
});
}
const { sourceOrigin: rawOrigin, hmacSecret } = request.body ?? {};
const { sourceOrigin: rawOrigin, hmacSecret, instanceName: reqInstanceName } = request.body ?? {};
if (!rawOrigin || typeof rawOrigin !== 'string') {
return reply.code(400).send({ error: 'sourceOrigin is required', statusCode: 400 });
@@ -471,7 +471,6 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
}
// Queue for admin approval — upsert into peer_approval_requests
const { instanceName: reqInstanceName } = request.body as { instanceName?: string };
const now = Date.now();
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
@@ -551,6 +550,7 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
db.update(schema.federationPeers)
.set({
hmacSecret,
instanceName: reqInstanceName ?? null,
status: 'active',
lastSeenAt: Date.now(),
})
@@ -577,6 +577,7 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
db.update(schema.federationPeers)
.set({
hmacSecret,
instanceName: reqInstanceName ?? null,
status: 'active',
lastSeenAt: Date.now(),
})
@@ -602,6 +603,7 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
db.update(schema.federationPeers)
.set({
hmacSecret,
instanceName: reqInstanceName ?? null,
status: 'active',
lastSeenAt: Date.now(),
})
@@ -622,6 +624,7 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
id: peerId,
origin: sourceOrigin,
hmacSecret,
instanceName: reqInstanceName ?? null,
status: 'active',
lastSeenAt: Date.now(),
createdAt: Date.now(),