Commit Graph
421 Commits
Author SHA1 Message Date
Jannis Braun 4ed9b30ce9 fix(federation): don't copy source thumbnail filename to relay attachments
The source instance's thumbnail file doesn't exist locally, so copying
its thumbnailFilename causes a 404 in the chat view. Set to null so
AttachmentRenderer falls back to the full image URL.
2026-03-26 06:37:26 +01:00
Jannis Braun ee8abc1022 fix(federation): file worker UPDATEs existing attachment rows + broadcasts
The file worker now UPDATEs the attachment row created by processCreateEvent
(swapping sourceUrl interim filename to local path) instead of INSERTing a
duplicate. Falls back to INSERT for legacy queue entries.

After download, broadcasts dm_message_updated so clients see the attachment
transition from remote hotlink to local file in real-time.
2026-03-26 06:23:12 +01:00
Jannis Braun 50f7a398dc fix(federation): create attachment rows immediately in processCreateEvent
Insert attachment rows with sourceUrl as interim filename so the initial
WebSocket broadcast includes working remote URLs. AttachmentRenderer
already handles http:// filenames as direct URLs. The file worker will
UPDATE the filename to the local path after download completes.

This fixes the invisible bubble bug where attachment-only messages
(content: null, attachments: []) appeared as empty messages.
2026-03-26 06:21:01 +01:00
Jannis Braun ca591024cf refactor(federation): replace outbox hooks in events.ts with queueDmRelay() 2026-03-26 06:17:51 +01:00
Jannis Braun b49be65dcd refactor(federation): replace outbox hooks in dm.ts with queueDmRelay() 2026-03-26 06:15:13 +01:00
Jannis Braun 58e6dbabcd feat(federation): add queueDmRelay() to consolidate relay payload construction
Single function builds complete relay payload with attachments (including
sourceUrl) and participants. Eliminates 4-way duplication that caused
the attachments: [] hardcode bug.
2026-03-26 06:12:56 +01:00
Jannis Braun cf9fcb78ed fix(federation): add participants array to relay events and fix recipient resolution
The relay was failing because processCreateEvent relied on the friends
table to discover the DM recipient, but friendships aren't federated
across instances. Also, resolveLocalUser matched deleted replicated
users before active ones.

- Add participants[] to FederationRelayEvent with homeUserId/homeInstance
  for all DM channel members
- Add getDmParticipants() helper to look up member identities
- Include participants in outbox payloads (create/update) and sync events
- Rewrite processCreateEvent to resolve participants directly, compute
  canonicalDmPairId, and findOrCreateDmChannel — removing the entire
  friends-list fallback (60+ lines)
- Fix resolveLocalUser to filter out deleted users (is_deleted = 0)
  and prefer the replicated user match when multiple candidates exist
2026-03-26 05:36:41 +01:00
Jannis Braun cb9a70d7a6 fix(federation): use canonical_pair_id for sync scope instead of home_instance matching
The old query missed channels where both members appear as 'local'
(e.g., native Nova + native youruser on orbit). Using
canonical_pair_id is simpler and correct — any channel with a pair
ID is a federated DM that should be synced.
2026-03-26 04:41:00 +01:00
Jannis Braun 49f88c2552 fix(federation): actually remove source_instance IS NULL filter from sync SQL queries 2026-03-26 04:37:47 +01:00
Jannis Braun 277ff0e50c fix(federation): sync ALL locally-created messages, not just local-home-user messages
Messages by replicated users (e.g., Jannis browsing orbit) have
source_instance IS NULL because they were created on that instance.
The sync endpoint was incorrectly filtering these out, causing most
DM history to be missing from the sync response.
2026-03-26 04:34:13 +01:00
Jannis Braun ae004b72e1 feat(federation): trigger checkpoint sync on startup for peers that have never been synced 2026-03-26 04:30:54 +01:00
Jannis Braun a3d41fd568 fix(federation): handle existing canonical_pair_id during backfill by merging duplicate first 2026-03-26 04:25:03 +01:00
Jannis Braun 306e26a525 fix(federation): remove require('crypto') — already imported as ESM at top of file 2026-03-26 04:22:08 +01:00
Jannis Braun 960ca1c97f fix(federation): set correct homeInstance in relay payload and fix canonical backfill SQL
- buildRelayPayload now uses config.domain for local users instead of
  empty string, so the relay receiver can resolve the user
