(core) Fix unhandledRejection caused by exception from verifyClient.

Summary:
This includes two fixes: one to ensure that any exception from websocket
upgrade handlers are handled (by destroying the socket). A test case is
added for this.

The other is to ensure verifyClient returns false instead of failing; this
should lead to a better error to the client (Forbidden, rather than just socket
close). This is only tested manually with a curl request.

Test Plan: Added a test case for the more sensitive half of the fix.

Reviewers: georgegevoian

Reviewed By: georgegevoian

Subscribers: georgegevoian

Differential Revision: https://phab.getgrist.com/D4323
This commit is contained in:
Dmitry S
2024-08-15 13:50:32 -04:00
parent 3e70a77729
commit ef4180c8da
3 changed files with 58 additions and 16 deletions

View File

@@ -1,7 +1,7 @@
import { assert } from 'chai';
import * as http from 'http';
import { GristClientSocket } from 'app/client/components/GristClientSocket';
import { GristSocketServer } from 'app/server/lib/GristSocketServer';
import { GristSocketServer, GristSocketServerOptions } from 'app/server/lib/GristSocketServer';
import { fromCallback, listenPromise } from 'app/server/lib/serverUtils';
import { AddressInfo } from 'net';
import httpProxy from 'http-proxy';
@@ -29,9 +29,9 @@ describe(`GristSockets`, function () {
await stopSocketServer();
});
async function startSocketServer() {
async function startSocketServer(options?: GristSocketServerOptions) {
server = http.createServer((req, res) => res.writeHead(404).end());
socketServer = new GristSocketServer(server);
socketServer = new GristSocketServer(server, options);
await listenPromise(server.listen(0, 'localhost'));
serverPort = (server.address() as AddressInfo).port;
}
@@ -165,6 +165,30 @@ describe(`GristSockets`, function () {
await closePromise;
});
it("should fail gracefully if verifyClient throws exception", async function () {
// Restart servers with a failing verifyClient method.
await stopProxyServer();
await stopSocketServer();
await startSocketServer({verifyClient: () => { throw new Error("Test error from verifyClient"); }});
await startProxyServer();
// Check whether we are getting an unhandledRejection.
let rejection: unknown = null;
const onUnhandledRejection = (err: unknown) => { rejection = err; };
process.on('unhandledRejection', onUnhandledRejection);
try {
// The "poll error" comes from the fallback to polling.
await assert.isRejected(connectClient(wsAddress), /poll error/);
} finally {
// Typings for process.removeListener are broken, possibly by electron's presence
// (https://github.com/electron/electron/issues/9626).
process.removeListener('unhandledRejection' as any, onUnhandledRejection);
}
// The important thing is that we don't get unhandledRejection.
assert.equal(rejection, null);
});
});
}
});
});