diff --git a/packages/client/package.json b/packages/client/package.json index 0c528a632b9..9a889d98318 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -68,7 +68,7 @@ "@emotion/styled": "^10.0.27", "@mattkrick/graphql-trebuchet-client": "^2.2.1", "@mattkrick/sanitize-svg": "0.4.0", - "@mattkrick/trebuchet-client": "^3.0.2", + "@mattkrick/trebuchet-client": "3.0.1", "@mui/icons-material": "^5.8.4", "@mui/material": "^5.9.2", "@mui/x-date-pickers": "^6.3.1", diff --git a/packages/server/socketHandlers/handleOpen.ts b/packages/server/socketHandlers/handleOpen.ts index b002ac03168..f2920fe0ff0 100644 --- a/packages/server/socketHandlers/handleOpen.ts +++ b/packages/server/socketHandlers/handleOpen.ts @@ -1,9 +1,13 @@ import {WebSocketBehavior} from 'uWebSockets.js' +import {TrebuchetCloseReason} from '../../client/types/constEnums' import activeClients from '../activeClients' import AuthToken from '../database/types/AuthToken' import ConnectionContext from '../socketHelpers/ConnectionContext' import keepAlive from '../socketHelpers/keepAlive' import {sendEncodedMessage} from '../socketHelpers/sendEncodedMessage' +import {isAuthenticated} from '../utils/authorization' +import checkBlacklistJWT from '../utils/checkBlacklistJWT' +import sendToSentry from '../utils/sendToSentry' import handleConnect from './handleConnect' const APP_VERSION = process.env.npm_package_version @@ -11,11 +15,32 @@ export type SocketUserData = { connectionContext: ConnectionContext authToken: AuthToken ip: string + protocol: string done?: true } const handleOpen: WebSocketBehavior['open'] = async (socket) => { - const {authToken, ip} = socket.getUserData() + const {authToken, ip, protocol} = socket.getUserData() + if (protocol !== 'trebuchet-ws') { + sendToSentry(new Error(`WebSocket error: invalid protocol: ${protocol}`)) + // WS Error 1002 is roughly HTTP 412 Precondition Failed because we can't support the req header + socket.end(412, 'Invalid protocol') + return + } + + if (!isAuthenticated(authToken)) { + socket.end(401, TrebuchetCloseReason.EXPIRED_SESSION) + return + } + + // ALL async calls must come after the message listener, or we'll skip out on messages (e.g. resub after server restart) + const {sub: userId, iat} = authToken + const isBlacklistedJWT = await checkBlacklistJWT(userId, iat) + if (isBlacklistedJWT) { + socket.end(401, TrebuchetCloseReason.EXPIRED_SESSION) + return + } + const connectionContext = (socket.getUserData().connectionContext = new ConnectionContext( socket, authToken, diff --git a/packages/server/socketHandlers/handleUpgrade.ts b/packages/server/socketHandlers/handleUpgrade.ts index 07874d2783d..9120b5459fe 100644 --- a/packages/server/socketHandlers/handleUpgrade.ts +++ b/packages/server/socketHandlers/handleUpgrade.ts @@ -1,38 +1,14 @@ import {WebSocketBehavior} from 'uWebSockets.js' -import {TrebuchetCloseReason} from '../../client/types/constEnums' -import safetyPatchRes from '../safetyPatchRes' -import {isAuthenticated} from '../utils/authorization' -import checkBlacklistJWT from '../utils/checkBlacklistJWT' import getQueryToken from '../utils/getQueryToken' -import sendToSentry from '../utils/sendToSentry' import uwsGetIP from '../utils/uwsGetIP' -const handleUpgrade: WebSocketBehavior['upgrade'] = async (res, req, context) => { - safetyPatchRes(res) - const protocol = req.getHeader('sec-websocket-protocol') - if (protocol !== 'trebuchet-ws') { - sendToSentry(new Error(`WebSocket error: invalid protocol: ${protocol}`)) - // WS Error 1002 is roughly HTTP 412 Precondition Failed because we can't support the req header - res.writeStatus('412').end() - return - } - const authToken = getQueryToken(req) - if (!isAuthenticated(authToken)) { - res.writeStatus('401').end() - return - } - +const handleUpgrade: WebSocketBehavior['upgrade'] = (res, req, context) => { const key = req.getHeader('sec-websocket-key') + const protocol = req.getHeader('sec-websocket-protocol') const extensions = req.getHeader('sec-websocket-extensions') const ip = uwsGetIP(res, req) - const {sub: userId, iat} = authToken - // ALL async calls must come after the message listener, or we'll skip out on messages (e.g. resub after server restart) - const isBlacklistedJWT = await checkBlacklistJWT(userId, iat) - if (isBlacklistedJWT) { - res.writeStatus('401').end(TrebuchetCloseReason.EXPIRED_SESSION) - return - } - res.upgrade({ip, authToken}, key, protocol, extensions, context) + const authToken = getQueryToken(req) + res.upgrade({ip, authToken, protocol}, key, protocol, extensions, context) } export default handleUpgrade diff --git a/yarn.lock b/yarn.lock index d5eaa0e63b8..bb7170ab1de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4980,10 +4980,10 @@ resolved "https://registry.yarnpkg.com/@mattkrick/sanitize-svg/-/sanitize-svg-0.4.0.tgz#388c29614cf72aa0dd9803c77c9c9d070bd3cd2d" integrity sha512-TnPI97WVAxo8SQcPy8aV3OF9/2WjXB5/+pRNVudIWR7Bhi5ZjtR/ur162So08GkvsvB914AXCW2sxFh1x6KhHA== -"@mattkrick/trebuchet-client@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@mattkrick/trebuchet-client/-/trebuchet-client-3.0.2.tgz#336d687256fb9ac7bb407f656bf2f69f35f817e1" - integrity sha512-JLOx8gd+cGkDeHhdnns0oor2UNv5uRZ8A/Jfw3NhQcVqQe6R+x9xIHW29M2T78TDHUWqItTgntX+cdDLqVjVvg== +"@mattkrick/trebuchet-client@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@mattkrick/trebuchet-client/-/trebuchet-client-3.0.1.tgz#3fc49f7858652a55dca92cb0c14c0313df931619" + integrity sha512-5uHCldCqmVntoyujTzRfmGtjld8k4JuBdFN0SvhvRFf83FPaNeoit6Mh8VgrcxxxKujKeYCQDMQH6LWwpsshgQ== dependencies: "@mattkrick/fast-rtc-peer" "^0.4.1" eventemitter3 "^4.0.7" @@ -20311,7 +20311,7 @@ string-similarity@^3.0.0: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-3.0.0.tgz#07b0bc69fae200ad88ceef4983878d03793847c7" integrity sha512-7kS7LyTp56OqOI2BDWQNVnLX/rCxIQn+/5M0op1WV6P8Xx6TZNdajpuqQdiJ7Xx+p1C5CsWMvdiBp9ApMhxzEQ== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -20329,15 +20329,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.0.tgz#5ab00980cfb29f43e736b113a120a73a0fb569d3" @@ -20414,7 +20405,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -20428,13 +20419,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -22287,7 +22271,7 @@ workbox-window@6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -22305,15 +22289,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"