fix(spaces): pin visibility and ownerDID as server-authoritative
Automerge CRDT sync could overwrite space visibility when a client with a stale cached doc reconnects and merges. Now the server snapshots visibility and ownerDID before processing sync messages and reverts any client-side changes to these fields. These fields can only be changed through the authenticated API (PATCH /api/spaces/:slug), not through CRDT sync. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
dce608ae1b
commit
c0b4250e96
|
|
@ -618,6 +618,10 @@ export function receiveSyncMessage(
|
||||||
|
|
||||||
const peerState = getPeerSyncState(slug, peerId);
|
const peerState = getPeerSyncState(slug, peerId);
|
||||||
|
|
||||||
|
// Snapshot server-authoritative fields before sync merge
|
||||||
|
const prevVisibility = doc.meta?.visibility;
|
||||||
|
const prevOwnerDID = doc.meta?.ownerDID;
|
||||||
|
|
||||||
// Apply incoming sync message
|
// Apply incoming sync message
|
||||||
const result = Automerge.receiveSyncMessage(
|
const result = Automerge.receiveSyncMessage(
|
||||||
doc,
|
doc,
|
||||||
|
|
@ -625,9 +629,23 @@ export function receiveSyncMessage(
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
|
|
||||||
const newDoc = result[0];
|
let newDoc = result[0];
|
||||||
const newSyncState = result[1];
|
const newSyncState = result[1];
|
||||||
|
|
||||||
|
// Pin server-authoritative fields — clients must not overwrite these via sync.
|
||||||
|
// Visibility and ownership can only be changed through the authenticated API.
|
||||||
|
if (newDoc !== doc) {
|
||||||
|
const visChanged = newDoc.meta?.visibility !== prevVisibility;
|
||||||
|
const ownerChanged = newDoc.meta?.ownerDID !== prevOwnerDID;
|
||||||
|
if (visChanged || ownerChanged) {
|
||||||
|
console.warn(`[Store] Sync tried to change authoritative fields in ${slug} — reverting (vis: ${prevVisibility}→${newDoc.meta?.visibility}, owner: ${prevOwnerDID}→${newDoc.meta?.ownerDID})`);
|
||||||
|
newDoc = Automerge.change(newDoc, 'Pin server-authoritative fields', (d) => {
|
||||||
|
if (visChanged && prevVisibility) d.meta.visibility = prevVisibility;
|
||||||
|
if (ownerChanged && prevOwnerDID) d.meta.ownerDID = prevOwnerDID;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
communities.set(slug, newDoc);
|
communities.set(slug, newDoc);
|
||||||
peerState.syncState = newSyncState;
|
peerState.syncState = newSyncState;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue