Merge branch 'fix/peer-initiate-202'

This commit is contained in:
Jannis Braun
2026-04-21 22:44:44 +02:00
2 changed files with 33 additions and 2 deletions
+4 -2
View File
@@ -43,8 +43,10 @@ Backspace federation is peer-to-peer with no central authority. Each instance ma
- Creates local peer record with `status='pending'`
- POSTs to `{remoteOrigin}/api/federation/peer/accept` with `{ sourceOrigin, challenge, hmacSecret }`
- Timeout: 10 seconds (`AbortSignal.timeout`)
- On remote acceptance: updates local peer to `status='active'`, sets `lastSeenAt`
- On failure: deletes pending peer, returns 502 (network error) or 504 (timeout)
- On remote 200 (accepted): updates local peer to `status='active'`, sets `lastSeenAt`, broadcasts `federation_peers_changed` to admin WS subscribers, returns 200 with `{ peer }`
- On remote 202 (queued for remote admin approval): transitions local peer to `status='awaiting_approval'` (does **not** activate), broadcasts `federation_peers_changed`, returns 202 with `{ peer }`. Without this branch `response.ok` would be true and the local peer would flip to `active` while the remote had us pending — a transient local-active / remote-pending split that only self-healed when the remote admin approved. Mirrors the auto-peer 202 branch in `federationPeering.ts:performHandshake`.
- On remote 403 / other non-2xx: deletes pending peer, returns 502 with the remote's error message
- On network error / timeout: deletes pending peer, returns 502 (network) or 504 (timeout)
**Phase 2 -- Accept** (`POST /api/federation/peer/accept`)
- Auth: **none** (first contact -- no JWT, no HMAC)
+29
View File
@@ -304,6 +304,34 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
signal: AbortSignal.timeout(10_000),
});
if (response.status === 202) {
// Remote instance queued our request for admin approval
// (autoAcceptPeering is off on their side). Do NOT activate the
// local peer — mirror the auto-peer flow in federationPeering.ts
// by transitioning the pending record to awaiting_approval.
// Without this branch the local peer would flip to `active`
// (because response.ok is true for 202) while the remote had us
// pending, producing a local-active / remote-pending split that
// only self-heals when the remote admin approves.
db.update(schema.federationPeers)
.set({ status: 'awaiting_approval' })
.where(eq(schema.federationPeers.id, peerId))
.run();
connectionManager.sendToAdmins({ type: 'federation_peers_changed' as const });
const peer = db
.select()
.from(schema.federationPeers)
.where(eq(schema.federationPeers.id, peerId))
.get();
if (!peer) {
return reply.code(500).send({ error: 'Failed to read peer after queuing', statusCode: 500 });
}
return reply.code(202).send({ peer: sanitizePeer(peer) });
}
if (!response.ok) {
let errorMessage = `Remote instance rejected peering (HTTP ${response.status})`;
try {
@@ -325,6 +353,7 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
.set({ status: 'active', lastSeenAt: Date.now() })
.where(eq(schema.federationPeers.id, peerId))
.run();
connectionManager.sendToAdmins({ type: 'federation_peers_changed' as const });
const peer = db
.select()