chore: Use httpOnly authentication cookie (#5552)

This commit is contained in:
Tom Moor
2023-07-15 16:56:32 -04:00
committed by GitHub
parent b1230d0c81
commit 39e12cef65
16 changed files with 114 additions and 120 deletions

View File

@@ -1,6 +1,6 @@
import http, { IncomingMessage } from "http";
import { Duplex } from "stream";
import invariant from "invariant";
import cookie from "cookie";
import Koa from "koa";
import IO from "socket.io";
import { createAdapter } from "socket.io-redis";
@@ -56,8 +56,7 @@ export default function init(
server.on(
"upgrade",
function (req: IncomingMessage, socket: Duplex, head: Buffer) {
if (req.url?.startsWith(path)) {
invariant(ioHandleUpgrade, "Existing upgrade handler must exist");
if (req.url?.startsWith(path) && ioHandleUpgrade) {
ioHandleUpgrade(req, socket, head);
return;
}
@@ -92,29 +91,13 @@ export default function init(
}
});
io.on("connection", (socket: SocketWithAuth) => {
io.on("connection", async (socket: SocketWithAuth) => {
Metrics.increment("websockets.connected");
Metrics.gaugePerInstance("websockets.count", io.engine.clientsCount);
socket.on("authentication", async function (data) {
try {
await authenticate(socket, data);
Logger.debug("websockets", `Authenticated socket ${socket.id}`);
socket.emit("authenticated", true);
void authenticated(io, socket);
} catch (err) {
Logger.error(`Authentication error socket ${socket.id}`, err);
socket.emit("unauthorized", { message: err.message }, function () {
socket.disconnect();
});
}
});
socket.on("disconnect", async () => {
Metrics.increment("websockets.disconnected");
Metrics.gaugePerInstance("websockets.count", io.engine.clientsCount);
await Redis.defaultClient.hdel(socket.id, "userId");
});
setTimeout(function () {
@@ -126,6 +109,19 @@ export default function init(
socket.disconnect("unauthorized");
}
}, 1000);
try {
await authenticate(socket);
Logger.debug("websockets", `Authenticated socket ${socket.id}`);
socket.emit("authenticated", true);
void authenticated(io, socket);
} catch (err) {
Logger.error(`Authentication error socket ${socket.id}`, err);
socket.emit("unauthorized", { message: err.message }, function () {
socket.disconnect();
});
}
});
// Handle events from event queue that should be sent to the clients down ws
@@ -204,15 +200,17 @@ async function authenticated(io: IO.Server, socket: SocketWithAuth) {
* Authenticate the socket with the given token, attach the user model for the
* duration of the session.
*/
async function authenticate(socket: SocketWithAuth, data: { token: string }) {
const { token } = data;
async function authenticate(socket: SocketWithAuth) {
const cookies = socket.request.headers.cookie
? cookie.parse(socket.request.headers.cookie)
: {};
const { accessToken } = cookies;
const user = await getUserForJWT(token);
if (!accessToken) {
throw new Error("No access token");
}
const user = await getUserForJWT(accessToken);
socket.client.user = user;
// store the mapping between socket id and user id in redis so that it is
// accessible across multiple websocket servers. Lasts 24 hours, if they have
// a websocket connection that lasts this long then well done.
await Redis.defaultClient.hset(socket.id, "userId", user.id);
await Redis.defaultClient.expire(socket.id, 3600 * 24);
return user;
}