Commit Graph
406 Commits
Author SHA1 Message Date
Jannis Braun c229b32771 feat: add processProfileUpdateEvent S2S relay processor 2026-04-07 13:54:43 +02:00
Jannis Braun 34902fe320 refactor: extract collectProfileBroadcastTargetIds shared helper from userDeletion 2026-04-07 13:52:21 +02:00
Jannis Braun 1a2bc0ec52 fix: use timestamp 0 for never-edited profiles in LWW comparisons
profileUpdatedAt ?? createdAt treated freshly registered users as having
"newer" profiles than users with real edit history, because createdAt is
always NOW at registration time. This broke federation profile sync:
the client correctly pushed home → remote, but the remote server's LWW
guard rejected the write (stored createdAt > incoming profileUpdatedAt).

A null profileUpdatedAt means "never edited" — that's timestamp 0, not
the user's creation time.
2026-04-03 05:27:51 +02:00
Jannis Braun 02a44c201d fix: clean up replicatedInstances and registry on federation identity deletion
After deleting a federated identity, the server-side user_federation_registry
and users.replicated_instances were not cleaned up, causing "already connected"
errors when trying to re-federate. The deletion endpoint now authoritatively
removes both the registry row and the replicatedInstances entry, and bumps the
LWW timestamp to prevent stale client syncs from re-inserting them.

Also extends the endpoint to accept mode 'leave' (skip S2S, just clean up),
and enables the "Select instances..." scope option in DeleteIdentityDialog.
2026-04-03 04:56:25 +02:00
Jannis Braun 9d4b759cb4 feat: add user_updated broadcast to federation identity delete, use shared helper
Switch from manual space-ID collection to collectDeletionBroadcastTargets and
add user_updated broadcast so clients patch their caches when a federated user
is deleted via S2S. Force-disconnect moved after broadcasts so other tabs
receive events before the connection is torn down.
2026-04-03 04:20:56 +02:00
Jannis Braun 5ef79ea8cd feat: broadcast member_left and user_updated on admin deletion 2026-04-03 04:19:58 +02:00
Jannis Braun 24351fba81 feat: broadcast member_left and user_updated on self-deletion 2026-04-03 04:19:09 +02:00
Jannis Braun 8c2587e90c feat: add collectDeletionBroadcastTargets helper for user deletion broadcasts 2026-04-03 04:18:10 +02:00
Jannis Braun 32133d20cc fix: orphaned DM cleanup runs in both modes, nuke deletes space messages
- Orphaned DM channels (zero members) are unreachable garbage — clean
  them up regardless of purgeContent mode, not just in full/nuke mode.
- Full/nuke mode now also deletes the user's space messages, their
  attachments, and embeds. This is the meaningful distinction: "Delete
  User" preserves all content as "Deleted User", "Nuke" removes it.
2026-04-03 03:50:52 +02:00
Jannis Braun c0e6c4019d fix: filter isDeleted=0 in identity delete endpoint user lookup
After a prior deletion + re-federation, multiple user records share
the same homeUserId (one deleted, one live). The unfiltered .get()
returned the older deleted record, causing the idempotency check to
short-circuit and miss the live record entirely.
2026-04-03 03:23:16 +02:00
Jannis Braun 793a3967be feat: add DELETE /api/federation/identity S2S endpoint 2026-04-03 02:38:42 +02:00
Jannis Braun 97470a4d3a feat: add home-side federation identity delete trigger endpoint 2026-04-03 02:38:35 +02:00
Jannis Braun 2c09953864 fix: scope zombie guard by homeInstance to prevent cross-instance false matches
homeUserId snowflakes aren't globally unique — must also match
homeInstance to avoid blocking stub creation for unrelated users.
2026-04-03 02:35:06 +02:00
Jannis Braun 4da373c970 fix: prevent resolveOrCreateReplicatedUser from recreating deleted user stubs
When a federated user's identity has been tombstoned (isDeleted=1),
findFederatedUser filters them out, causing resolveOrCreateReplicatedUser
to silently create a new stub — a "zombie" resurrection. This guard checks
for a deleted row before creating a stub and returns null instead.

All 11 call sites across federation.ts and dm.ts have been updated with
appropriate null guards: federation relay handlers reject or skip the event
(participant_not_found / accepted no-op), while dm.ts routes convert null
to undefined so the existing 404 path handles it.
2026-04-03 02:32:36 +02:00
Jannis Braun bb4b031423 feat: add purgeContent option to tombstoneUser for soft-delete mode 2026-04-03 02:30:00 +02:00
Jannis Braun 5e778f400b fix(server): use two-step MAX(created_at) for DM last message in ready payload
Aligns the ready payload query with the GET /api/dm approach:
get MAX(created_at) per channel first, then fetch the actual
message rows. Avoids issues with federated relay messages whose
local snowflake IDs don't match chronological order.
2026-04-03 00:04:40 +02:00
Jannis Braun afa583df1d feat(server): include attachment metadata in GET /api/dm lastMessage 2026-04-02 17:53:14 +02:00
Jannis Braun 6e5792ff72 feat(server): include attachment metadata in ready payload DM lastMessage 2026-04-02 17:51:37 +02:00
Jannis Braun 1d81838868 feat(server): filter DM read states from ready payload for federated users 2026-04-02 11:33:59 +02:00
Jannis Braun b1ba7b35d2 feat(server): skip DM channel queries in ready payload for federated users 2026-04-02 11:33:51 +02:00
Jannis Braun 54bce681f9 feat(server): add isFederated flag and visibleChannelIdSet to ready payload builder 2026-04-02 11:33:43 +02:00
Jannis Braun c94e1644a8 feat(server): add WS federation gating for DM events and shared handlers 2026-04-02 10:51:20 +02:00
Jannis Braun 7200ccd7e4 feat(server): centralize DM auth via plugin-scoped hooks, add federation gating 2026-04-02 10:48:45 +02:00
Jannis Braun 32d9f16a15 feat(server): cache isFederated flag at WS auth, pass to event handler 2026-04-02 10:47:52 +02:00
Jannis Braun 8f2f494ffd test(server): add unit tests for requireLocalUser guard 2026-04-02 10:47:43 +02:00
Jannis Braun 017256162b feat(server): add homeInstance to authenticate, add requireLocalUser guard 2026-04-02 10:45:50 +02:00
Jannis Braun b8ab162570 fix(server): add registry size/duplicate validation; update database and API docs 2026-04-01 18:25:24 +02:00
Jannis Braun 7db9459f11 feat(server): add GET & PUT federation registry API endpoints
Adds persistent federation registry storage endpoints under
/api/users/@me/federation-registry. GET returns all registry entries
with the stored updatedAt timestamp. PUT replaces the full registry via
LWW (409 on stale write) in an atomic transaction.
2026-04-01 17:54:46 +02:00
Jannis Braun 9586ac2f9c feat(server): add user_federation_registry table and federationRegistryUpdatedAt column 2026-04-01 17:52:42 +02:00
Jannis Braun b1efeda1f3 perf(deploy): parallel deploys, preserve build cache, prod-only runtime deps
Three changes to cut deploy time:
1. deploy.sh: Pi and VM deploy concurrently (wall time = max, not sum)
2. deploy.sh: Build cache capped at 2GB instead of nuked after 24h,
   so pnpm install layer is reused between deploys
3. Dockerfile: runtime stage uses --prod (skip devDeps). Moved tsx
   from devDependencies to dependencies since it's used in CMD.
