Commit Graph
1206 Commits
Author SHA1 Message Date
Jannis Braun 06d2c972b9 feat(federation): add shared types for FED-009 federated DM calls 2026-03-31 23:17:31 +02:00
Jannis Braun 35c8c66dac docs: clean federation spec — remove resolved issue artifacts, describe current architecture 2026-03-31 22:03:28 +02:00
Jannis Braun abdaf99bb4 fix(federation): address code review findings for FED-011
- Fix race window: store pendingHmacSecret AFTER remote peer confirms,
  not before (admin endpoint + auto-rotation worker)
- Add hex validation on newSecret at /peer/rotate endpoint
- Use pending-secret-aware signing in initial sync worker
- Add test for corrupt state (pendingHmacSecret set, secretRotationAt null)
2026-03-31 21:01:02 +02:00
Jannis Braun f91312a6d9 docs: update federation docs for FED-011 secret rotation 2026-03-31 20:51:30 +02:00
Jannis Braun 0c09a58d61 feat(federation): add grace period finalization and auto-rotation to health worker (FED-011) 2026-03-31 20:49:15 +02:00
Jannis Braun 1f6605b801 feat(federation): outbox worker signs with pending secret during rotation (FED-011) 2026-03-31 20:48:00 +02:00
Jannis Braun d529ff20c2 feat(federation): add admin rotation endpoint and expose rotation state (FED-011) 2026-03-31 20:46:41 +02:00
Jannis Braun bd9a598b36 feat(federation): add /peer/rotate endpoint for secret rotation (FED-011) 2026-03-31 20:44:17 +02:00
Jannis Braun 2ceea5e474 feat(federation): switch relay+sync handlers to verifyPeerSignature (FED-011) 2026-03-31 20:43:41 +02:00
Jannis Braun dcf0bc1857 feat(federation): add verifyPeerSignature with dual-secret grace period (FED-011) 2026-03-31 20:40:38 +02:00
Jannis Braun 0ac140bf51 feat(federation): add secret rotation columns to federation_peers (FED-011) 2026-03-31 20:40:06 +02:00
Jannis Braun 720a5de945 fix(federation): add strict origin enforcement for user attribution (FED-010)
Prevent malicious peers from forging events attributed to users on other
instances. Every relay event processor now verifies the acting user's
homeInstance (from payload) matches X-Federation-Origin (from HMAC-verified
header) via verifyAttribution(), normalized to bare domain.

