chore: Use httpOnly authentication cookie (#5552)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user