- Fixed canonical_pair_id backfill: SQLite NULL || ':' || x = NULL,
  so use COALESCE(home_user_id, id) instead of concatenation
2026-03-26 04:19:18 +01:00
Jannis Braun 4a109f71da fix(federation): backfill canonical_pair_id and merge duplicate DM channels
Existing 1-on-1 DM channels created before federation had canonical_pair_id = NULL,
causing the relay to create duplicate channels on receipt of a message. On server
startup, migrations now: (1) compute and set canonical_pair_id for all existing
1-on-1 channels using home_user_id (falling back to regular id for local users),
and (2) merge any duplicate channels that share the same canonical_pair_id by
moving all messages, read states, and members into the oldest channel and deleting
the stale duplicate. Both steps are non-fatal and idempotent.
2026-03-26 04:15:12 +01:00
Jannis Braun b34732e064 fix(federation): enable relay by default — it fixes a bug, not an optional feature
Federation relay was incorrectly defaulting to disabled, requiring
admins to discover a buried settings toggle. Since federation itself
is opt-in (you connect instances manually), relay should be on by
default. Also migrates existing instances from 0 → 1.
2026-03-26 03:54:20 +01:00
Jannis Braun c5188c1702 fix(federation): fix TypeScript strict mode error in parseFederationHeaders 2026-03-25 21:51:35 +01:00
Jannis Braun 46e6f8a7e2 feat(federation): add cleanup functions for outbox, mutation log, and file queue TTL 2026-03-25 21:48:46 +01:00
Jannis Braun 44de9b6f41 feat(federation): add client peering integration, transparency indicator, and admin federation settings
Task 13: Hook server-to-server peering into connectToRemote (non-fatal)
and add federation API namespace to the client (initiate, peers, revoke).

Task 14: Show a transparency notice in the DM welcome header when the
other user is on a remote instance, informing that messages are stored
on both home instances and are not end-to-end encrypted.

Task 15: Add Federation section to the instance settings General panel
with DM relay toggle, TTL config, and a live peer list with revoke
buttons. Also extends InstanceAdminSettings type and the server settings
route to support federationRelayEnabled / federationRelayTtlDays.
2026-03-25 21:47:10 +01:00
Jannis Braun 3898e4940a feat(federation): add outbox delivery, file queue download, and health check workers 2026-03-25 21:39:32 +01:00
Jannis Braun 7f20a4517c feat(federation): implement checkpoint sync endpoint using mutation log
Add POST /api/federation/sync for peer instances to catch up on missed
DM mutations after downtime. Uses HMAC-SHA256 authentication (same as
relay), determines shared channels between peers via raw SQL join on
dm_members/users, queries the federation_mutation_log with timestamp
pagination, and returns FederationRelayEvent payloads for each mutation
type (create, update, delete, reaction_add, reaction_remove).
2026-03-25 21:33:47 +01:00
Jannis Braun 5b9272e22b feat(federation): implement relay receive endpoint with dedup, channel mapping, SSRF validation 2026-03-25 21:27:28 +01:00
Jannis Braun fbdec82172 feat(federation): add peering handshake and admin peer management endpoints
Add federation routes module with four endpoints:
- POST /api/federation/peer/initiate — admin-only, starts peering handshake
  with a remote instance (validates origin, generates HMAC secret, sends
  server-to-server accept request with 10s timeout)
- POST /api/federation/peer/accept — server-to-server first contact,
  rate-limited to 10 req/min per IP, creates/activates peer record
- GET /api/federation/peers — admin-only, lists all peers (hmacSecret excluded)
- DELETE /api/federation/peers/:id — admin-only, revokes peer and cleans outbox
2026-03-25 21:19:47 +01:00
Jannis Braun 32a0c2e618 feat(federation): hook outbox and mutation log into DM message and reaction handlers
Wire appendMutationLog + queueOutboxEvent + buildRelayPayload into all
DM mutation paths so federation peers receive relay events:

- REST: POST /api/dm/:id/messages, PATCH /api/dm/messages/:id,
  DELETE /api/dm/messages/:id
- WebSocket: dm_message_create, dm_message_edit, dm_message_delete,
  reaction_add (DM path), reaction_remove (DM path)
- Fix buildRelayPayload parameter types to accept optional replyToId
  and editedAt (matching DmMessageWithUser's optional fields)
2026-03-25 21:14:34 +01:00
Jannis Braun 88ad1c676d feat(federation): add outbox coalescing writer, mutation log, and relay helpers 2026-03-25 21:07:16 +01:00
Jannis Braun 2ff53a3f9b feat(federation): add HMAC signing and verification utilities
Implements federationAuth.ts with generateHmacSecret, signRequest,
verifySignature (constant-time, 15-minute replay window), buildFederationHeaders,
and parseFederationHeaders for server-to-server federation authentication.
2026-03-25 21:03:03 +01:00
Jannis Braun 4726eeb097 feat(federation): add migrations for federation tables, indexes, and mutation log backfill
Adds column migrations (dm_channels.canonical_pair_id, dm_messages.source_*,
attachments.source_url, instance_settings.federation_relay_*), CREATE TABLE
for federation_peers/outbox/file_queue/mutation_log, partial/filtered indexes,
and an idempotent backfill that seeds the mutation log from existing DM messages.

Also makes migrateDmMessagesReplyToFk and migrateAttachmentsDmMessageFk
column-aware so table recreation preserves any columns added by earlier
migrations (prevents dropping federation columns on upgrade).
2026-03-25 21:00:10 +01:00
Jannis Braun c80d565f7f feat(federation): add federation tables and columns to schema 2026-03-25 20:53:43 +01:00
Jannis Braun b4582a53f1 feat: wire allowCustomBitrate through rowToLimits and PATCH handler in settings route 2026-03-25 04:05:48 +01:00
Jannis Braun f02cb4e52a feat: add allow_custom_bitrate column to instance_settings schema and migration 2026-03-25 04:05:43 +01:00
Jannis Braun 7240aac2e0 fix(social): allow single-char search and prefer native federated profiles
Lower minimum query length from 2 to 1 character so single-letter
searches return results.

Fix dedup to prefer native profiles (homeUserId=null) over replicated
ones. Previously the first-seen result won, which was usually the
local replicated profile (no instance badge, namespaced username).
Now when a native profile is found on the remote instance, it replaces
the replicated copy — showing the clean username with the instance badge.
2026-03-25 02:26:33 +01:00
Jannis Braun 123e40bb9a feat(server): populate embed width/height from OG tags and image probing 2026-03-25 01:53:39 +01:00
Jannis Braun bbb2c8bd19 feat(server): extract og:image:width/height from HTML metadata 2026-03-25 01:51:31 +01:00
Jannis Braun 2f883c6f63 refactor(server): extract SSRF validation into shared ssrf.ts utility 2026-03-25 01:48:18 +01:00
Jannis Braun f90cf91760 fix: immediately clean up voice state when voice-active socket disconnects
Previously, voice cleanup only happened in finalizeDisconnect after a
5-second grace period, and only when ALL connections closed. If a user
was logged in on multiple devices and closed the one in voice, the
voice state was never cleaned up — creating a permanent ghost in the
channel sidebar.

Now removeConnection checks if the closing socket is the voice-active
one (tracked via voiceWs map) and immediately cleans up voice state,
broadcasts the leave event, and notifies remaining tabs.
2026-03-23 14:30:57 +01:00
Jannis Braun 319832ebc8 feat: clear voiceWs on all voice leave paths 2026-03-23 14:27:42 +01:00
Jannis Braun cc10876b93 feat: wire setVoiceWs into voice join and DM call handlers
Adds device-switch guardrail to handleVoiceJoin: if the user already
has a voice session on a different WebSocket, that old socket receives
a voice_disconnected/displaced event before the new session takes over.
Also binds voiceWs on the caller socket in handleDmCallStart and on
the acceptor socket in handleDmCallAccept.
2026-03-23 14:25:42 +01:00
Jannis Braun 357ba8b4d9 refactor: thread ws parameter through handleClientEvent to voice handlers 2026-03-23 14:23:48 +01:00
Jannis Braun 876326e7b7 feat: add voiceWs map and accessors to ConnectionManager 2026-03-23 14:22:28 +01:00
Jannis Braun 58f6fb1baa fix: raise upload rate limit from 10 to 30 per minute 2026-03-23 02:55:00 +01:00
Jannis Braun fdc8732ba7 fix: raise upload size cap from 500 MB to 5 GB
The 500 MB cap was arbitrary. Self-hosted admins should decide based
on their own disk space. Global multipart ceiling raised to 5 GB,
validation cap to 5120 MB.
2026-03-23 02:36:22 +01:00
Jannis Braun 4807bfeeb1 feat: read/write maxUploadSizeMb in instance settings API 2026-03-23 02:22:22 +01:00
Jannis Braun 5be2e73c8a fix: use per-request upload limit from DB and fix silent truncation
Reads max_upload_size_bytes from instance_settings per-request and
passes it to request.file() so Fastify kills the stream at the
admin's configured limit. Checks file.truncated to properly reject
files that exceed the limit instead of saving corrupted data.
2026-03-23 02:22:15 +01:00
Jannis Braun ee11408060 feat: add age-based media cleanup endpoint
Adds cleanupOldMedia() to the storage janitor and a new endpoint
POST /api/admin/storage/cleanup-media { maxAgeDays, dryRun }.
Deletes chat attachments older than the specified threshold while
preserving profile images.
2026-03-23 02:20:28 +01:00
Jannis Braun eb743ca2ca feat: add max_upload_size_bytes column and shared type 2026-03-23 02:19:42 +01:00
Jannis Braun f634f35450 fix: add FK constraint to attachments.dm_message_id via table rebuild
Rebuilds the attachments table with ON DELETE CASCADE on dm_message_id.
Dangling records are excluded during copy — their files become standard
disk orphans detectable by the storage janitor. Also updates the raw SQL
in index.ts to include width/height/duration columns for fresh installs.
2026-03-23 01:55:26 +01:00
Jannis Braun 8bcb5cc977 fix: wrap DM message deletion in transaction for atomicity
Matches the space message delete handler pattern. Prevents inconsistent
state if the server crashes between individual delete operations.
2026-03-23 01:54:42 +01:00
Jannis Braun d687988ba8 fix: use explicit counter for danglingFilesOnDisk in referencedFiles calc
Matches the orphanedFiles counting pattern and avoids a subtle
discrepancy if two dangling records shared the same filename.
2026-03-23 01:53:25 +01:00
Jannis Braun b40999995b feat: detect and clean up dangling attachment records in storage janitor
Adds getDanglingAttachments() to find attachment records whose
messageId/dmMessageId points to a deleted message. Updates stats
to show dangling count and cleanup to delete them.
2026-03-23 01:49:41 +01:00
Jannis Braun 1bd684a6b0 feat: self-healing login for federated users with stale password hashes
When a federated user's local password hash is stale (e.g. they changed
their password on the home instance and sync failed), the login handler
now falls back to verifying credentials against the home instance. If the
home instance accepts the password, the local hash is silently updated
without touching passwordChangedAt, so existing valid JWTs remain valid.
2026-03-23 00:48:32 +01:00