✨(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.
This commit is contained in:
@@ -2,6 +2,7 @@ import {
|
||||
HocuspocusProvider,
|
||||
HocuspocusProviderWebsocket,
|
||||
} from '@hocuspocus/provider';
|
||||
import { v1 as uuidv1, v4 as uuidv4 } from 'uuid';
|
||||
import WebSocket from 'ws';
|
||||
|
||||
const port = 5559;
|
||||
@@ -52,9 +53,9 @@ describe('Server Tests', () => {
|
||||
|
||||
test('WebSocket connection with bad origin should be closed', () => {
|
||||
const { promise, done } = promiseDone();
|
||||
|
||||
const room = uuidv4();
|
||||
const ws = new WebSocket(
|
||||
`ws://localhost:${port}/collaboration/ws/?room=test-room`,
|
||||
`ws://localhost:${port}/collaboration/ws/?room=${room}`,
|
||||
{
|
||||
headers: {
|
||||
Origin: 'http://bad-origin.com',
|
||||
@@ -72,9 +73,9 @@ describe('Server Tests', () => {
|
||||
|
||||
test('WebSocket connection without cookies header should be closed', () => {
|
||||
const { promise, done } = promiseDone();
|
||||
|
||||
const room = uuidv4();
|
||||
const ws = new WebSocket(
|
||||
`ws://localhost:${port}/collaboration/ws/?room=test-room`,
|
||||
`ws://localhost:${port}/collaboration/ws/?room=${room}`,
|
||||
{
|
||||
headers: {
|
||||
Origin: origin,
|
||||
@@ -92,9 +93,46 @@ describe('Server Tests', () => {
|
||||
|
||||
test('WebSocket connection not allowed if room not matching provider name', () => {
|
||||
const { promise, done } = promiseDone();
|
||||
|
||||
const room = uuidv4();
|
||||
const wsHocus = new HocuspocusProviderWebsocket({
|
||||
url: `ws://localhost:${portWS}/?room=my-test`,
|
||||
url: `ws://localhost:${portWS}/?room=${room}`,
|
||||
WebSocketPolyfill: WebSocket,
|
||||
maxAttempts: 1,
|
||||
quiet: true,
|
||||
});
|
||||
|
||||
const providerName = uuidv4();
|
||||
const provider = new HocuspocusProvider({
|
||||
websocketProvider: wsHocus,
|
||||
name: providerName,
|
||||
broadcast: false,
|
||||
quiet: true,
|
||||
preserveConnection: false,
|
||||
onClose: (data) => {
|
||||
expect(console.error).toHaveBeenCalledWith(
|
||||
'Invalid room name - Probable hacking attempt:',
|
||||
providerName,
|
||||
room,
|
||||
);
|
||||
|
||||
wsHocus.stopConnectionAttempt();
|
||||
expect(data.event.reason).toBe('Forbidden');
|
||||
wsHocus.webSocket?.close();
|
||||
wsHocus.disconnect();
|
||||
provider.destroy();
|
||||
wsHocus.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
test('WebSocket connection not allowed if room is not a valid uuid v4', () => {
|
||||
const { promise, done } = promiseDone();
|
||||
const room = uuidv1();
|
||||
const wsHocus = new HocuspocusProviderWebsocket({
|
||||
url: `ws://localhost:${portWS}/?room=${room}`,
|
||||
WebSocketPolyfill: WebSocket,
|
||||
maxAttempts: 1,
|
||||
quiet: true,
|
||||
@@ -102,15 +140,49 @@ describe('Server Tests', () => {
|
||||
|
||||
const provider = new HocuspocusProvider({
|
||||
websocketProvider: wsHocus,
|
||||
name: 'hocuspocus-test',
|
||||
name: room,
|
||||
broadcast: false,
|
||||
quiet: true,
|
||||
preserveConnection: false,
|
||||
onClose: (data) => {
|
||||
expect(console.error).toHaveBeenCalledWith(
|
||||
'Invalid room name - Probable hacking attempt:',
|
||||
'hocuspocus-test',
|
||||
'my-test',
|
||||
'Room name is not a valid uuid:',
|
||||
room,
|
||||
);
|
||||
|
||||
wsHocus.stopConnectionAttempt();
|
||||
expect(data.event.reason).toBe('Forbidden');
|
||||
wsHocus.webSocket?.close();
|
||||
wsHocus.disconnect();
|
||||
provider.destroy();
|
||||
wsHocus.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
test('WebSocket connection not allowed if room is not a valid uuid', () => {
|
||||
const { promise, done } = promiseDone();
|
||||
const room = 'not-a-valid-uuid';
|
||||
const wsHocus = new HocuspocusProviderWebsocket({
|
||||
url: `ws://localhost:${portWS}/?room=${room}`,
|
||||
WebSocketPolyfill: WebSocket,
|
||||
maxAttempts: 1,
|
||||
quiet: true,
|
||||
});
|
||||
|
||||
const provider = new HocuspocusProvider({
|
||||
websocketProvider: wsHocus,
|
||||
name: room,
|
||||
broadcast: false,
|
||||
quiet: true,
|
||||
preserveConnection: false,
|
||||
onClose: (data) => {
|
||||
expect(console.error).toHaveBeenCalledWith(
|
||||
'Room name is not a valid uuid:',
|
||||
room,
|
||||
);
|
||||
|
||||
wsHocus.stopConnectionAttempt();
|
||||
@@ -131,8 +203,9 @@ describe('Server Tests', () => {
|
||||
|
||||
mockDocFetch.mockRejectedValue('');
|
||||
|
||||
const room = uuidv4();
|
||||
const wsHocus = new HocuspocusProviderWebsocket({
|
||||
url: `ws://localhost:${portWS}/?room=my-test`,
|
||||
url: `ws://localhost:${portWS}/?room=${room}`,
|
||||
WebSocketPolyfill: WebSocket,
|
||||
maxAttempts: 1,
|
||||
quiet: true,
|
||||
@@ -140,7 +213,7 @@ describe('Server Tests', () => {
|
||||
|
||||
const provider = new HocuspocusProvider({
|
||||
websocketProvider: wsHocus,
|
||||
name: 'my-test',
|
||||
name: room,
|
||||
broadcast: false,
|
||||
quiet: true,
|
||||
preserveConnection: false,
|
||||
@@ -167,6 +240,7 @@ describe('Server Tests', () => {
|
||||
test('WebSocket connection fails if user do not have correct retrieve ability', () => {
|
||||
const { promise, done } = promiseDone();
|
||||
|
||||
const room = uuidv4();
|
||||
mockDocFetch.mockResolvedValue({
|
||||
abilities: {
|
||||
retrieve: false,
|
||||
@@ -174,7 +248,7 @@ describe('Server Tests', () => {
|
||||
});
|
||||
|
||||
const wsHocus = new HocuspocusProviderWebsocket({
|
||||
url: `ws://localhost:${portWS}/?room=my-test`,
|
||||
url: `ws://localhost:${portWS}/?room=${room}`,
|
||||
WebSocketPolyfill: WebSocket,
|
||||
maxAttempts: 1,
|
||||
quiet: true,
|
||||
@@ -182,14 +256,14 @@ describe('Server Tests', () => {
|
||||
|
||||
const provider = new HocuspocusProvider({
|
||||
websocketProvider: wsHocus,
|
||||
name: 'my-test',
|
||||
name: room,
|
||||
broadcast: false,
|
||||
quiet: true,
|
||||
preserveConnection: false,
|
||||
onClose: (data) => {
|
||||
expect(console.error).toHaveBeenCalledWith(
|
||||
'onConnect: Unauthorized to retrieve this document',
|
||||
'my-test',
|
||||
room,
|
||||
);
|
||||
|
||||
wsHocus.stopConnectionAttempt();
|
||||
@@ -217,19 +291,20 @@ describe('Server Tests', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const room = uuidv4();
|
||||
const wsHocus = new HocuspocusProviderWebsocket({
|
||||
url: `ws://localhost:${portWS}/?room=hocuspocus-test`,
|
||||
url: `ws://localhost:${portWS}/?room=${room}`,
|
||||
WebSocketPolyfill: WebSocket,
|
||||
});
|
||||
|
||||
const provider = new HocuspocusProvider({
|
||||
websocketProvider: wsHocus,
|
||||
name: 'hocuspocus-test',
|
||||
name: room,
|
||||
broadcast: false,
|
||||
quiet: true,
|
||||
onConnect: () => {
|
||||
void hocusPocusServer
|
||||
.openDirectConnection('hocuspocus-test')
|
||||
.openDirectConnection(room)
|
||||
.then((connection) => {
|
||||
connection.document?.getConnections().forEach((connection) => {
|
||||
expect(connection.readOnly).toBe(!canEdit);
|
||||
@@ -262,29 +337,28 @@ describe('Server Tests', () => {
|
||||
id: 'test-user-id',
|
||||
});
|
||||
|
||||
const room = uuidv4();
|
||||
const wsHocus = new HocuspocusProviderWebsocket({
|
||||
url: `ws://localhost:${portWS}/?room=hocuspocus-test`,
|
||||
url: `ws://localhost:${portWS}/?room=${room}`,
|
||||
WebSocketPolyfill: WebSocket,
|
||||
});
|
||||
|
||||
const provider = new HocuspocusProvider({
|
||||
websocketProvider: wsHocus,
|
||||
name: 'hocuspocus-test',
|
||||
name: room,
|
||||
broadcast: false,
|
||||
quiet: true,
|
||||
onConnect: () => {
|
||||
void hocusPocusServer
|
||||
.openDirectConnection('hocuspocus-test')
|
||||
.then((connection) => {
|
||||
connection.document?.getConnections().forEach((connection) => {
|
||||
expect(connection.context.userId).toBe('test-user-id');
|
||||
});
|
||||
|
||||
void connection.disconnect();
|
||||
provider.destroy();
|
||||
wsHocus.destroy();
|
||||
done();
|
||||
void hocusPocusServer.openDirectConnection(room).then((connection) => {
|
||||
connection.document?.getConnections().forEach((connection) => {
|
||||
expect(connection.context.userId).toBe('test-user-id');
|
||||
});
|
||||
|
||||
void connection.disconnect();
|
||||
provider.destroy();
|
||||
wsHocus.destroy();
|
||||
done();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"cors": "2.8.5",
|
||||
"express": "4.21.2",
|
||||
"express-ws": "5.0.2",
|
||||
"uuid": "11.1.0",
|
||||
"y-protocols": "1.0.6",
|
||||
"yjs": "*"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Server } from '@hocuspocus/server';
|
||||
import { validate as uuidValidate, version as uuidVersion } from 'uuid';
|
||||
|
||||
import { fetchDocument } from '@/api/getDoc';
|
||||
import { getMe } from '@/api/getMe';
|
||||
@@ -27,6 +28,12 @@ export const hocusPocusServer = Server.configure({
|
||||
return Promise.reject(new Error('Wrong room name: Unauthorized'));
|
||||
}
|
||||
|
||||
if (!uuidValidate(documentName) || uuidVersion(documentName) !== 4) {
|
||||
console.error('Room name is not a valid uuid:', documentName);
|
||||
|
||||
return Promise.reject(new Error('Wrong room name: Unauthorized'));
|
||||
}
|
||||
|
||||
let can_edit = false;
|
||||
|
||||
try {
|
||||
@@ -35,7 +42,7 @@ export const hocusPocusServer = Server.configure({
|
||||
if (!document.abilities.retrieve) {
|
||||
console.error(
|
||||
'onConnect: Unauthorized to retrieve this document',
|
||||
roomParam,
|
||||
documentName,
|
||||
);
|
||||
return Promise.reject(new Error('Wrong abilities:Unauthorized'));
|
||||
}
|
||||
|
||||
@@ -6334,13 +6334,20 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*", "@types/node@22.10.7", "@types/node@22.13.9", "@types/node@^22.7.5":
|
||||
"@types/node@*", "@types/node@^22.7.5":
|
||||
version "22.13.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.9.tgz#5d9a8f7a975a5bd3ef267352deb96fb13ec02eca"
|
||||
integrity sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==
|
||||
dependencies:
|
||||
undici-types "~6.20.0"
|
||||
|
||||
"@types/node@22.10.7":
|
||||
version "22.10.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.7.tgz#14a1ca33fd0ebdd9d63593ed8d3fbc882a6d28d7"
|
||||
integrity sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==
|
||||
dependencies:
|
||||
undici-types "~6.20.0"
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
|
||||
@@ -6396,7 +6403,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
|
||||
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
|
||||
|
||||
"@types/react-dom@*", "@types/react-dom@19.0.0":
|
||||
"@types/react-dom@*":
|
||||
version "19.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.0.tgz#e7f5d618a080486eaf9952246dbf59eaa2c64130"
|
||||
integrity sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w==
|
||||
@@ -6415,7 +6422,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044"
|
||||
integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==
|
||||
|
||||
"@types/react@*", "@types/react@19.0.0":
|
||||
"@types/react@*":
|
||||
version "19.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.0.tgz#fbbb53ce223f4e2b750ad5dd09580b2c43522bbf"
|
||||
integrity sha512-MY3oPudxvMYyesqs/kW1Bh8y9VqSmf+tzqw3ae8a9DZW68pUe3zAdHeI1jc6iAysuRdACnVknHP8AhwD4/dxtg==
|
||||
@@ -6533,7 +6540,7 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@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":
|
||||
"@typescript-eslint/eslint-plugin@*", "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
|
||||
version "8.26.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz#7e880faf91f89471c30c141951e15f0eb3a0599e"
|
||||
integrity sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==
|
||||
@@ -6548,7 +6555,7 @@
|
||||
natural-compare "^1.4.0"
|
||||
ts-api-utils "^2.0.1"
|
||||
|
||||
"@typescript-eslint/parser@*", "@typescript-eslint/parser@8.26.0", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
|
||||
"@typescript-eslint/parser@*", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
|
||||
version "8.26.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.26.0.tgz#9b4d2198e89f64fb81e83167eedd89a827d843a9"
|
||||
integrity sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==
|
||||
@@ -8793,7 +8800,7 @@ eslint-visitor-keys@^4.2.0:
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45"
|
||||
integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
|
||||
|
||||
eslint@*, eslint@8.57.0:
|
||||
eslint@*:
|
||||
version "8.57.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668"
|
||||
integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==
|
||||
@@ -15086,7 +15093,7 @@ typed-array-length@^1.0.7:
|
||||
possible-typed-array-names "^1.0.0"
|
||||
reflect.getprototypeof "^1.0.6"
|
||||
|
||||
typescript@*, typescript@5.8.2, typescript@^5.0.4:
|
||||
typescript@*, typescript@^5.0.4:
|
||||
version "5.8.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
|
||||
integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==
|
||||
@@ -15387,6 +15394,11 @@ utils-merge@1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
|
||||
|
||||
uuid@11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912"
|
||||
integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==
|
||||
|
||||
uuid@^11.0.3:
|
||||
version "11.0.5"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.5.tgz#07b46bdfa6310c92c3fb3953a8720f170427fc62"
|
||||
@@ -16103,7 +16115,7 @@ yargs@17.7.2, yargs@^17.3.1:
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^21.1.1"
|
||||
|
||||
yjs@*, yjs@13.6.23, yjs@^13.6.15:
|
||||
yjs@*, yjs@^13.6.15:
|
||||
version "13.6.23"
|
||||
resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.6.23.tgz#62358dfa52e92dc870b8a0bedcf0d4cbd4c5ffa8"
|
||||
integrity sha512-ExtnT5WIOVpkL56bhLeisG/N5c4fmzKn4k0ROVfJa5TY2QHbH7F0Wu2T5ZhR7ErsFWQEFafyrnSI8TPKVF9Few==
|
||||
|
||||
Reference in New Issue
Block a user