- Add verifyAttribution() helper using extractDomain normalization
- Guard all 13 event processors before any user resolution or DB writes
- Add homeInstance to FederationRelayReaction type + outbound payloads
- Replace unnormalized string equality in friend handlers
- Log mismatched values on rejection for debugging
2026-03-31 19:16:48 +02:00
Jannis Braun d0ed43cf58 fix(federation): add nonce length validation and fix verification flow docs (FED-008) 2026-03-31 18:18:50 +02:00
Jannis Braun 9961d9012b docs: update federation docs for FED-008 replay attack protection 2026-03-31 18:14:23 +02:00
Jannis Braun 5c5d41e462 feat(federation): wire nonce verification into relay and sync handlers (FED-008)
Pass nonce through to verifySignature and enforce replay protection in
both /api/federation/relay and /api/federation/sync: reject duplicate
nonces (409), reject nonce-less requests from peers that previously sent
nonces (401), warn for legacy peers, and auto-ratchet nonceSupported flag.
2026-03-31 18:12:16 +02:00
Jannis Braun 2f9cce9d38 feat(federation): add nonceSupported column to federation_peers (FED-008)
Adds nonce_supported INTEGER column to the federation_peers table in both
the Drizzle schema definition and via a safe ALTER TABLE migration, enabling
the auto-ratchet mechanism for replay attack protection.
2026-03-31 18:09:40 +02:00
Jannis Braun d2073efdd7 feat(federation): add in-memory nonce store with TTL eviction (FED-008) 2026-03-31 18:08:53 +02:00
Jannis Braun d7c30f2c50 feat(federation): add nonce to HMAC sign/verify and header build/parse (FED-008)
- signRequest now accepts optional nonce; payload becomes `${timestamp}.${nonce}.${body}` when present, falling back to `${timestamp}.${body}` for legacy peers
- verifySignature accepts matching nonce parameter and passes it through
- buildFederationHeaders generates a UUID nonce per request and includes X-Federation-Nonce header
- parseFederationHeaders extracts X-Federation-Nonce into nonce field (null when absent)
2026-03-31 18:07:19 +02:00
Jannis Braun 75504e07c0 fix(federation): add per-peer rate limiting to relay endpoint (FED-007)
Sliding-window rate limiter (30 req/min per peer origin) on POST
/api/federation/relay, matching the existing accept endpoint pattern.
Returns 429 when exceeded — outbox workers retry with backoff. Check
runs before HMAC verification to avoid wasted computation on floods.
2026-03-31 17:33:57 +02:00
Jannis Braun 2cac39a460 fix(federation): replace DNS hairpin self-POST with direct function call (FED-005)
Extract processRelayEvents() from the relay HTTP handler and call it
directly in runInitialSyncForNewPeers(), eliminating the HTTP round-trip
through public DNS that failed on networks without hairpin NAT.
2026-03-31 05:20:16 +02:00
Jannis Braun 37fc52d85b fix(migration): require at least one stub in merge pair to prevent false positives
Two real accounts from the same remote instance sharing a 1-on-1 DM
are different people, not duplicates. The shared DM is a legitimate
relay. Only merge when at least one user has passwordHash =
'!federation-replicated' (a relay-created stub).
2026-03-31 05:08:22 +02:00
Jannis Braun c7bdfef038 docs: update federation identity resolution and auth specs for FED-006 fix
- Document extractDomain, findFederatedUser, updated resolveOrCreateReplicatedUser
- Update origin normalization table to reflect bare domain storage
- Mark Known Issue #2 (duplicate user stubs) as resolved
- Document federated stub upgrade behavior in auth registration flow
2026-03-31 04:55:51 +02:00
Jannis Braun 373989aa44 fix(migration): detect and merge duplicate federated user stubs 2026-03-31 04:54:15 +02:00
Jannis Braun d2e4f9240e fix(migration): normalize homeInstance to bare domain for consistent identity matching 2026-03-31 04:53:53 +02:00
Jannis Braun 3eb46ede67 feat(auth): upgrade existing federation stubs during registration instead of creating duplicates 2026-03-31 04:49:19 +02:00
Jannis Braun f5a937eff7 feat(federation): pass profile username hints to all resolveOrCreateReplicatedUser call sites 2026-03-31 04:46:41 +02:00
Jannis Braun f8e41e3cc5 feat(federation): resolveOrCreateReplicatedUser uses findFederatedUser + normalizes homeInstance 2026-03-31 04:42:12 +02:00
Jannis Braun 8167540afc feat(federation): add findFederatedUser lookup + backfillHomeUserId helper 2026-03-31 04:41:51 +02:00
Jannis Braun 77f02c805e refactor(federation): extract extractDomain helper from resolveOrCreateReplicatedUser 2026-03-31 04:41:14 +02:00
Jannis Braun a3a7527c9e chore: add system docs, specs, and misc updates from other sessions
- Add complete docs/systems/ reference (18 system docs)
- Add federation relay status doc and prior spec/plan docs
- Remove superseded docs/federation-dm-s2s.md (replaced by docs/systems/federation.md)
- CLAUDE.md updates
- Minor fixes in social.ts, types.ts, AddDmMemberModal, NewDmModal, UserSettings
2026-03-31 03:40:34 +02:00
Jannis Braun 70678ff198 fix(federation): batch A production readiness — normalization, logging, security
FED-001: normalize homeInstance in processCreateEvent member skip
FED-002: normalize homeInstance in getFriendEventTargets
FED-003: normalize homeInstance in handleSizeRejection
FED-004: add warning log when queueOutboxEvent drops events (zero peer match)
FED-012: remove unused challenge from peer handshake
FED-013: reject non-HTTPS origins in validateOrigin (except localhost)
2026-03-31 03:33:22 +02:00
Jannis Braun d021ee796d docs: add federated DM S2S protocol reference
Comprehensive internal document covering the complete lifecycle of
federated DMs: data model, relay pipeline, event processing, bootstrap
path, local-only broadcast principle, system messages, and all known
pitfalls with their fixes.
2026-03-28 02:10:55 +01:00
Jannis Braun 41658ec2ef fix: repair corrupted group DMs, prevent ownerId nulling, show empty groups
Three fixes for group DM data integrity and display:

