Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow an application to choose whether a sync connection is read-write or read-only #549

Open
trenta3 opened this issue Jun 25, 2023 · 6 comments
Assignees

Comments

@trenta3
Copy link

trenta3 commented Jun 25, 2023

Is your feature request related to a problem? Please describe.
The need continuously arises to have at least basic levels of permissions on Documents: typically this means sharing the document with someone as editable and with others as read-only.
I think this feature should be implemented inside of Yjs and its transport layers, as it would bring benefits for a lot of use cases.

Describe the solution you'd like
Ideally, the transport layers (I'm thinking of WebRTC which can be used in a decentralized way) should implement a way to pass additional metadata to the connection in order to identify the user in an application-specific way (e.g. I could pass a signature of some data with my private key to certify my identity) and on reception of this message call a custom function that decides whether the other user connected is allowed to (a) write to the Document or (b) read it but not write it (c) not even read it.

Describe alternatives you've considered
For WebSocket connections, this can be implemented server-side with specific code (blocking the YjsUpdate and YjsSyncStep2), but in distributed settings without a central server, this is more easily achievable if Yjs itself implements it internally and externally exposes an API.

Moreover the WebSocket use case could still benefit from it by allowing the client to easily set up a "draft mode", where the Document receives updates from the server but doesn't send the new ones it has locally, and the user can later make the choice to commit the draft or to discard it. This requires that the WS connection is set up as one-way-only during draft mode.

A reference implementation for WS: stoplightio/y-websocket@aa78639

An alternative to implementing an API for it would be to provide more clear documentation on how this can be implemented with the various transport layers.

Additional context
It seems there already is demand for this feature in different places (see list below), and IMHO it would in general greatly raise the usefulness of Yjs as a plug-in solution.

#311
yjs/y-websocket#79
https://discuss.yjs.dev/t/read-only-or-one-way-only-sync/135
https://discuss.yjs.dev/t/about-read-only-mode-and-permissions/1587

@himself65
Copy link
Contributor

I think it's not the things that yjs should handle.

Suppose you want to prohibit some user's modification on the doc. It would be best if you handled it manually on the client code and server code.

The cost here is to persist the allowlist and blocklist(write permission and read-only permission) in memory and runtime, so that will need local storage in the web and database backend in the server. This is out of transport layer things.

In my product (AFFiNE), we control the permission in our editor and during the web socket server connection. The client and server will check if the token is valid. Even if you did bypass the read-only mode on the client side. The modification will be lost when you refresh the page

@trenta3
Copy link
Author

trenta3 commented Jun 30, 2023

Let me try to explain my intentions better:

  • In my usecase the only server is a WebRTC signalling server so that permission checks has to occur on the clients. Of course you would check on each client whether an update is permitted or not, so that bypassing the read-only mode on the client side would have no influence on other clients when receiving that specific update.
  • Permissions for a document can be persisted in the YDoc itself (with a deny all initial policy). I'm aware this has some problems (e.g. when you remove someone and at the same time he pushes an update, the end result is not well-defined) but they don't matter that much for my usecase.

The problem I'm currently experiencing is a lack of documentation on how to implement such functionality (which I think is a common need) so that, even after reading multiple codebases, I'm not sure how to proceed for the basic case (setting read-only a whole document for almost everyone).

@himself65 Do you have specific AFFiNE code I could look at?

@lijie1129
Copy link

I think if your service has at least three layers:

  • UI layer
  • Data binding layer
  • YJS data layer

Then maybe you can implement relevant permission control in the data binding layer, as shown in the figure

image

@himself65
Copy link
Contributor

himself65 commented Jul 1, 2023

In my usecase the only server is a WebRTC signalling server so that permission checks has to occur on the clients. Of course you would check on each client whether an update is permitted or not, so that bypassing the read-only mode on the client side would have no influence on other clients when receiving that specific update.

You can put the permission info into awareness. And I think it's pretty weak because a hacker just opens the F12 console and get the ydoc then he can do anything. You can never guarantee that the client side is safe, always.

@himself65 Do you have specific AFFiNE code I could look at?

We were using y-websocket in the front end and custom rust backend. As you can see, we check the permission when handshake time.

https://github.com/toeverything/OctoBase/blob/ee488e74474ce8bd36474c3ebff6a89cc44de339/apps/cloud/src/api/collaboration.rs#L27-L41

And for now, we are migrating to socket.io. You can see our progress here toeverything/AFFiNE#2881

@himself65
Copy link
Contributor

I also implemented a socket.io provider last year. Hope you can get some inspiration.

https://github.com/TexteaInc/y-socket.io/blob/main/src/server/socket/index.ts

@himself65
Copy link
Contributor

himself65 commented Jul 1, 2023

Forget to say that Yjs already have such things called PermanentUserData

export class PermanentUserData {

Maybe it would be useful in your user case.

https://github.com/yjs/yjs-demos/blob/d8e33e619d6f2da0fae0c6a361286e6901635a9b/prosemirror-versions/prosemirror-versions.js#L143

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants