Skip to content

Commit 81ce838

Browse files
authored
fix: reset auth state on failed auth, so a new auth message can run auth hooks again. fixes #944 (#1065)
1 parent 667e145 commit 81ce838

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed

packages/server/src/ClientConnection.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,12 @@ export class ClientConnection<Context = any> {
382382
);
383383

384384
this.websocket.send(message.toUint8Array());
385+
386+
// Clean up all state for this document so a retry is treated
387+
// as a fresh first connection attempt.
388+
this.documentConnectionsEstablished.delete(documentName);
389+
delete this.hookPayloads[documentName];
390+
delete this.incomingMessageQueue[documentName];
385391
}
386392

387393
// Catch errors due to failed decoding of data
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import test from "ava";
2+
import {
3+
newHocuspocus,
4+
newHocuspocusProvider,
5+
newHocuspocusProviderWebsocket,
6+
sleep,
7+
} from "../utils/index.ts";
8+
9+
test("provider retries auth with token function after initial failure", async (t) => {
10+
const docName = "superSecretDoc";
11+
const requiredToken = "SUPER-SECRET-TOKEN";
12+
13+
const server = await newHocuspocus({
14+
async onAuthenticate({ token, documentName }) {
15+
if (documentName !== docName) {
16+
throw new Error();
17+
}
18+
19+
if (token !== requiredToken) {
20+
throw new Error();
21+
}
22+
},
23+
});
24+
25+
const socket = newHocuspocusProviderWebsocket(server);
26+
27+
let tokenCallCount = 0;
28+
29+
const provider = newHocuspocusProvider(server, {
30+
websocketProvider: socket,
31+
name: docName,
32+
token: () => {
33+
tokenCallCount++;
34+
return tokenCallCount === 1 ? "wrongToken" : requiredToken;
35+
},
36+
onAuthenticationFailed() {
37+
provider.sendToken();
38+
provider.startSync();
39+
},
40+
});
41+
42+
await sleep(2000);
43+
44+
t.is(tokenCallCount, 2);
45+
t.is(provider.isAuthenticated, true);
46+
});
47+
48+
test("second provider with same doc name succeeds after first fails auth", async (t) => {
49+
const docName = "superSecretDoc";
50+
const requiredToken = "SUPER-SECRET-TOKEN";
51+
52+
const server = await newHocuspocus({
53+
async onAuthenticate({ token, documentName }) {
54+
if (documentName !== docName) {
55+
throw new Error();
56+
}
57+
58+
if (token !== requiredToken) {
59+
throw new Error();
60+
}
61+
},
62+
});
63+
64+
const socket = newHocuspocusProviderWebsocket(server);
65+
66+
const providerFail = newHocuspocusProvider(server, {
67+
websocketProvider: socket,
68+
token: "wrongToken",
69+
name: docName,
70+
onAuthenticated() {
71+
t.fail("providerFail should not authenticate");
72+
},
73+
});
74+
75+
await sleep(1000);
76+
77+
const providerOK = newHocuspocusProvider(server, {
78+
websocketProvider: socket,
79+
token: requiredToken,
80+
name: docName,
81+
onAuthenticationFailed() {
82+
t.fail("providerOK should not fail auth");
83+
},
84+
});
85+
86+
await sleep(1000);
87+
88+
t.is(providerFail.isAuthenticated, false);
89+
t.is(providerOK.isAuthenticated, true);
90+
});

0 commit comments

Comments
 (0)