docs: document S2S profile sync relay and write-protection guard
This commit is contained in:
@@ -28,6 +28,8 @@ GET /users/:id → { user }
|
|||||||
GET /users/:id/mutuals ?homeUserId= → { mutualFriends[], mutualSpaces[] }
|
GET /users/:id/mutuals ?homeUserId= → { mutualFriends[], mutualSpaces[] }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Write protection:** If the authenticated user is a replicated user (`homeInstance` is set), the following fields are rejected with 403: `displayName`, `avatar`, `banner`, `accentColor`, `avatarColor`, `bio`. These fields are managed by the home instance via S2S relay.
|
||||||
|
|
||||||
## Spaces (`routes/spaces.ts`) — auth required
|
## Spaces (`routes/spaces.ts`) — auth required
|
||||||
```
|
```
|
||||||
GET /spaces → { spaces[] }
|
GET /spaces → { spaces[] }
|
||||||
|
|||||||
@@ -706,15 +706,30 @@ When relay events carry `FederationRelayProfileSnapshot` data:
|
|||||||
|
|
||||||
`hydrateReplicatedUserProfile` only fills null/empty fields. Avatar/banner are overwritten only if the current value is not an absolute URL (catches stale bare filenames).
|
`hydrateReplicatedUserProfile` only fills null/empty fields. Avatar/banner are overwritten only if the current value is not an absolute URL (catches stale bare filenames).
|
||||||
|
|
||||||
### Client-side LWW Sync (`profileSync.ts`)
|
#### Profile Sync (S2S)
|
||||||
|
|
||||||
Operates via the web client, not S2S relay:
|
Profile data is synced server-to-server. The home instance is authoritative — when a user updates their profile, the home server queues a `profile_update` relay event to all active peers via the outbox.
|
||||||
- On connect to a remote instance, compares `profileUpdatedAt` timestamps
|
|
||||||
- If home is newer: pushes profile to remote (re-uploads avatar/banner)
|
|
||||||
- If remote is newer: pulls from remote to home, then relays to all other remotes
|
|
||||||
- Incremental: `syncProfileUpdateToRemotes` pushes partial updates after local profile edits
|
|
||||||
|
|
||||||
This is a **client-driven** mechanism -- it only runs when a user is actively connected to multiple instances. It does not use the relay pipeline or outbox.
|
**Event:** `profile_update` (contextType: `profile`)
|
||||||
|
|
||||||
|
**Payload:** `FederationProfileUpdatePayload` — full snapshot of 6 durable fields:
|
||||||
|
- `homeUserId`, `homeInstance`, `profileUpdatedAt` (monotonic version)
|
||||||
|
- `displayName`, `avatar` (absolute URL or null), `banner` (absolute URL or null)
|
||||||
|
- `accentColor`, `avatarColor`, `bio`
|
||||||
|
|
||||||
|
**Targeting:** Broadcast to all active peers. Peers silently accept if they have no replica.
|
||||||
|
|
||||||
|
**Coalescing:** `entityId = homeUserId`. Rapid successive edits coalesce to one delivery per peer.
|
||||||
|
|
||||||
|
**Processing:** Remote overwrites all 6 fields unconditionally. Rejects if incoming `profileUpdatedAt ≤ stored`. Broadcasts `user_updated` to local WS clients.
|
||||||
|
|
||||||
|
**Write protection:** Remote instances reject PATCH /users/@me profile field updates for replicated users (homeInstance set). Profile data is read-only on remote — only updated via S2S relay.
|
||||||
|
|
||||||
|
**Bootstrap:** When a new origin appears in a user's `replicatedInstances`, the home server queues a targeted `profile_update` to that peer.
|
||||||
|
|
||||||
|
**File handling:** Avatar/banner stored as absolute URLs pointing to the home instance's `/api/uploads/`. Browser caching (immutable, 1-year) ensures performance.
|
||||||
|
|
||||||
|
**Replaces:** Client-driven `profileSync.ts` (deleted). `hydrateReplicatedUserProfile` still bootstraps null fields during DM/friend relay — it is not replaced.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user