Generate an inverse update?

I’m implementing support for Y.js in Dexie Cloud and need advice on how to implement update rejection.

An update that reaches the Dexie Cloud server endpoint might be rejected due to access control violation. Is there a way in a client to revert an update in an open Y.Doc instance? What I’ve read so far is the recommendation to produce a reverse update. The problem is how to produce such given an update on a document. From what I’ve read so far, there is no direct library function that can produce n inversion of an update. One option would be to rebuild the document from all updates except the rejected one, but then it would be hard to get existing editors updated with that change as they are already holding on to the existing Y.Doc instance and listening to its events.

Any help would be much appreciated.

David

What about using an undo manager?

What I’ve seen so far, the UndoManager can only undo the last update. This is not sufficient since the rejection might come some time later and the user might have been doing more updates in between.

Maybe it’s possible to extract code from UndoManager to accomplish this… unless someone has already done it…

Or maybe a solution would be to:

  1. Create a new Y.Doc instance and an UndoManager.
  2. Apply all updates from start until the rejected update but not include updates that have arrived since.
  3. Do undoManager.undo() and record the inverse update it produces?

Would that work?

Yjs only supports full access / no access and read-only access. I recommend rejecting update messages from read-only/noaccess users before you apply them to a document. In y-protocol, this can be achieved by rejecting messages that start with [0,1] and [0,2].

Allowing access to only a part of a Yjs document in a secure manner is currently not possible. It would require a dedicated API that performs multiple sanity checks on updates to avoid security violations.

I don’t recommend using the undo manager to revert updates for access control either. Firstly, it is inefficient to apply an update and then revert it immediately. More importantly, it would be easy to trick a remote peer into accepting unauthorized updates like that. I’m not sharing how because I know that some companies implemented this approach against my advice.

If you want different access rules for different parts of your data, I recommend splitting your data into two or more Yjs (sub)documents. You can even include Yjs documents into other Yjs documents, making the data look more “whole”.

I’d recommend splitting data over multiple documents even if we had that special API that performs auth checks on updates. 1. Splitting data works in systems that don’t have a central source to dictate auth*. 2. Checking updates requires reading content fully and performing a parent check on every single insertion before applying the update to the document. With document-level checks we only need to check the first two bytes. 3. Having two auth APIs (document-level, part of document) just complicates things for users.

I’m really looking forward to Yjs support in Dexie Cloud :star_struck:
(btw, I’d love an announcement in this forum)

1 Like

Many thanks for these advice. I’m not planning for more detailed access control than read/write/no-access per document, so that probably simplifies things.

The problem I haven’t been able to solve is how to communicate to open Doc instances that a certain update should be rejected. For example I assume that editors such as Tiptap or Prosemirror talk directly to the Doc instance and produce updates. Or can we provide them with a read-only Doc instance? If some way of solving this is possible synchronoously that would be great. But I will also need to reject persisted updates that have already been stored locally but rejected by the server. This would be a rare event and I think performance wouldn’t be crucial there. I would never persist the inverse updates - it would only be a way to communicate to open Doc instances. Maybe UndoManager could help me in that situation? Maybe I could get the inverse updates by creating a fresh Doc instance with only the rejected updates and then undo them and track the updates that is generated from UndoManager and finally apply these inverse updates to the open Doc instances?

Thanks for your interest in Dexie Cloud! The Y.js support is my no 1 prio right now. I finished the work in the “dexie” package this week and will soon release a beta of it, while I’m continuing the work on “dexie-cloud-addon” and the server-end.

Yjs currently doesn’t have a read-only feature. However, you could simulate it by throwing an exception when the user creates a transaction in the beforeTransaction event.

Most editor bindings, however, do have a read-only feature.

1 Like