ueberdosis / hocuspocus

The CRDT Yjs WebSocket backend for conflict-free real-time collaboration in your app.
https://tiptap.dev/docs/hocuspocus/introduction
MIT License
1.29k stars 125 forks source link

Periodic reauthentication #752

Open haines opened 11 months ago

haines commented 11 months ago

The problem I am facing We check that the user is authorized to edit the document during the initial authentication handshake. But if the user's access is revoked while they have a connection open, they can continue to edit the document until they reconnect.

The solution I would like I would like the server to keep track of the last time it received an authentication message, to be able to configure a timeout after which a client who has not reauthenticated would be disconnected, and to be able to configure the provider to reauthenticate on a given interval.

solirpa commented 11 months ago

you can use debounce on onChange hook, for example:

import { debounce } from "debounce";
import { Server } from "@hocuspocus/server";

let debounced;

const server = Server.configure({
  async onChange(data) {
    const reauth = () => {
        // your logic
    };

    debounced?.clear();
    debounced = debounce(reauth, 30000);
    debounced();
  },
});

server.listen();
haines commented 11 months ago

That wouldn't work for us, unfortunately. We have short-lived access tokens stored in the user's session cookie and we need to refresh those and update the cookie, which requires some participation of the provider rather than just doing it in the server.

Our token function in the provider performs an HTTP request to issue a token, and the auth logic is handled within that HTTP handler. So if we could just have the provider call the token function on an interval and send an authentication message to the server, that'd be ideal.

mortenson commented 10 months ago

I think @solirpa's solution would work for me, but having a re-auth period as a configuration option would feel a bit cleaner

janthurau commented 9 months ago

In order to re-evaluate the permissions of a user before applying a message, you can use the beforeHandleMessage hook. It's called before every received message, so you might want to cache expensive permission checks.

I'll keep this open to track progress on resending authentication information from the provider side, which we currently don't officially support.

p1nox commented 9 months ago

In order to re-evaluate the permissions of a user before applying a message, you can use the beforeHandleMessage hook. It's called before every received message, so you might want to cache expensive permission checks.

I'll keep this open to track progress on resending authentication information from the provider side, which we currently don't officially support.

In case the process to re-evaluate the permissions of each user while is using editing the doc is fetching a record in a relational database (or course, a future improvement would be to replicate this in something like Redis), which hook would you recommend @janthurau ? beforeHandleMessage mentioned by you, or onChange mentioned by @solirpa ? What could be the different between those two? I guess that performance should be the variable to take in account for answering this question.

Separate question, depending of which hook is used, if the check passes nothing happens, but if the user is no longer authorized to read-write that doc, an error is returned to the client side, and I guess the connection is closed in the provider? And then how can this be handled in the client side? In which listener this error is received?

talhazubairbutt commented 8 months ago

You can use stateless messages to send newly issued tokens(by your API) from the client to the server via the socket. The token can be persisted/associated with the connection on the server side and then used to apply/discard updates or even drop the connection when needed.

BrentFarese commented 7 months ago

@p1nox stumbled across this ticket b/c we're trying to periodically re-authenticate too. Our approach was to use beforeHandleMessage but we strap on a lastAuthCheck onto context in the onAuthenticate hook first. Then, we only re-check authentication every N seconds based on lastAuthCheck (which is updated every time re-authentication happens). This is pretty effective since it's performant (only checking authentication using out API every N seconds), but it also ensures a user that is working with a document maintains access/is re-authenticated on a decent frequency.

RubaXa commented 5 months ago

I'll keep this open to track progress on resending authentication information from the provider side, which we currently don't officially support.

Now this logic is sorely lacking, there is only one output, invoke disconnect on server 🙁

Ideally, I need