Nk/edit permissions - #28
Merged
Merged
Conversation
Add server-side enforcement for the GP/private edit rail and a fileKey-
encrypted view-plane mirror, so the collab-server (not just the room key)
governs who may write and lets viewers load the latest editor state.
Edit admission & revocation:
- verifyEditUcan: verify a gate-minted collab/EDIT UCAN against the pinned
GATE_DID and extract its signed facts (editGrantEpoch, nullifier); the
epoch comes from the token, never a client arg (auth.ts).
- GateEpochCache: passive, single-flight, per-doc editGrantEpoch cache with
expiry-on-read and monotonic writes — no timer, no background poll
(gate-epoch.ts).
- Rail-exclusive admission at /auth: an editUcan admits on the GP rail only
(fresh epoch or 403, no fall-through); the public arm admits only when
collabJoinEnabled === true; workspace via workspaceEditEnabled. Per-actor
identity (nullifier / address) carried on the connection (socket-handlers).
- Per-write chokepoint: reject ddoc writes with EDIT_REVOKED when a GP
editor's admitted epoch is stale or the rail flag is off (owner bypass).
- Owner ops (HTTP): POST /documents/:id/refresh-edit-grant (cache-bypass
refetch + synchronous force-drop of stale GP sockets), workspace-edit-tier,
and collab-join-enabled (owner-op-routes.ts).
- Session flags workspaceEditEnabled / collabJoinEnabled default off
(fail-closed); startup refuses to boot if GATE_DID is set without GATE_URL
(config/index.ts, index.ts).
View-plane mirror (latest-state for viewers):
- document-mirror model + store: upsert, getLatest (ordered by server-stamped
createdAt), purge (mongodb-store.ts).
- handleMirrorSnapshot write handler at POST /documents/mirror-snapshot — any
admitted editor (by rail), never broadcast; the fileKey ciphertext is the
only boundary.
- Open read GET /documents/:documentId/mirror (no auth — the ciphertext IS
the boundary; the server never holds the fileKey).
Config: adds GATE_URL, GATE_DID, COLLAB_SERVER_DID.
Tests: vitest suite updated across handlers + new gate-epoch and
handleMirrorSnapshot suites.
Deploy: land AFTER the gate exposes the 0x03 edit role / editGrantEpoch;
capabilities here stay inert (flags off, no editUcans minted) until the
gate and client cutover ship.
- verifyEditUcan returns a discriminated actor|legacy union - EditBoundCache: fail-closed per-actor (editHandle) admission poll against the gate /edit-bound endpoint, with TTL and stale-bound degrade while the gate is unreachable - enforce admission at the join, write, and mirror chokepoints - POST /documents/:id/evict-edit-actors for targeted cache eviction
…ing-DID workspace arm, {ok,updated} tier route
Phase 1 server side of whole-team PrivateEdit:
- joinOnly connections never create/bind a session (404 ROOM_NOT_ESTABLISHED),
role capped at editor, null-fill heal skipped
- identity-based role determination on bound ddoc sessions: proven identityToken
DID vs bound ownerIdentityDid decides owner/editor; invalid token = 401;
token-less joins keep legacy compare behind LEGACY_ROLE_FALLBACK (default on)
- workspace arm admission replaced: requires ownerToken DID MATCHING the
session's ownerDid + workspaceEditEnabled === true (closes cross-portal hole)
- workspace-edit-tier route returns {ok, updated} row count
- test-mock repairs for the pre-existing editBoundCache/kind stale-mock class
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…actor revocation Remove the legacy gp editGrantEpoch admission rail (GateEpochCache and the /refresh-edit-grant owner op) and enforce edit revocation deterministically: - evict-edit-actors hard-kicks matching gp-actor sockets across all of a document's session rooms (was cache-bust only) - the write and mirror chokepoints disconnect the socket on a failed admission re-check instead of only returning 403 - a throttled awareness re-check drops revoked-but-idle actors Only the targeted actor is affected: every disconnect traces to an exact owner-supplied editHandle or that socket's own failed re-check, and a gate error never kicks (stale-bound grace).
GET /documents/:documentId/share-context -> { exists, isPublished } lets
the app distinguish "created, first publish pending" from a real 404 (the
meta row is upserted on owner connect). Open read — same trust model as
the mirror GET; the handler try/catches to 500 since Express 4 does not
catch async rejections.
…dit eviction - rotate-route + rotation-coordinator: old->new session cutover with opaque relay payload, liveEditors snapshot, minEditEpoch stamped via $max - documentEditEpoch store; auth rejects editUcan below the doc's minEditEpoch - owner-op routes: evict-edit-actors with epoch stamp; socket handlers gate admission on epoch load
… diagnostics share-context GET accepts ?sessionDid= and answers sessionExists through the same getSession lookup the owner-ops open with, so a client can confirm the durable session exists before firing an op that would 404. The owner-op session-not-found branches now log the supplied sessionDid against the doc's non-terminated sessions (absence vs stale-key skew evidence).
…sions are never owner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.