1. processOwnershipTransferEvent: use resolveOrCreateReplicatedUser instead
   of resolveLocalUser to guarantee a valid ownerId. The previous ?? null
   fallback converted group DMs into 1-on-1s when resolution failed.

2. Self-healing migration: detect group DMs with UUID-format federated_id
   but NULL owner_id (corrupted by the old fallback) and restore owner from
   the first remaining member. Found and repaired 7 across both instances.

3. Sidebar: group DMs with 0 other members (last person standing) now show
   as "Empty Group" instead of being hidden. 1-on-1 DMs with 0 others are
   still correctly filtered out.
2026-03-27 17:38:09 +01:00
Jannis Braun 44b6317c16 fix: federation relay for group DMs — origin normalization, missing federatedId, local-only broadcasts
Three stacked bugs prevented federation relay from working for group DMs:

1. Origin format mismatch: users.home_instance stores bare domains
   ("nova.ddns.net") but federation_peers.origin stores full URLs
   ("https://nova.ddns.net"). getGroupDmTargetOrigins() built target
   lists from bare domains, so queueOutboxEvent() never matched any
   peers — events were never queued into the outbox.

2. Missing federatedId in outbox reconstruction: the outbox worker
   rebuilt relay events from stored payloads but never copied the
   federatedId field. Receiving instances check this field and rejected
   all member_add/remove/ownership_transfer events with
   "missing_membership_payload".

3. Duplicate channels from cross-instance broadcasts: dm_channel_created
   was sent to ALL members including remote replicas. Users connected to
   multiple instances received the event twice (once per instance),
   creating duplicate group DMs in their sidebar. Fixed by only
   broadcasting to members whose home instance matches the current
   server — remote members receive the channel via federation bootstrap
   on their home instance.
2026-03-27 16:53:35 +01:00
Jannis Braun 04c665e0f7 fix: three code review issues — leaveGroup duplicate, as any casts, federation ownership fallback
- Remove redundant `leaveGroup` API method from client.ts (duplicated `leave`); update MessageList.tsx WelcomeHeader to call `api.dm.leave` directly
- Add optional `type` field to shared `Message` interface so `MessageWithUser` carries it; remove `(msg as any).type` casts in `isSameGroup` and the render branch in MessageList.tsx
- Fix `processOwnershipTransferEvent` in federation.ts: replace `channel.ownerId` fallbacks (pre-update, old owner) with `event.ownership.newOwner.homeUserId` in the db update, dm_owner_updated broadcast, and both system message content payloads
2026-03-27 05:50:38 +01:00
Jannis Braun c3191be4e7 feat: system messages, group DM welcome header, and dm_owner_updated event
- Render system messages (member_added, member_removed, owner_changed) inline
  in MessageList with icon + human-readable text; system messages never group
  with adjacent user messages
- Rewrite WelcomeHeader to branch on ownerId: group DMs show overlapping avatars,
  group name, creator attribution, federated privacy note, and a Leave Group button
- Add dm_owner_updated ServerEvent; broadcast from dm.ts leave handler and
  federation processOwnershipTransferEvent so all clients update ownerId in real-time
- Add updateDmOwner action to spaceStore and handle dm_owner_updated in useWebSocket
- Add leaveGroup alias to API client dm namespace
2026-03-27 05:43:30 +01:00
Jannis Braun 2eeca93268 feat: add system messages for federated member add/remove/ownership events
On the receiving instance, federation event processors now write
dm_messages with type='system' for member_added, member_removed, and
owner_changed events and broadcast them via dm_message_created to all
connected local WebSocket clients, matching the behaviour of local
group DM operations.
2026-03-27 05:37:27 +01:00
Jannis Braun 8e589f41fd fix: send dm_channel_created to local users when bootstrapping federated group DM
When processMemberAddEvent bootstrapped a new group DM channel for the
first time on a receiving instance, local users were never notified via
WebSocket — only dm_member_added was broadcast, which requires the client
to already know about the channel. Now, after bootstrap, dm_channel_created
is sent directly to each local connected member, and the redundant
dm_member_added broadcast is skipped for the bootstrap path.
2026-03-27 05:35:29 +01:00
Jannis Braun 72aa318928 feat: insert and broadcast system messages for group DM membership events
On group DM creation, emit a system message per added member (event:
member_added). On POST /api/dm/:id/members, emit a system message for
the newly added member. On DELETE /api/dm/:id/members, emit a
member_removed system message before the row is deleted (so the leaver
is still a member at broadcast time), and emit an owner_changed system
message when ownership transfers. The newOwnerUser query is moved
outside the federation-only block so it is available unconditionally.
2026-03-27 05:33:45 +01:00
Jannis Braun 56e8fc8795 feat: add type column to dm_messages for system message support
Adds a `type` column (TEXT NOT NULL DEFAULT 'user') to the dm_messages
table via schema, migration, and type definition. Updates
buildDmMessageWithUser and the inline replyTo builder in the GET
messages handler to include the field in all DM message responses.
2026-03-27 05:30:42 +01:00
Jannis Braun c5c7ab571a fix(federation): use inline badge for all attachment types
Overlay badges inside overflow-hidden containers (images, video)
were invisible. Use inline badges below the media for all types.
2026-03-27 05:20:58 +01:00
Jannis Braun dd3ddd9cc2 fix(federation): fix badge positioning and tooltip text for file attachments
- Use inline badges for file/audio attachments (next to file size)
  instead of absolute positioning which overflowed the container
- Keep overlay badges for images/video (overflow-hidden containers)
- Match tooltip text to toast notification text for consistency
2026-03-27 05:07:55 +01:00
Jannis Braun ceb0c9812d fix(federation): resolve code review issues in upload size mismatch
- Fix critical: outbox worker now copies file_rejected payload fields
  (attachmentId, sourceFilename, rejectionReason, rejectionLimit,
  affectedUserIds) so the reverse relay actually delivers them
- Fix: add sourceFilename to file_rejected event for reliable
  multi-attachment matching on the sender side
- Fix: change text-accent-warning to text-accent-amber (valid class)
- Add Array.isArray guard on federationMeta parse
2026-03-27 04:51:30 +01:00
Jannis Braun 589af25b45 feat(federation): show toast on file_rejected federation event 2026-03-27 04:43:23 +01:00
Jannis Braun 4686ec6594 feat(federation): add federation status badges to attachment renderer 2026-03-27 04:42:15 +01:00
Jannis Braun bfb358c530 feat(federation): include federation status columns in attachment queries
Both buildDmMessageWithUser and buildMessageWithUser manually map
attachment fields — add federationStatus and federationMeta so they
reach the frontend.
2026-03-27 04:40:48 +01:00
Jannis Braun b99cbea268 feat(federation): process file_rejected relay events on sender instance 2026-03-27 04:39:48 +01:00
Jannis Braun 1990b90022 feat(federation): send file_rejected reverse relay on size limit exceeded 2026-03-27 04:37:27 +01:00
Jannis Braun 7125c62a1f feat(federation): store remote peer upload size limit from relay response 2026-03-27 04:34:57 +01:00
Jannis Braun 49d1be68bc feat(federation): add file_rejected relay event and attachment federation types 2026-03-27 04:34:02 +01:00