Skip to content

Commit 7e1eed3

Browse files
committed
✨(y-provider) check hocuspocus documentName validity
We only use uuid v4 as hocuspocus dicument name. To be sure nothing else is used we check that the documentName is a valid uuid version 4.
1 parent 8bee476 commit 7e1eed3

File tree

4 files changed

+133
-39
lines changed

4 files changed

+133
-39
lines changed

src/frontend/servers/y-provider/__tests__/hocusPocusWS.test.ts

Lines changed: 104 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
HocuspocusProvider,
33
HocuspocusProviderWebsocket,
44
} from '@hocuspocus/provider';
5+
import { v1 as uuidv1, v4 as uuidv4 } from 'uuid';
56
import WebSocket from 'ws';
67

78
const port = 5559;
@@ -52,9 +53,9 @@ describe('Server Tests', () => {
5253

5354
test('WebSocket connection with bad origin should be closed', () => {
5455
const { promise, done } = promiseDone();
55-
56+
const room = uuidv4();
5657
const ws = new WebSocket(
57-
`ws://localhost:${port}/collaboration/ws/?room=test-room`,
58+
`ws://localhost:${port}/collaboration/ws/?room=${room}`,
5859
{
5960
headers: {
6061
Origin: 'http://bad-origin.com',
@@ -72,9 +73,9 @@ describe('Server Tests', () => {
7273

7374
test('WebSocket connection without cookies header should be closed', () => {
7475
const { promise, done } = promiseDone();
75-
76+
const room = uuidv4();
7677
const ws = new WebSocket(
77-
`ws://localhost:${port}/collaboration/ws/?room=test-room`,
78+
`ws://localhost:${port}/collaboration/ws/?room=${room}`,
7879
{
7980
headers: {
8081
Origin: origin,
@@ -92,25 +93,96 @@ describe('Server Tests', () => {
9293

9394
test('WebSocket connection not allowed if room not matching provider name', () => {
9495
const { promise, done } = promiseDone();
95-
96+
const room = uuidv4();
9697
const wsHocus = new HocuspocusProviderWebsocket({
97-
url: `ws://localhost:${portWS}/?room=my-test`,
98+
url: `ws://localhost:${portWS}/?room=${room}`,
9899
WebSocketPolyfill: WebSocket,
99100
maxAttempts: 1,
100101
quiet: true,
101102
});
102103

104+
const providerName = uuidv4();
103105
const provider = new HocuspocusProvider({
104106
websocketProvider: wsHocus,
105-
name: 'hocuspocus-test',
107+
name: providerName,
106108
broadcast: false,
107109
quiet: true,
108110
preserveConnection: false,
109111
onClose: (data) => {
110112
expect(console.error).toHaveBeenCalledWith(
111113
'Invalid room name - Probable hacking attempt:',
112-
'hocuspocus-test',
113-
'my-test',
114+
providerName,
115+
room,
116+
);
117+
118+
wsHocus.stopConnectionAttempt();
119+
expect(data.event.reason).toBe('Forbidden');
120+
wsHocus.webSocket?.close();
121+
wsHocus.disconnect();
122+
provider.destroy();
123+
wsHocus.destroy();
124+
done();
125+
},
126+
});
127+
128+
return promise;
129+
});
130+
131+
test('WebSocket connection not allowed if room is not a valid uuid v4', () => {
132+
const { promise, done } = promiseDone();
133+
const room = uuidv1();
134+
const wsHocus = new HocuspocusProviderWebsocket({
135+
url: `ws://localhost:${portWS}/?room=${room}`,
136+
WebSocketPolyfill: WebSocket,
137+
maxAttempts: 1,
138+
quiet: true,
139+
});
140+
141+
const provider = new HocuspocusProvider({
142+
websocketProvider: wsHocus,
143+
name: room,
144+
broadcast: false,
145+
quiet: true,
146+
preserveConnection: false,
147+
onClose: (data) => {
148+
expect(console.error).toHaveBeenCalledWith(
149+
'Room name is not a valid uuid:',
150+
room,
151+
);
152+
153+
wsHocus.stopConnectionAttempt();
154+
expect(data.event.reason).toBe('Forbidden');
155+
wsHocus.webSocket?.close();
156+
wsHocus.disconnect();
157+
provider.destroy();
158+
wsHocus.destroy();
159+
done();
160+
},
161+
});
162+
163+
return promise;
164+
});
165+
166+
test('WebSocket connection not allowed if room is not a valid uuid', () => {
167+
const { promise, done } = promiseDone();
168+
const room = 'not-a-valid-uuid';
169+
const wsHocus = new HocuspocusProviderWebsocket({
170+
url: `ws://localhost:${portWS}/?room=${room}`,
171+
WebSocketPolyfill: WebSocket,
172+
maxAttempts: 1,
173+
quiet: true,
174+
});
175+
176+
const provider = new HocuspocusProvider({
177+
websocketProvider: wsHocus,
178+
name: room,
179+
broadcast: false,
180+
quiet: true,
181+
preserveConnection: false,
182+
onClose: (data) => {
183+
expect(console.error).toHaveBeenCalledWith(
184+
'Room name is not a valid uuid:',
185+
room,
114186
);
115187

116188
wsHocus.stopConnectionAttempt();
@@ -131,16 +203,17 @@ describe('Server Tests', () => {
131203

132204
mockDocFetch.mockRejectedValue('');
133205

206+
const room = uuidv4();
134207
const wsHocus = new HocuspocusProviderWebsocket({
135-
url: `ws://localhost:${portWS}/?room=my-test`,
208+
url: `ws://localhost:${portWS}/?room=${room}`,
136209
WebSocketPolyfill: WebSocket,
137210
maxAttempts: 1,
138211
quiet: true,
139212
});
140213

141214
const provider = new HocuspocusProvider({
142215
websocketProvider: wsHocus,
143-
name: 'my-test',
216+
name: room,
144217
broadcast: false,
145218
quiet: true,
146219
preserveConnection: false,
@@ -167,29 +240,30 @@ describe('Server Tests', () => {
167240
test('WebSocket connection fails if user do not have correct retrieve ability', () => {
168241
const { promise, done } = promiseDone();
169242

243+
const room = uuidv4();
170244
mockDocFetch.mockResolvedValue({
171245
abilities: {
172246
retrieve: false,
173247
},
174248
});
175249

176250
const wsHocus = new HocuspocusProviderWebsocket({
177-
url: `ws://localhost:${portWS}/?room=my-test`,
251+
url: `ws://localhost:${portWS}/?room=${room}`,
178252
WebSocketPolyfill: WebSocket,
179253
maxAttempts: 1,
180254
quiet: true,
181255
});
182256

183257
const provider = new HocuspocusProvider({
184258
websocketProvider: wsHocus,
185-
name: 'my-test',
259+
name: room,
186260
broadcast: false,
187261
quiet: true,
188262
preserveConnection: false,
189263
onClose: (data) => {
190264
expect(console.error).toHaveBeenCalledWith(
191265
'onConnect: Unauthorized to retrieve this document',
192-
'my-test',
266+
room,
193267
);
194268

195269
wsHocus.stopConnectionAttempt();
@@ -217,19 +291,20 @@ describe('Server Tests', () => {
217291
},
218292
});
219293

294+
const room = uuidv4();
220295
const wsHocus = new HocuspocusProviderWebsocket({
221-
url: `ws://localhost:${portWS}/?room=hocuspocus-test`,
296+
url: `ws://localhost:${portWS}/?room=${room}`,
222297
WebSocketPolyfill: WebSocket,
223298
});
224299

225300
const provider = new HocuspocusProvider({
226301
websocketProvider: wsHocus,
227-
name: 'hocuspocus-test',
302+
name: room,
228303
broadcast: false,
229304
quiet: true,
230305
onConnect: () => {
231306
void hocusPocusServer
232-
.openDirectConnection('hocuspocus-test')
307+
.openDirectConnection(room)
233308
.then((connection) => {
234309
connection.document?.getConnections().forEach((connection) => {
235310
expect(connection.readOnly).toBe(!canEdit);
@@ -262,29 +337,28 @@ describe('Server Tests', () => {
262337
id: 'test-user-id',
263338
});
264339

340+
const room = uuidv4();
265341
const wsHocus = new HocuspocusProviderWebsocket({
266-
url: `ws://localhost:${portWS}/?room=hocuspocus-test`,
342+
url: `ws://localhost:${portWS}/?room=${room}`,
267343
WebSocketPolyfill: WebSocket,
268344
});
269345

270346
const provider = new HocuspocusProvider({
271347
websocketProvider: wsHocus,
272-
name: 'hocuspocus-test',
348+
name: room,
273349
broadcast: false,
274350
quiet: true,
275351
onConnect: () => {
276-
void hocusPocusServer
277-
.openDirectConnection('hocuspocus-test')
278-
.then((connection) => {
279-
connection.document?.getConnections().forEach((connection) => {
280-
expect(connection.context.userId).toBe('test-user-id');
281-
});
282-
283-
void connection.disconnect();
284-
provider.destroy();
285-
wsHocus.destroy();
286-
done();
352+
void hocusPocusServer.openDirectConnection(room).then((connection) => {
353+
connection.document?.getConnections().forEach((connection) => {
354+
expect(connection.context.userId).toBe('test-user-id');
287355
});
356+
357+
void connection.disconnect();
358+
provider.destroy();
359+
wsHocus.destroy();
360+
done();
361+
});
288362
},
289363
});
290364

src/frontend/servers/y-provider/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"cors": "2.8.5",
2525
"express": "4.21.2",
2626
"express-ws": "5.0.2",
27+
"uuid": "11.1.0",
2728
"y-protocols": "1.0.6",
2829
"yjs": "*"
2930
},

src/frontend/servers/y-provider/src/servers/hocusPocusServer.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Server } from '@hocuspocus/server';
2+
import { validate as uuidValidate, version as uuidVersion } from 'uuid';
23

34
import { fetchDocument } from '@/api/getDoc';
45
import { getMe } from '@/api/getMe';
@@ -27,6 +28,12 @@ export const hocusPocusServer = Server.configure({
2728
return Promise.reject(new Error('Wrong room name: Unauthorized'));
2829
}
2930

31+
if (!uuidValidate(documentName) || uuidVersion(documentName) !== 4) {
32+
console.error('Room name is not a valid uuid:', documentName);
33+
34+
return Promise.reject(new Error('Wrong room name: Unauthorized'));
35+
}
36+
3037
let can_edit = false;
3138

3239
try {
@@ -35,7 +42,7 @@ export const hocusPocusServer = Server.configure({
3542
if (!document.abilities.retrieve) {
3643
console.error(
3744
'onConnect: Unauthorized to retrieve this document',
38-
roomParam,
45+
documentName,
3946
);
4047
return Promise.reject(new Error('Wrong abilities:Unauthorized'));
4148
}

src/frontend/yarn.lock

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6334,13 +6334,20 @@
63346334
dependencies:
63356335
"@types/node" "*"
63366336

6337-
"@types/node@*", "@types/node@22.10.7", "@types/[email protected]", "@types/node@^22.7.5":
6337+
"@types/node@*", "@types/node@^22.7.5":
63386338
version "22.13.9"
63396339
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.9.tgz#5d9a8f7a975a5bd3ef267352deb96fb13ec02eca"
63406340
integrity sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==
63416341
dependencies:
63426342
undici-types "~6.20.0"
63436343

6344+
6345+
version "22.10.7"
6346+
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.7.tgz#14a1ca33fd0ebdd9d63593ed8d3fbc882a6d28d7"
6347+
integrity sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==
6348+
dependencies:
6349+
undici-types "~6.20.0"
6350+
63446351
"@types/parse-json@^4.0.0":
63456352
version "4.0.2"
63466353
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
@@ -6396,7 +6403,7 @@
63966403
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
63976404
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
63986405

6399-
"@types/react-dom@*", "@types/[email protected]":
6406+
"@types/react-dom@*":
64006407
version "19.0.0"
64016408
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.0.tgz#e7f5d618a080486eaf9952246dbf59eaa2c64130"
64026409
integrity sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w==
@@ -6415,7 +6422,7 @@
64156422
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044"
64166423
integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==
64176424

6418-
"@types/react@*", "@types/[email protected]":
6425+
"@types/react@*":
64196426
version "19.0.0"
64206427
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.0.tgz#fbbb53ce223f4e2b750ad5dd09580b2c43522bbf"
64216428
integrity sha512-MY3oPudxvMYyesqs/kW1Bh8y9VqSmf+tzqw3ae8a9DZW68pUe3zAdHeI1jc6iAysuRdACnVknHP8AhwD4/dxtg==
@@ -6533,7 +6540,7 @@
65336540
dependencies:
65346541
"@types/yargs-parser" "*"
65356542

6536-
"@typescript-eslint/eslint-plugin@*", "@typescript-eslint/eslint-plugin@8.26.0", "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
6543+
"@typescript-eslint/eslint-plugin@*", "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
65376544
version "8.26.0"
65386545
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz#7e880faf91f89471c30c141951e15f0eb3a0599e"
65396546
integrity sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==
@@ -6548,7 +6555,7 @@
65486555
natural-compare "^1.4.0"
65496556
ts-api-utils "^2.0.1"
65506557

6551-
"@typescript-eslint/parser@*", "@typescript-eslint/parser@8.26.0", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
6558+
"@typescript-eslint/parser@*", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
65526559
version "8.26.0"
65536560
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.26.0.tgz#9b4d2198e89f64fb81e83167eedd89a827d843a9"
65546561
integrity sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==
@@ -8793,7 +8800,7 @@ eslint-visitor-keys@^4.2.0:
87938800
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45"
87948801
integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
87958802

8796-
8803+
eslint@*:
87978804
version "8.57.0"
87988805
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668"
87998806
integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==
@@ -15086,7 +15093,7 @@ typed-array-length@^1.0.7:
1508615093
possible-typed-array-names "^1.0.0"
1508715094
reflect.getprototypeof "^1.0.6"
1508815095

15089-
typescript@*, typescript@5.8.2, typescript@^5.0.4:
15096+
typescript@*, typescript@^5.0.4:
1509015097
version "5.8.2"
1509115098
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
1509215099
integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==
@@ -15387,6 +15394,11 @@ [email protected]:
1538715394
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
1538815395
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
1538915396

15397+
15398+
version "11.1.0"
15399+
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912"
15400+
integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==
15401+
1539015402
uuid@^11.0.3:
1539115403
version "11.0.5"
1539215404
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.5.tgz#07b46bdfa6310c92c3fb3953a8720f170427fc62"
@@ -16103,7 +16115,7 @@ [email protected], yargs@^17.3.1:
1610316115
y18n "^5.0.5"
1610416116
yargs-parser "^21.1.1"
1610516117

16106-
yjs@*, yjs@13.6.23, yjs@^13.6.15:
16118+
yjs@*, yjs@^13.6.15:
1610716119
version "13.6.23"
1610816120
resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.6.23.tgz#62358dfa52e92dc870b8a0bedcf0d4cbd4c5ffa8"
1610916121
integrity sha512-ExtnT5WIOVpkL56bhLeisG/N5c4fmzKn4k0ROVfJa5TY2QHbH7F0Wu2T5ZhR7ErsFWQEFafyrnSI8TPKVF9Few==

0 commit comments

Comments
 (0)