fix(dm): ownership transfer divergence after back-and-forth — canonicalize ownerHomeInstance + normalize authority checks
Manual ownership transfers between two federated instances diverged because `dm_channels.ownerHomeInstance` was stored as a BARE host (`orbit.ddns.net`) for federated owners — via `transferGroupDmOwnership` copying `users.homeInstance` verbatim — while `sourceInstance` always arrives as a full URL on the wire. `processOwnershipTransferEvent` and `processMemberRemoveEvent` then compared the two with strict equality and rejected legitimate inbound events as `unauthorized_source`, keeping ownership permanently divergent across peers. Live DB inspection on the two test instances confirmed both rows (nova + orbit) had a BARE `owner_home_instance`, matching the bug report exactly. Three compounding fixes: 1. Receiver authority checks now compare via `normalizeOriginForCompare` so legacy bare-vs-full rows accept legitimate transfers (and kicks). 2. New `canonicalizeHomeInstance` helper in `federationAuth.ts`; every write site that persists `ownerHomeInstance` (`transferGroupDmOwnership`, group DM creation, lazy federation in member-add, `processMemberAddEvent` bootstrap, `processOwnershipTransferEvent` receiver storage) routes through it. Full URL is the canonical storage form, matching how `sourceInstance` arrives. 3. `dm_owner_updated` WS event extended with optional `newOwnerHomeUserId` and `newOwnerHomeInstance` fields. Client `updateDmOwner` writes them when present and leaves existing values untouched otherwise (legacy-server safe). Without this, `getOwnerInstanceForDm` returned the previous owner's home after a successful WS broadcast, routing the next owner-only op to the wrong instance. Coverage: new `federation.ownershipTransfer.test.ts` (7 receiver tests including the headline bare-vs-full regression and the dedup replay guard); new bare-vs-full case in `federation.kick.test.ts`; two new client-side cases in `groupDm.ownerRouting.test.ts` covering both the extended-payload write path and the legacy-server passthrough. Tests: 1053 server + 364 web, all green. Specs updated: `dm-system.md` historical bugs + frontend handler table + WS state-change events table; `federation.md` `ownership_transfer` receiver flow; `websocket.md` event-fields table.
This commit is contained in:
@@ -129,7 +129,12 @@ interface SpaceState {
|
||||
removeDmChannel: (id: string) => void;
|
||||
addDmMember: (dmChannelId: string, user: User) => void;
|
||||
removeDmMember: (dmChannelId: string, userId: string) => void;
|
||||
updateDmOwner: (dmChannelId: string, newOwnerId: string) => void;
|
||||
updateDmOwner: (
|
||||
dmChannelId: string,
|
||||
newOwnerId: string,
|
||||
newOwnerHomeUserId?: string,
|
||||
newOwnerHomeInstance?: string,
|
||||
) => void;
|
||||
updateDmMetadata: (dmChannelId: string, patch: { name?: string | null; icon?: string | null }) => void;
|
||||
closeDm: (id: string) => Promise<void>;
|
||||
leaveDm: (id: string) => Promise<void>;
|
||||
@@ -321,10 +326,19 @@ export const useSpaceStore = create<SpaceState>((set, get) => ({
|
||||
),
|
||||
})),
|
||||
|
||||
updateDmOwner: (dmChannelId, newOwnerId) => set((state) => ({
|
||||
dmChannels: state.dmChannels.map(dm =>
|
||||
dm.id === dmChannelId ? { ...dm, ownerId: newOwnerId } : dm
|
||||
),
|
||||
updateDmOwner: (dmChannelId, newOwnerId, newOwnerHomeUserId, newOwnerHomeInstance) => set((state) => ({
|
||||
dmChannels: state.dmChannels.map(dm => {
|
||||
if (dm.id !== dmChannelId) return dm;
|
||||
const next = { ...dm, ownerId: newOwnerId };
|
||||
// Only overwrite the federation routing fields when the caller supplies
|
||||
// them. Older servers that omit these fields must not blank out the
|
||||
// existing values — the DM would otherwise lose its owner-routing data
|
||||
// and `getOwnerInstanceForDm` would silently fall back to '' (home),
|
||||
// re-introducing the bug this WS extension fixes.
|
||||
if (newOwnerHomeUserId !== undefined) next.ownerHomeUserId = newOwnerHomeUserId;
|
||||
if (newOwnerHomeInstance !== undefined) next.ownerHomeInstance = newOwnerHomeInstance;
|
||||
return next;
|
||||
}),
|
||||
})),
|
||||
|
||||
// Patches the group DM's display metadata (name + icon). Idempotent: a
|
||||
|
||||
Reference in New Issue
Block a user