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,433 @@
import { getDb, schema } from '../../../db/index.js';
import { getOurOrigin, normalizeOriginForCompare } from '../../../utils/federationAuth.js';
import { sanitizeUser } from '../../../utils/sanitize.js';
import { generateSnowflake } from '../../../utils/snowflake.js';
import { connectionManager } from '../../../ws/handler.js';
import { and, eq, or } from 'drizzle-orm';
import type { FederationRelayEvent } from '@backspace/shared';
import { extractDomain, resolveLocalUser, resolveOrCreateReplicatedUser, verifyAttribution } from '../identity.js';
import { hydrateReplicatedUserProfile } from '../profile.js';
export async function processFriendRequestCreateEvent(
event: FederationRelayEvent,
sourceInstance: string,
db: ReturnType<typeof getDb>,
accepted: string[],
rejected: Array<{ messageId: string; reason: string }>,
): Promise<void> {
if (!event.friendship) {
rejected.push({ messageId: event.messageId, reason: 'missing_friendship_payload' });
return;
}
const { from, to } = event.friendship;
// Attribution: sender must belong to source instance (FED-010)
if (!verifyAttribution(from.homeInstance, sourceInstance)) {
console.warn(`[federation] Attribution mismatch in friend_request_create: from homeInstance=${extractDomain(from.homeInstance)} source=${extractDomain(sourceInstance)}`);
rejected.push({ messageId: event.messageId, reason: 'attribution_mismatch' });
return;
}
// Self-target guard (defense-in-depth): from-identity must not equal to-identity.
// Sender's local cannot_friend_self check should catch this, but the receiver must not trust it.
if (
from.homeUserId === to.homeUserId &&
normalizeOriginForCompare(from.homeInstance) === normalizeOriginForCompare(to.homeInstance)
) {
console.warn(`[federation] Self-target friend_request_create rejected: homeUserId=${from.homeUserId} homeInstance=${extractDomain(from.homeInstance)} source=${extractDomain(sourceInstance)}`);
rejected.push({ messageId: event.messageId, reason: 'self_target_invalid' });
return;
}
// Resolve the sender (create stub if needed — they're on a remote instance)
const fromUserResolved = resolveOrCreateReplicatedUser(from.homeUserId, from.homeInstance, db, { username: event.friendship.fromProfile?.username, status: event.friendship.fromProfile?.status, deleted: event.friendship.fromProfile?.deleted });
if (!fromUserResolved) {
// Sender's identity has been deleted — silently accept to drop the event
accepted.push(event.messageId);
return;
}
let fromUser = await hydrateReplicatedUserProfile(fromUserResolved, event.friendship.fromProfile, db);
// Resolve the recipient — must be a local user on this instance
const toUser = resolveLocalUser(to.homeUserId, db);
if (!toUser) {
rejected.push({ messageId: event.messageId, reason: 'recipient_not_found' });
return;
}
// Idempotency: if already friends, accept as no-op
const existingFriend = db
.select()
.from(schema.friends)
.where(
or(
and(eq(schema.friends.userId, fromUser.id), eq(schema.friends.friendId, toUser.id)),
and(eq(schema.friends.userId, toUser.id), eq(schema.friends.friendId, fromUser.id)),
),
)
.get();
if (existingFriend) {
accepted.push(event.messageId);
return;
}
// Idempotency: a pending request in EITHER direction makes this event a no-op.
// Forward (from→to): re-delivery of an event we've already processed.
// Reverse (to→from): the local user has already sent a request TO this remote sender.
// Race window: both sides click "add friend" near-simultaneously. Each sender's both-direction
// check passes locally (no rows yet anywhere). When the events cross, each receiver must
// treat the reverse-direction collision as idempotent — otherwise both instances end up
// with two opposite-direction pending rows for the same logical pair. Mirror the
// sender-side both-direction check (`incoming_request_exists` in social.ts).
const existingRequest = db
.select()
.from(schema.friendRequests)
.where(
and(
or(
and(eq(schema.friendRequests.fromId, fromUser.id), eq(schema.friendRequests.toId, toUser.id)),
and(eq(schema.friendRequests.fromId, toUser.id), eq(schema.friendRequests.toId, fromUser.id)),
),
eq(schema.friendRequests.status, 'pending'),
),
)
.get();
if (existingRequest) {
accepted.push(event.messageId);
return;
}
// Create the friend request
const id = generateSnowflake();
const now = event.friendship.createdAt || Date.now();
db.insert(schema.friendRequests)
.values({
id,
fromId: fromUser.id,
toId: toUser.id,
status: 'pending',
createdAt: now,
})
.run();
// Broadcast to the recipient
connectionManager.sendToUser(toUser.id, {
type: 'friend_request_received',
request: {
id,
fromId: fromUser.id,
toId: toUser.id,
status: 'pending' as const,
createdAt: now,
user: sanitizeUser(fromUser),
},
});
accepted.push(event.messageId);
}
export function processFriendRequestUpdateEvent(
event: FederationRelayEvent,
sourceInstance: string,
db: ReturnType<typeof getDb>,
accepted: string[],
rejected: Array<{ messageId: string; reason: string }>,
): void {
if (!event.friendship || !event.friendship.status) {
rejected.push({ messageId: event.messageId, reason: 'missing_friendship_payload' });
return;
}
const { from, to, status } = event.friendship;
// Attribution: recipient (acceptor/decliner) must belong to source instance (FED-010)
if (!verifyAttribution(to.homeInstance, sourceInstance)) {
console.warn(`[federation] Attribution mismatch in friend_request_update: to homeInstance=${extractDomain(to.homeInstance)} source=${extractDomain(sourceInstance)}`);
rejected.push({ messageId: event.messageId, reason: 'attribution_mismatch' });
return;
}
// Resolve the sender — must be a local user (the one who sent the original request)
const fromUser = resolveLocalUser(from.homeUserId, db);
if (!fromUser) {
rejected.push({ messageId: event.messageId, reason: 'sender_not_found' });
return;
}
// Resolve the recipient (create stub if needed — they're on the remote instance)
const toUser = resolveOrCreateReplicatedUser(to.homeUserId, to.homeInstance, db, { username: event.friendship.toProfile?.username, status: event.friendship.toProfile?.status, deleted: event.friendship.toProfile?.deleted });
if (!toUser) {
// Recipient's identity has been deleted — accept idempotently to drop the event
accepted.push(event.messageId);
return;
}
// Find the pending request
const pendingRequest = db
.select()
.from(schema.friendRequests)
.where(
and(
eq(schema.friendRequests.fromId, fromUser.id),
eq(schema.friendRequests.toId, toUser.id),
eq(schema.friendRequests.status, 'pending'),
),
)
.get();
if (!pendingRequest) {
// Accept idempotently — friend_add may have arrived first
accepted.push(event.messageId);
return;
}
// Update request status
db.update(schema.friendRequests)
.set({ status: status as string })
.where(eq(schema.friendRequests.id, pendingRequest.id))
.run();
if (status === 'accepted') {
const now = event.friendship.createdAt || Date.now();
connectionManager.sendToUser(fromUser.id, {
type: 'friend_request_accepted',
friend: {
...sanitizeUser(toUser),
addedAt: now,
},
requestId: pendingRequest.id,
});
} else if (status === 'declined') {
connectionManager.sendToUser(fromUser.id, {
type: 'friend_request_declined',
requestId: pendingRequest.id,
userId: toUser.id,
});
}
accepted.push(event.messageId);
}
export function processFriendRequestCancelEvent(
event: FederationRelayEvent,
sourceInstance: string,
db: ReturnType<typeof getDb>,
accepted: string[],
rejected: Array<{ messageId: string; reason: string }>,
): void {
if (!event.friendship) {
rejected.push({ messageId: event.messageId, reason: 'missing_friendship_payload' });
return;
}
const { from, to } = event.friendship;
// Attribution: sender must belong to source instance (FED-010)
if (!verifyAttribution(from.homeInstance, sourceInstance)) {
console.warn(`[federation] Attribution mismatch in friend_request_cancel: from homeInstance=${extractDomain(from.homeInstance)} source=${extractDomain(sourceInstance)}`);
rejected.push({ messageId: event.messageId, reason: 'attribution_mismatch' });
return;
}
// Resolve both users — must both exist locally for there to be a pending request
const fromUser = resolveLocalUser(from.homeUserId, db);
const toUser = resolveLocalUser(to.homeUserId, db);
if (!fromUser || !toUser) {
// Accept idempotently — if either user doesn't exist, there's nothing to cancel
accepted.push(event.messageId);
return;
}
// Find the pending request
const pendingRequest = db
.select()
.from(schema.friendRequests)
.where(
and(
eq(schema.friendRequests.fromId, fromUser.id),
eq(schema.friendRequests.toId, toUser.id),
eq(schema.friendRequests.status, 'pending'),
),
)
.get();
if (!pendingRequest) {
// Accept idempotently — already cancelled or never existed
accepted.push(event.messageId);
return;
}
// Delete the request
db.delete(schema.friendRequests)
.where(eq(schema.friendRequests.id, pendingRequest.id))
.run();
// Broadcast to the recipient
connectionManager.sendToUser(toUser.id, {
type: 'friend_request_cancelled',
requestId: pendingRequest.id,
userId: fromUser.id,
});
accepted.push(event.messageId);
}
export async function processFriendAddEvent(
event: FederationRelayEvent,
sourceInstance: string,
db: ReturnType<typeof getDb>,
accepted: string[],
rejected: Array<{ messageId: string; reason: string }>,
): Promise<void> {
if (!event.friendship) {
rejected.push({ messageId: event.messageId, reason: 'missing_friendship_payload' });
return;
}
const { from, to } = event.friendship;
// Attribution: acceptor must belong to source instance (FED-010)
if (!verifyAttribution(to.homeInstance, sourceInstance)) {
console.warn(`[federation] Attribution mismatch in friend_add: to homeInstance=${extractDomain(to.homeInstance)} source=${extractDomain(sourceInstance)}`);
rejected.push({ messageId: event.messageId, reason: 'attribution_mismatch' });
return;
}
// Resolve both users (create stubs if needed) and hydrate with profile data
const fromUserResolved = resolveOrCreateReplicatedUser(from.homeUserId, from.homeInstance, db, { username: event.friendship.fromProfile?.username, status: event.friendship.fromProfile?.status, deleted: event.friendship.fromProfile?.deleted });
if (!fromUserResolved) {
// One party's identity is deleted — accept idempotently to drop the event
accepted.push(event.messageId);
return;
}
let fromUser = await hydrateReplicatedUserProfile(fromUserResolved, event.friendship.fromProfile, db);
const toUserResolved = resolveOrCreateReplicatedUser(to.homeUserId, to.homeInstance, db, { username: event.friendship.toProfile?.username, status: event.friendship.toProfile?.status, deleted: event.friendship.toProfile?.deleted });
if (!toUserResolved) {
accepted.push(event.messageId);
return;
}
let toUser = await hydrateReplicatedUserProfile(toUserResolved, event.friendship.toProfile, db);
// Idempotency: if friendship already exists, accept as no-op
const existingFriend = db
.select()
.from(schema.friends)
.where(
or(
and(eq(schema.friends.userId, fromUser.id), eq(schema.friends.friendId, toUser.id)),
and(eq(schema.friends.userId, toUser.id), eq(schema.friends.friendId, fromUser.id)),
),
)
.get();
if (existingFriend) {
accepted.push(event.messageId);
return;
}
// Insert friendship row
const now = event.friendship.createdAt || Date.now();
db.insert(schema.friends)
.values({
userId: fromUser.id,
friendId: toUser.id,
createdAt: now,
})
.run();
// Auto-resolve any pending friend request between these users to 'accepted'
// (handles friend_add arriving before friend_request_update)
db.update(schema.friendRequests)
.set({ status: 'accepted' })
.where(
and(
or(
and(eq(schema.friendRequests.fromId, fromUser.id), eq(schema.friendRequests.toId, toUser.id)),
and(eq(schema.friendRequests.fromId, toUser.id), eq(schema.friendRequests.toId, fromUser.id)),
),
eq(schema.friendRequests.status, 'pending'),
),
)
.run();
// Determine which user is local and broadcast to them
const ourOrigin = getOurOrigin();
const localUser = from.homeInstance === ourOrigin ? fromUser : toUser;
const remoteUser = from.homeInstance === ourOrigin ? toUser : fromUser;
connectionManager.sendToUser(localUser.id, {
type: 'friend_request_accepted',
friend: {
...sanitizeUser(remoteUser),
addedAt: now,
},
// Use empty string for requestId since the request may not exist locally yet
requestId: '',
});
accepted.push(event.messageId);
}
export function processFriendRemoveEvent(
event: FederationRelayEvent,
sourceInstance: string,
db: ReturnType<typeof getDb>,
accepted: string[],
rejected: Array<{ messageId: string; reason: string }>,
): void {
if (!event.friendship) {
rejected.push({ messageId: event.messageId, reason: 'missing_friendship_payload' });
return;
}
const { from, to } = event.friendship;
// Attribution: at least one side must belong to source instance (FED-010)
if (!verifyAttribution(from.homeInstance, sourceInstance) && !verifyAttribution(to.homeInstance, sourceInstance)) {
console.warn(`[federation] Attribution mismatch in friend_remove: from homeInstance=${extractDomain(from.homeInstance)} to homeInstance=${extractDomain(to.homeInstance)} source=${extractDomain(sourceInstance)}`);
rejected.push({ messageId: event.messageId, reason: 'attribution_mismatch' });
return;
}
// Resolve both users — must both exist locally for there to be a friendship
const fromUser = resolveLocalUser(from.homeUserId, db);
const toUser = resolveLocalUser(to.homeUserId, db);
if (!fromUser || !toUser) {
// Accept idempotently — if either user doesn't exist locally, nothing to remove
accepted.push(event.messageId);
return;
}
// Delete friendship in both directions
db.delete(schema.friends)
.where(
or(
and(eq(schema.friends.userId, fromUser.id), eq(schema.friends.friendId, toUser.id)),
and(eq(schema.friends.userId, toUser.id), eq(schema.friends.friendId, fromUser.id)),
),
)
.run();
// Determine which user is local (the one whose home instance is NOT the source)
// The removing user is on the source instance; broadcast to the other user
const ourOrigin = getOurOrigin();
const localUser = from.homeInstance === ourOrigin ? fromUser : toUser;
const removingUser = from.homeInstance === ourOrigin ? toUser : fromUser;
connectionManager.sendToUser(localUser.id, {
type: 'friend_removed',
userId: removingUser.id,
});
accepted.push(event.messageId);
}