UNPKG

@nodecg/types

Version:

Dynamic broadcast graphics rendered in a browser

121 lines 6.17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createSocketAuthMiddleware = void 0; const serialize_error_1 = require("serialize-error"); const socket_protocol_1 = require("../../types/socket-protocol"); const config_1 = require("../config"); const logger_1 = require("../logger"); const UnauthorizedError_1 = require("../login/UnauthorizedError"); const log = (0, logger_1.createLogger)("socket-auth"); const socketsByKey = new Map(); const createSocketAuthMiddleware = (db) => { return async function socketAuthMiddleware(socket, next) { try { const { token } = socket.handshake.query; if (!token) { next(new UnauthorizedError_1.UnauthorizedError(socket_protocol_1.UnAuthErrCode.InvalidToken, "no token provided")); return; } if (Array.isArray(token)) { next(new UnauthorizedError_1.UnauthorizedError(socket_protocol_1.UnAuthErrCode.InvalidToken, "more than one token provided")); return; } const apiKey = await db.findApiKey(token); if (!apiKey) { next(new UnauthorizedError_1.UnauthorizedError(socket_protocol_1.UnAuthErrCode.CredentialsRequired, "no credentials found")); return; } const user = await db.findUser(apiKey.user.id); if (!user) { next(new UnauthorizedError_1.UnauthorizedError(socket_protocol_1.UnAuthErrCode.CredentialsRequired, "no user associated with provided credentials")); return; } // But only authed sockets can join the Authed room. const provider = user.identities[0].provider_type; const providerAllowed = config_1.config.login.enabled && config_1.config.login?.[provider]?.enabled; const allowed = db.isSuperUser(user) && providerAllowed; if (allowed) { if (!socketsByKey.has(token)) { socketsByKey.set(token, new Set()); } const socketSet = socketsByKey.get(token); /* istanbul ignore next: should be impossible */ if (!socketSet) { throw new Error("socketSet was somehow falsey"); } socketSet.add(socket); socket.on("regenerateToken", async (cb) => { try { // Lookup the ApiKey for this token we want to revoke. const keyToDelete = await db.findApiKey(token); // If there's a User associated to this key (there should be) // give them a new ApiKey if (keyToDelete) { // Make the new api key const newApiKey = await db.createApiKey(); // Remove the old key from the user, replace it with the new const user = await db.findUser(keyToDelete.user.id); if (!user) { throw new Error("should have been a user here"); } user.apiKeys = user.apiKeys.filter((ak) => ak.secret_key !== token); user.apiKeys.push(newApiKey); await db.saveUser(user); // Delete the old key entirely await db.deleteSecretKey(token); if (cb) { cb(undefined, undefined); } } else { // Something is weird if we're here, just close the socket. if (cb) { cb(undefined, undefined); } socket.disconnect(true); } // Close all sockets that are using the invalidated key, // EXCEPT the one that requested the revocation. // If we close the one that requested the revocation, // there will be a race condition where it might get redirected // to an error page before it receives the new key. for (const s of socketSet) { if (s === socket) { continue; } s.emit("protocol_error", new UnauthorizedError_1.UnauthorizedError(socket_protocol_1.UnAuthErrCode.TokenRevoked, "This token has been invalidated").serialized); // We need to wait a bit before disconnecting the socket, // because we need to give them time to receive the "error" // message we just sent. setTimeout(() => { s.disconnect(true); }, 500); } socketsByKey.delete(token); } catch (error) { log.error((0, serialize_error_1.serializeError)(error)); if (cb) { cb(error, undefined); } } }); // Don't leak memory by retaining references to all sockets indefinitely socket.on("disconnect", () => { socketSet.delete(socket); }); } if (allowed) { next(undefined); } else { next(new UnauthorizedError_1.UnauthorizedError(socket_protocol_1.UnAuthErrCode.InvalidToken, "user is not allowed")); } } catch (error) { next(error); } }; }; exports.createSocketAuthMiddleware = createSocketAuthMiddleware; //# sourceMappingURL=socketAuthMiddleware.js.map