2026-04-01 13:13:17 +02:00
Jannis Braun f2ca7295d0 chore(server): add vitest as dev dependency with test scripts
Test files existed but vitest wasn't installed. Adds vitest v4,
"test" and "test:watch" scripts. All 22 existing tests pass.
2026-04-01 13:03:03 +02:00
Jannis Braun e1ece8a5b6 feat(federation): inbound typing relay processors + implicit clear on message relay
processDmTypingStartEvent and processDmTypingStopEvent handle
typing indicator relay from peers. Uses federatedId for channel
lookup, resolveLocalUser for ephemeral identity (no stub creation).
Also clears typing indicator in processCreateEvent when a relayed
message arrives — belt-and-suspenders for dropped relay packets.
2026-04-01 12:56:44 +02:00
Jannis Braun b3011fb3da feat(federation): add fire-and-forget S2S typing relay
sendTypingRelay() mirrors sendCallRelay() — direct POST to peers,
no outbox, no retry. Uses federatedId for cross-instance channel
identification. Wired into handleDmTypingStart() for typing_start
and broadcastDmMessage() for typing_stop.
2026-04-01 12:53:55 +02:00
Jannis Braun 187f71e68e feat: add dm_typing_stop event to clear typing indicator on message send
Broadcasts dm_typing_stop to DM members before dm_message_created,
so the typing indicator clears immediately when a message arrives
instead of lingering for up to 3 seconds after delivery.
2026-04-01 12:50:03 +02:00
Jannis Braun 4d7a777ae4 feat(federation): POST /api/dm/:id/members accepts homeUserId+homeInstance
Extend the add-member endpoint to resolve federated identity via
resolveOrCreateReplicatedUser() when homeUserId+homeInstance are provided,
falling back to the existing local userId lookup.
2026-04-01 12:40:57 +02:00
Jannis Braun 5c02bff2d2 feat(federation): POST /api/dm accepts homeUserId+homeInstance for federated DM creation 2026-04-01 12:38:50 +02:00
Jannis Braun 73583a4b61 fix(migration): stop ownerId ping-pong on group DMs with 2 members
migrateFixOneOnOneOwnerIds was too aggressive — it set owner_id=NULL
on any 2-member channel, including group DMs that happened to have 2
members. The group DM repair then restored owner_id, creating noisy
logs every restart.

Now only targets channels with NULL or 32-char hex federatedId
(true 1-on-1 DMs), skipping UUID-format group DMs.
2026-04-01 04:00:07 +02:00
Jannis Braun e40a63ba9b fix(federation): assign deterministic federatedId on 1-on-1 DM creation
POST /api/dm created channels with federatedId=NULL, so when the S2S
reply arrived, processCreateEvent couldn't find the channel and created
a duplicate. Now computes the deterministic SHA256 hash at creation time
when either participant is federated.
2026-04-01 03:57:42 +02:00
Jannis Braun 4b596afae5 fix(federation): allow homeward relay in attribution check
Client-federation users (e.g., youruser@nova logged into orbit)
send DMs on the remote server. The S2S relay forwards these back to the
author's home instance, but verifyAttribution rejected them because the
author's homeInstance didn't match the sourceInstance.

Now also accepts when the author's home matches the receiving instance
(getOurOrigin()), covering the homeward relay case.
2026-04-01 03:56:12 +02:00
Jannis Braun 42cf8afddd fix(federation): raise relay rate limit from 30 to 90 req/min per peer
The outbox worker interval was reduced from 10s to 1s in FED-009,
so a busy sender can now hit 60 req/min during sustained traffic.
90 gives 50% headroom.
2026-04-01 02:00:19 +02:00
Jannis Braun 3265670047 feat(server): add PATCH peer and permanent delete endpoints for federation admin 2026-04-01 01:22:41 +02:00
Jannis Braun 83074e40ef feat(server): add defaultAutoRotateIntervalDays to instance settings schema and API 2026-04-01 01:22:41 +02:00
Jannis Braun 626fbfdba8 fix(federation): address code review findings for FED-009
- Resolve homeUserId from DB in sendFederatedCallStart/End (not raw userId)
- Clear existing timeout in createFederatedCall before overwriting
- Clear federatedCallToken/Url in leaveVoice and handleForceDisconnect
- Remove unnecessary `as any` cast in relay processor
2026-03-31 23:58:27 +02:00
Jannis Braun aff2a403d7 fix(federation): reduce outbox worker interval from 10s to 1s for lower relay latency 2026-03-31 23:42:31 +02:00
Jannis Braun 09916917f1 fix(federation): include livekitUrl/livekitToken in dm_call_incoming relay event (FED-009) 2026-03-31 23:37:24 +02:00
Jannis Braun 1f4c2bbeed feat(federation): add relay processors for dm_call_start/accept/reject/end (FED-009) 2026-03-31 23:35:43 +02:00
Jannis Braun 2f47d32136 feat(federation): handleDmCallAccept/Reject/End with federated call support (FED-009) 2026-03-31 23:30:27 +02:00
Jannis Braun f2affb0f1d feat(federation): handleDmCallStart sends S2S to remote instances (FED-009) 2026-03-31 23:27:42 +02:00
Jannis Braun cbcb324e24 feat(federation): add federated call registry with ringing timeout (FED-009) 2026-03-31 23:24:45 +02:00
Jannis Braun 75b84c6ecb feat(federation): add sendCallRelay for direct S2S delivery (FED-009) 2026-03-31 23:22:06 +02:00