From 5db9139b5c62f0e5034f1b908c47e413d722c0d3 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Sun, 13 Mar 2022 14:08:32 -0700 Subject: [PATCH] Remove use of sessionParser and build cookies directly Signed-off-by: James Ketrenos --- client/src/App.js | 34 ++++++++++++++++++ server/app.js | 67 ++---------------------------------- server/package.json | 2 +- server/routes/games.js | 78 +++++++++++++----------------------------- 4 files changed, 62 insertions(+), 119 deletions(-) diff --git a/client/src/App.js b/client/src/App.js index 76ab63b..450a19e 100755 --- a/client/src/App.js +++ b/client/src/App.js @@ -225,6 +225,7 @@ const Table = () => { }); }, [ gameId, setGameId ]); + /* Once a game id is known, create the sole WebSocket connection * to the backend. This WebSocket is then shared with any component * that performs game state updates. Those components should @@ -319,6 +320,39 @@ const Table = () => { }; const App = () => { + const [playerId, setPlayerId] = useState(undefined); + const [error, setError] = useState(undefined); + + useEffect(() => { + if (playerId) { + return; + } + window.fetch(`${base}/api/v1/games/`, { + method: 'GET', + cache: 'no-cache', + credentials: 'same-origin', /* include cookies */ + headers: { + 'Content-Type': 'application/json' + }, + }).then((res) => { + if (res.status >= 400) { + const error = `Unable to connect to Ketr Ketran game server! ` + + `Try refreshing your browser in a few seconds.`; + console.error(error); + setError(error); + } + console.log(res.headers); + return res.json(); + }).then((data) => { + setPlayerId(data.player); + }).catch((error) => { + }); + }, [playerId, setPlayerId]); + + if (!playerId) { + return <>{ error }; + } + return ( diff --git a/server/app.js b/server/app.js index 531cd10..1af9763 100755 --- a/server/app.js +++ b/server/app.js @@ -8,22 +8,14 @@ const express = require("express"), bodyParser = require("body-parser"), config = require("config"), session = require('express-session'), - hb = require("handlebars"), - SQLiteStore = require('connect-sqlite3')(session), basePath = require("./basepath"), + cookieParser = require("cookie-parser"), app = express(), fs = require('fs'); -let server; +const server = require("http").createServer(app); -if (0) { - const key = fs.readFileSync("ssl/server-key.pem", "utf8"), - cert = fs.readFileSync("ssl/server-cert.pem", "utf8"), - credentials = { key, cert }; - server = require("https").createServer(credentials, app); -} else { - server = require("http").createServer(app); -} +app.use(cookieParser()); const ws = require('express-ws')(app, server); @@ -38,7 +30,6 @@ let userDB, gameDB; app.use(bodyParser.json()); - /* App is behind an nginx proxy which we trust, so use the remote address * set in the headers */ app.set("trust proxy", true); @@ -49,39 +40,6 @@ app.use(basePath, require("./routes/basepath.js")); /* Handle static files first so excessive logging doesn't occur */ app.use(basePath, express.static(frontendPath, { index: false })); -/* -app.use(bodyParser.urlencoded({ - extended: false -})); -*/ - -/* body-parser does not support text/*, so add support for that here */ -if (0) app.use(function(req, res, next){ - if (!req.is('text/*')) { - return next(); - } - req.setEncoding('utf8'); - let text = ''; - req.on('data', function(chunk) { - text += chunk; - }); - req.on('end', function() { - req.text = text; - next(); - }); -}); - -const sessionParser = session({ - store: new SQLiteStore({ db: config.get("sessions.db") }), - secret: config.get("sessions.store-secret"), - cookie: { maxAge: 21 * 24 * 60 * 60 * 1000 }, // 3 weeks - saveUninitialized: false, - resave: true -}); - -app.use(sessionParser); -app.locals.sessionParser = sessionParser; - const index = require("./routes/index"); if (config.has("admin")) { @@ -95,7 +53,6 @@ app.use(basePath, index); /* /games loads the default index */ app.use(basePath + "games", index); - /* Allow access to the 'users' API w/out being logged in */ /* const users = require("./routes/users"); @@ -110,24 +67,6 @@ app.use(function(err, req, res, next) { }); }); -if (0) { -/* Check authentication */ -app.use(basePath, function(req, res, next) { - return users.getSessionUser(req).then(function(user) { - if (user.restriction) { - return res.status(401).send(user.restriction); - } - req.user = user; - return next(); - }).catch(function(error) { - return res.status(403).send(error); - }); -}); -} - -/* Everything below here requires a successful authentication */ -app.use(basePath, express.static(frontendPath, { index: false })); - app.use(`${basePath}api/v1/games`, require("./routes/games")); /* Declare the "catch all" index route last; the final route is a 404 dynamic router */ diff --git a/server/package.json b/server/package.json index b013be7..da4cfba 100644 --- a/server/package.json +++ b/server/package.json @@ -14,7 +14,7 @@ "body-parser": "^1.19.2", "config": "^3.1.0", "connect-sqlite3": "^0.9.11", - "cookie-parser": "^1.4.4", + "cookie-parser": "^1.4.6", "core-js": "^3.2.1", "express": "^4.17.1", "express-session": "^1.17.1", diff --git a/server/routes/games.js b/server/routes/games.js index 4647e34..b4ef755 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -437,17 +437,11 @@ const newPlayer = (color) => { }; } -const getSession = (game, reqSession) => { +const getSession = (game, id) => { if (!game.sessions) { game.sessions = {}; } - if (!reqSession.player_id) { - throw Error(`No session id for ${game.id}`); - } - - const id = reqSession.player_id; - /* If this session is not yet in the game, add it and set the player's name */ if (!(id in game.sessions)) { game.sessions[id] = { @@ -2803,7 +2797,7 @@ const ping = (session) => { } const wsInactive = (game, req) => { - const session = getSession(game, req.session); + const session = getSession(game, req.cookies.player); if (session && session.ws) { console.log(`Closing WebSocket to ${session.name} due to inactivity.`); @@ -3086,19 +3080,6 @@ const sendWarning = (session, warning) => { session.ws.send(JSON.stringify({ type: 'warning', warning })); } -router.ws("/ws/:id", (ws, req) => { - /* Connect the WebSocket to the app's sessionParser */ - req.app.locals.sessionParser(req, {}, () => { - if (!req.session.player_id) { - req.session.player_id = crypto.randomBytes(16).toString('hex'); - console.log(`[${req.session.player_id.substring(0, 8)}]: wss - New session connected`); - } else { - console.log(`[${req.session.player_id.substring(0, 8)}]: wss - Existing session being used`); - } - wsConnect(ws, req); - }); -}); - const getFilteredPlayers = (game) => { const filtered = {}; for (let color in game.players) { @@ -3117,13 +3098,16 @@ const getFilteredPlayers = (game) => { return filtered; }; -const wsConnect = async (ws, req) => { +router.ws("/ws/:id", async (ws, req) => { + if (!req.cookies || !req.cookies.player) { + ws.send({ type: 'error', error: `Unable to set session cookie` }); + return; + } + const { id } = req.params; const gameId = id; - if (!req.session.player_id) { - throw new Error(`player_id not set from http load`); - } - const short = `[${req.session.player_id.substring(0, 8)}]`; + + const short = `[${req.cookies.player.substring(0, 8)}]`; ws.id = short; console.log(`${short}: Game ${gameId} - New connection from client.`); @@ -3142,7 +3126,7 @@ const wsConnect = async (ws, req) => { if (!game) { return; } - const session = getSession(game, req.session); + const session = getSession(game, req.cookies.player); session.live = false; if (session.ws) { session.ws.close(); @@ -3157,7 +3141,7 @@ const wsConnect = async (ws, req) => { if (!game) { return; } - const session = getSession(game, req.session); + const session = getSession(game, req.cookies.player); if (session.player) { session.player.live = false; } @@ -3186,7 +3170,7 @@ const wsConnect = async (ws, req) => { return; } const game = await loadGame(gameId); - const session = getSession(game, req.session); + const session = getSession(game, req.cookies.player); if (!session.ws) { session.ws = ws; } @@ -3504,7 +3488,7 @@ const wsConnect = async (ws, req) => { return; } - const session = getSession(game, req.session); + const session = getSession(game, req.cookies.player); session.ws = ws; if (session.player) { session.player.live = true; @@ -3534,7 +3518,7 @@ const wsConnect = async (ws, req) => { clearTimeout(session.keepAlive); } session.keepAlive = setTimeout(() => { ping(session); }, 2500); -}; +}); const debugChat = (game, preamble) => { preamble = `Degug ${preamble.trim()}`; @@ -3825,7 +3809,7 @@ router.post("/", (req, res/*, next*/) => { } else { console.log(`[${req.session.player_id.substring(0, 8)}]: https - Existing session being used`); } - const session = getSession(game, req.session); + const session = getSession(game, req.cookies.player); saveGame(game); return res.status(200).send(getFilteredGameForPlayer(game, session)); }); @@ -3911,28 +3895,14 @@ const shuffleBoard = (game) => { game.signature = gameSignature(game); } -/* - return gameDB.sequelize.query("SELECT " + - "photos.*,albums.path AS path,photohashes.hash,modified,(albums.path || photos.filename) AS filepath FROM photos " + - "LEFT JOIN albums ON albums.id=photos.albumId " + - "LEFT JOIN photohashes ON photohashes.photoId=photos.id " + - "WHERE photos.id=:id", { - replacements: { - id: id - }, type: gameDB.Sequelize.QueryTypes.SELECT, - raw: true - }).then(function(photos) { - if (photos.length == 0) { - return null; - } - */ -if (0) { -router.get("/*", (req, res/*, next*/) => { - return gameDB.sequelize.query(query, { - replacements: replacements, type: gameDB.Sequelize.QueryTypes.SELECT - }).then((photos) => { - }); +/* Simple NO-OP to set session cookie so player-id can use it as the + * index */ +router.get("/", (req, res/*, next*/) => { + if (!req.cookies.player) { + res.cookie('player', crypto.randomBytes(16).toString('hex')); + } + console.log(`[${req.cookies.player.substring(0, 8)}]: Browser hand-shake has started.`) + return res.status(200).send({ player: req.cookies.player }); }); -} module.exports = router;