From 2eac3699c65360c884063fe3f9af6b2861c3f1f2 Mon Sep 17 00:00:00 2001 From: Tariq Soliman Date: Mon, 9 Jan 2023 10:29:03 -0800 Subject: [PATCH] #294 SameSite None env and login improvements (#302) * #294 SameSite First pass * #294 Fix session and use postgres as session store * Use a default session db port * #294 Regenerate session on login failure too --- API/Backend/Users/routes/users.js | 198 +++++++++++---------- docs/pages/Setup/ENVs/ENVs.md | 4 + package-lock.json | 250 ++++++++++++++++----------- package.json | 4 +- public/index.html | 2 + public/login.js | 11 +- run/init-db.js | 27 +++ run/server.js | 24 ++- sample.env | 5 + src/essence/Ancillary/Login/Login.js | 17 +- 10 files changed, 335 insertions(+), 207 deletions(-) diff --git a/API/Backend/Users/routes/users.js b/API/Backend/Users/routes/users.js index 6f7451f2..7eed5fe9 100644 --- a/API/Backend/Users/routes/users.js +++ b/API/Backend/Users/routes/users.js @@ -153,104 +153,118 @@ router.post("/signup", function (req, res, next) { * User login */ router.post("/login", function (req, res) { - let MMGISUser = req.cookies.MMGISUser - ? JSON.parse(req.cookies.MMGISUser) - : false; - let username = req.body.username || (MMGISUser ? MMGISUser.username : null); + clearLoginSession(req); - if (username == null) { - res.send({ status: "failure", message: "No username provided." }); - return; - } + req.session.regenerate((err) => { + let MMGISUser = req.cookies.MMGISUser + ? JSON.parse(req.cookies.MMGISUser) + : false; + let username = req.body.username || (MMGISUser ? MMGISUser.username : null); - User.findOne({ - where: { - username: username, - }, - attributes: ["id", "username", "email", "password", "permission"], - }) - .then((user) => { - if (!user) { - res.send({ - status: "failure", - message: "Invalid username or password.", - }); - } else { - function pass(err, result, again) { - if (result) { - // Save the user's info in the session - req.session.user = user.username; - req.session.uid = user.id; - req.session.token = crypto.randomBytes(128).toString("hex"); - req.session.permission = user.permission; + if (username == null) { + res.send({ status: "failure", message: "No username provided." }); + return; + } - User.update( - { - token: req.session.token, - }, - { - where: { - id: user.id, - username: user.username, - }, - } - ) - .then(() => { - res.send({ - status: "success", - username: user.username, + User.findOne({ + where: { + username: username, + }, + attributes: ["id", "username", "email", "password", "permission"], + }) + .then((user) => { + if (!user) { + res.send({ + status: "failure", + message: "Invalid username or password.", + }); + } else { + function pass(err, result, again) { + if (result) { + // Save the user's info in the session + req.session.user = user.username; + req.session.uid = user.id; + req.session.token = crypto.randomBytes(128).toString("hex"); + req.session.permission = user.permission; + + User.update( + { token: req.session.token, - groups: getUserGroups(user.username, req.leadGroupName), + }, + { + where: { + id: user.id, + username: user.username, + }, + } + ) + .then(() => { + req.session.save(() => { + res.send({ + status: "success", + username: user.username, + token: req.session.token, + groups: getUserGroups(user.username, req.leadGroupName), + additional: + process.env.THIRD_PARTY_COOKIES === "true" + ? `; SameSite=None;${ + process.env.NODE_ENV === "production" + ? " Secure" + : "" + }` + : "", + }); + }); + return null; + }) + .catch((err) => { + res.send({ status: "failure", message: "Login failed." }); + return null; }); + return null; + } else { + res.send({ + status: "failure", + message: "Invalid username or password.", + }); + return null; + } + } + + if (req.body.useToken && MMGISUser) { + if (MMGISUser.token == null) { + res.send({ status: "failure", message: "Bad token." }); + return null; + } + User.findOne({ + where: { + username: MMGISUser.username, + token: MMGISUser.token, + }, + }) + .then((user) => { + if (!user) { + res.send({ status: "failure", message: "Bad token." }); + } else { + pass(null, true, true); + } return null; }) .catch((err) => { - res.send({ status: "failure", message: "Login failed." }); - return null; + res.send({ status: "failure", message: "Bad token." }); }); return null; } else { - res.send({ - status: "failure", - message: "Invalid username or password.", - }); - return null; + bcrypt.compare(req.body.password, user.password, pass); } - } - - if (req.body.useToken && MMGISUser) { - if (MMGISUser.token == null) { - res.send({ status: "failure", message: "Bad token." }); - return null; - } - User.findOne({ - where: { - username: MMGISUser.username, - token: MMGISUser.token, - }, - }) - .then((user) => { - if (!user) { - res.send({ status: "failure", message: "Bad token." }); - } else { - pass(null, true, true); - } - return null; - }) - .catch((err) => { - res.send({ status: "failure", message: "Bad token." }); - }); return null; - } else { - bcrypt.compare(req.body.password, user.password, pass); } return null; - } - return null; - }) - .catch((err) => { - res.send({ status: "failure", message: "Bad token." }); - }); + }) + .catch((err) => { + res.send({ status: "failure", message: "Bad token." }); + }); + }); return null; }); @@ -259,10 +273,7 @@ router.post("/logout", function (req, res) { ? JSON.parse(req.cookies.MMGISUser) : false; - req.session.user = "guest"; - req.session.uid = null; - req.session.token = null; - req.session.permission = null; + clearLoginSession(req); if (MMGISUser == false) { res.send({ status: "failure", message: "No user." }); @@ -279,7 +290,11 @@ router.post("/logout", function (req, res) { } ) .then(() => { - res.send({ status: "success" }); + req.session.save(() => { + req.session.regenerate((err) => { + res.send({ status: "success" }); + }); + }); return null; }) .catch((err) => { @@ -299,4 +314,11 @@ function getUserGroups(user, leadGroupName) { return Object.keys(groups); } +function clearLoginSession(req) { + req.session.user = "guest"; + req.session.uid = null; + req.session.token = null; + req.session.permission = null; +} + module.exports = router; diff --git a/docs/pages/Setup/ENVs/ENVs.md b/docs/pages/Setup/ENVs/ENVs.md index bf9b7745..ae68e18c 100644 --- a/docs/pages/Setup/ENVs/ENVs.md +++ b/docs/pages/Setup/ENVs/ENVs.md @@ -80,6 +80,10 @@ Sets the `Content-Security-Policy: frame-ancestors` header to allow the embeddin Sets the `Content-Security-Policy: frame-src` header to allow the embedding iframes from external origins into MMGIS | string[] | default `null` | ex. FRAME_SRC='["http://localhost:8888"]' +#### `THIRD_PARTY_COOKIES=` + +Sets "SameSite=None; Secure" on the login cookie. Useful when using AUTH=local as an iframe within a cross-origin page. | boolean | default `false` + #### `PUBLIC_URL=` Set MMGIS to be deployed under a subpath. Use full and absolute paths only to the project's build directory. For example if serving at the subpath 'mmgis/' is desired, set PUBLIC_URL to 'https://{domain}/mmgis/build'. Changing PUBLIC_URL required a rebuild. | string | default `null` (domain root build '/build') diff --git a/package-lock.json b/package-lock.json index 072c2590..4317eb74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "chart.js": "^3.6.0", "chartjs-plugin-zoom": "^1.2.1", "compression": "^1.7.4", + "connect-pg-simple": "^8.0.0", "cookie-parser": "^1.4.5", "cors": "^2.8.5", "cross-env": "^7.0.2", @@ -49,7 +50,7 @@ "eslint-plugin-react-hooks": "^1.6.1", "express": "^4.16.3", "express-rate-limit": "^5.1.3", - "express-session": "^1.17.1", + "express-session": "^1.17.3", "file-loader": "4.3.0", "file-saver": "^2.0.2", "flat": "^5.0.2", @@ -68,7 +69,6 @@ "jquery": "^3.5.1", "lithosphere": "^1.5.4", "mark.js": "^8.11.1", - "memorystore": "^1.6.2", "mini-css-extract-plugin": "0.9.0", "nipplejs": "^0.8.5", "node-fetch": "^2.6.1", @@ -3941,6 +3941,16 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "node_modules/@types/pg": { + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.5.tgz", + "integrity": "sha512-tOkGtAqRVkHa/PVZicq67zuujI4Oorfglsr2IbKofDwBSysnaqSx7W1mDqFqdkGE6Fbgh+PZAl0r/BWON/mozw==", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", @@ -6613,6 +6623,43 @@ "node": ">=0.8" } }, + "node_modules/connect-pg-simple": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/connect-pg-simple/-/connect-pg-simple-8.0.0.tgz", + "integrity": "sha512-pBDa23RA1LCkwvRrPOh5xevB+Nknh1UDuhFOKsUrkUDodYqfiQT18P2qXc4lk/TqCMB6hI06B8KNncHh91bZMQ==", + "dependencies": { + "@types/pg": "^8.6.5", + "pg": "^8.8.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + } + }, + "node_modules/connect-pg-simple/node_modules/pg": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", + "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.2", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, "node_modules/console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -9355,23 +9402,31 @@ "integrity": "sha512-cjQH+oDrEPXxc569XvxhHC6QXqJiuBT6BhZ70X3bdAImcnHnTNMVuMAJaT0TXPoRiEErUrVPRcOTpZpM36VbOQ==" }, "node_modules/express-session": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.1.tgz", - "integrity": "sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q==", + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", + "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", "dependencies": { - "cookie": "0.4.0", + "cookie": "0.4.2", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~2.0.0", "on-headers": "~1.0.2", "parseurl": "~1.3.3", - "safe-buffer": "5.2.0", + "safe-buffer": "5.2.1", "uid-safe": "~2.1.5" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express-session/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -9391,12 +9446,26 @@ "node_modules/express-session/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express-session/node_modules/safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/express/node_modules/debug": { "version": "2.6.9", @@ -13122,32 +13191,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/memorystore": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/memorystore/-/memorystore-1.6.4.tgz", - "integrity": "sha512-51j4kUedbqkWGby44hAhf5f/hj8GOvHoLX00/YHURBNxOMf5k8JbPuGfmeNpZEXhc3vrmfnFben4+rOOx3HjEQ==", - "dependencies": { - "debug": "^4.3.0", - "lru-cache": "^4.0.3" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/memorystore/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/memorystore/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, "node_modules/merge-deep": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", @@ -14579,9 +14622,9 @@ } }, "node_modules/pg-connection-string": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.4.0.tgz", - "integrity": "sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" }, "node_modules/pg-int8": { "version": "1.0.1", @@ -14600,9 +14643,9 @@ } }, "node_modules/pg-pool": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz", - "integrity": "sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", + "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", "peerDependencies": { "pg": ">=8.0" } @@ -14622,9 +14665,9 @@ } }, "node_modules/pg-protocol": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.4.0.tgz", - "integrity": "sha512-El+aXWcwG/8wuFICMQjM5ZSAm6OWiJicFdNYo+VY3QP+8vI4SvLIWVe51PppTzMhikUJR+PsyIFKqfdXPz/yxA==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" }, "node_modules/pg-types": { "version": "2.2.0", @@ -16087,11 +16130,6 @@ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -16361,7 +16399,7 @@ "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", "engines": { "node": ">= 0.8" } @@ -25071,6 +25109,16 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "@types/pg": { + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.5.tgz", + "integrity": "sha512-tOkGtAqRVkHa/PVZicq67zuujI4Oorfglsr2IbKofDwBSysnaqSx7W1mDqFqdkGE6Fbgh+PZAl0r/BWON/mozw==", + "requires": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", @@ -27236,6 +27284,31 @@ "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" }, + "connect-pg-simple": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/connect-pg-simple/-/connect-pg-simple-8.0.0.tgz", + "integrity": "sha512-pBDa23RA1LCkwvRrPOh5xevB+Nknh1UDuhFOKsUrkUDodYqfiQT18P2qXc4lk/TqCMB6hI06B8KNncHh91bZMQ==", + "requires": { + "@types/pg": "^8.6.5", + "pg": "^8.8.0" + }, + "dependencies": { + "pg": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", + "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.2", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + } + } + }, "console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -29471,20 +29544,25 @@ "integrity": "sha512-cjQH+oDrEPXxc569XvxhHC6QXqJiuBT6BhZ70X3bdAImcnHnTNMVuMAJaT0TXPoRiEErUrVPRcOTpZpM36VbOQ==" }, "express-session": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.1.tgz", - "integrity": "sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q==", + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", + "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", "requires": { - "cookie": "0.4.0", + "cookie": "0.4.2", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~2.0.0", "on-headers": "~1.0.2", "parseurl": "~1.3.3", - "safe-buffer": "5.2.0", + "safe-buffer": "5.2.1", "uid-safe": "~2.1.5" }, "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -29501,12 +29579,12 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -32445,31 +32523,6 @@ } } }, - "memorystore": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/memorystore/-/memorystore-1.6.4.tgz", - "integrity": "sha512-51j4kUedbqkWGby44hAhf5f/hj8GOvHoLX00/YHURBNxOMf5k8JbPuGfmeNpZEXhc3vrmfnFben4+rOOx3HjEQ==", - "requires": { - "debug": "^4.3.0", - "lru-cache": "^4.0.3" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - } - } - }, "merge-deep": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", @@ -33622,9 +33675,9 @@ } }, "pg-connection-string": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.4.0.tgz", - "integrity": "sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" }, "pg-int8": { "version": "1.0.1", @@ -33637,9 +33690,9 @@ "integrity": "sha512-1KdmFGGTP6jplJoI8MfvRlfvMiyBivMRP7/ffh4a11RUFJ7kC2J0ZHlipoKiH/1hz+DVgceon9U2qbaHpPeyPg==" }, "pg-pool": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz", - "integrity": "sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", + "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", "requires": {} }, "pg-promise": { @@ -33654,9 +33707,9 @@ } }, "pg-protocol": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.4.0.tgz", - "integrity": "sha512-El+aXWcwG/8wuFICMQjM5ZSAm6OWiJicFdNYo+VY3QP+8vI4SvLIWVe51PppTzMhikUJR+PsyIFKqfdXPz/yxA==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" }, "pg-types": { "version": "2.2.0", @@ -34852,11 +34905,6 @@ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -35095,7 +35143,7 @@ "random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==" }, "randombytes": { "version": "2.1.0", diff --git a/package.json b/package.json index 0cca6546..977158f2 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "chart.js": "^3.6.0", "chartjs-plugin-zoom": "^1.2.1", "compression": "^1.7.4", + "connect-pg-simple": "^8.0.0", "cookie-parser": "^1.4.5", "cors": "^2.8.5", "cross-env": "^7.0.2", @@ -86,7 +87,7 @@ "eslint-plugin-react-hooks": "^1.6.1", "express": "^4.16.3", "express-rate-limit": "^5.1.3", - "express-session": "^1.17.1", + "express-session": "^1.17.3", "file-loader": "4.3.0", "file-saver": "^2.0.2", "flat": "^5.0.2", @@ -105,7 +106,6 @@ "jquery": "^3.5.1", "lithosphere": "^1.5.4", "mark.js": "^8.11.1", - "memorystore": "^1.6.2", "mini-css-extract-plugin": "0.9.0", "nipplejs": "^0.8.5", "node-fetch": "^2.6.1", diff --git a/public/index.html b/public/index.html index 129904c8..91b03a16 100644 --- a/public/index.html +++ b/public/index.html @@ -336,6 +336,7 @@ mmgisglobal.HOSTS = "#{HOSTS}"; mmgisglobal.PORT = "#{PORT}"; mmgisglobal.ENABLE_MMGIS_WEBSOCKETS = "#{ENABLE_MMGIS_WEBSOCKETS}"; + mmgisglobal.THIRD_PARTY_COOKIES = "#{THIRD_PARTY_COOKIES}"; break; default: mmgisglobal.AUTH = "%AUTH%"; @@ -348,6 +349,7 @@ mmgisglobal.CLEARANCE_NUMBER = "%CLEARANCE_NUMBER%"; mmgisglobal.PORT = "%PORT%"; mmgisglobal.ENABLE_MMGIS_WEBSOCKETS = "%ENABLE_MMGIS_WEBSOCKETS%"; + mmgisglobal.THIRD_PARTY_COOKIES = "#{THIRD_PARTY_COOKIES}"; // eslint-disable-next-line mmgisglobal.HOSTS = %HOSTS%; break; diff --git a/public/login.js b/public/login.js index 9bd59009..cbb40384 100644 --- a/public/login.js +++ b/public/login.js @@ -21,12 +21,11 @@ function login() { (data.hasOwnProperty("status") && data.status === "success") ) { //success - document.cookie = - "MMGISUser=" + - JSON.stringify({ - username: data.username, - token: data.token, - }); + document.cookie = "MMGISUser=;expires=Thu, 01 Jan 1970 00:00:01 GMT;"; + document.cookie = `MMGISUser=${JSON.stringify({ + username: data.username, + token: data.token, + })}${data.additional}`; window.location.reload(); } else { //error diff --git a/run/init-db.js b/run/init-db.js index e380d8fc..ee0bb428 100644 --- a/run/init-db.js +++ b/run/init-db.js @@ -87,9 +87,36 @@ async function initializeDatabase() { "connection" ); + return null; + }); + await sequelize + .query( + ` + CREATE TABLE "session" ( + "sid" varchar NOT NULL COLLATE "default", + "sess" json NOT NULL, + "expire" timestamp(6) NOT NULL + ) + WITH (OIDS=FALSE); + + ALTER TABLE "session" ADD CONSTRAINT "session_pkey" PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE; + + CREATE INDEX "IDX_session_expire" ON "session" ("expire");` + ) + .then(() => { + logger("info", `Created "session" table.`, "connection"); + return null; + }) + .catch((err) => { + logger( + "info", + `"session" table already exists. Nothing to do...`, + "connection" + ); resolve(); return null; }); + resolve(); }) .catch((err) => { logger( diff --git a/run/server.js b/run/server.js index c3aa0df5..b0966994 100644 --- a/run/server.js +++ b/run/server.js @@ -2,6 +2,7 @@ require("dotenv").config(); const fs = require("fs"); const http = require("http"); +const { Pool } = require("pg"); var path = require("path"); const packagejson = require("../package.json"); var bodyParser = require("body-parser"); @@ -18,7 +19,6 @@ const rateLimit = require("express-rate-limit"); const compression = require("compression"); const session = require("express-session"); -var MemoryStore = require("memorystore")(session); const apiRouter = require("../API/Backend/APIs/routes/apis"); @@ -83,16 +83,29 @@ permissions.users = process.env.CSSO_GROUPS const port = parseInt(process.env.PORT || "8888", 10); /** set the session for application */ +const cookieOptions = { maxAge: 86400000 }; +if (process.env.THIRD_PARTY_COOKIES === "true") { + cookieOptions.sameSite = "None"; + if (process.env.NODE_ENV === "production") cookieOptions.secure = true; +} + +const pool = new Pool({ + user: process.env.DB_USER, + host: process.env.DB_HOST, + database: process.env.DB_NAME, + password: process.env.DB_PASS, + port: process.env.DB_PORT || "5432", +}); app.use( session({ secret: process.env.SECRET || "Shhhh, it is a secret!", name: "MMGISSession", proxy: true, resave: false, - cookie: { maxAge: 86400000 }, + cookie: cookieOptions, saveUninitialized: false, - store: new MemoryStore({ - checkPeriod: 86400000, // prune expired entries every 24h + store: new (require("connect-pg-simple")(session))({ + pool, }), }) ); @@ -491,6 +504,8 @@ setups.getBackendSetups(function (setups) { ) ); + // STATICS + app.use("/build", ensureUser(), express.static(path.join(rootDir, "/build"))); app.use( "/documentation", @@ -690,6 +705,7 @@ setups.getBackendSetups(function (setups) { FORCE_CONFIG_PATH: process.env.FORCE_CONFIG_PATH, CLEARANCE_NUMBER: process.env.CLEARANCE_NUMBER, ENABLE_MMGIS_WEBSOCKETS: process.env.ENABLE_MMGIS_WEBSOCKETS, + THIRD_PARTY_COOKIES: process.env.THIRD_PARTY_COOKIES, PORT: process.env.PORT, HOSTS: JSON.stringify({ scienceIntent: process.env.SCIENCE_INTENT_HOST, diff --git a/sample.env b/sample.env index db0bc585..7bbc5af4 100644 --- a/sample.env +++ b/sample.env @@ -22,6 +22,11 @@ VERBOSE_LOGGING=false # Sets the Content-Security-Policy: frame-ancestors header to allow the embedding in external sites. default null, ex: FRAME_ANCESTORS='["https://*.jpl.nasa.gov"]' FRAME_ANCESTORS= +# Sets "SameSite=None; Secure" on the login cookie. Useful when using AUTH=local as an iframe within a cross-origin page. +THIRD_PARTY_COOKIES=false +# Sets the Content-Security-Policy: frame-src header to allow the embedding external sites with mmgis. default null, ex: FRAME_ANCESTORS='["https://*.jpl.nasa.gov"]'. +# Setting this will almost always have no effect +FRAME_SRC= # Allows MMGIS to be deployed at a subpath. Use an absolute path. For example if serving at the subpath 'mmgis' is desired, set PUBLIC_URL to 'https://{domain}/mmgis/build' PUBLIC_URL= diff --git a/src/essence/Ancillary/Login/Login.js b/src/essence/Ancillary/Login/Login.js index 51dbd590..bb8f3364 100644 --- a/src/essence/Ancillary/Login/Login.js +++ b/src/essence/Ancillary/Login/Login.js @@ -479,12 +479,17 @@ var Login = { function loginSuccess(data, ignoreError) { if (data.status == 'success') { - document.cookie = - 'MMGISUser=' + - JSON.stringify({ - username: data.username, - token: data.token, - }) + document.cookie = 'MMGISUser=;expires=Thu, 01 Jan 1970 00:00:01 GMT;' + document.cookie = `MMGISUser=${JSON.stringify({ + username: data.username, + token: data.token, + })}${ + mmgisglobal.THIRD_PARTY_COOKIES === 'true' + ? `; SameSite=None;${ + mmgisglobal.NODE_ENV === 'production' ? ' Secure' : '' + }` + : '' + }` Login.loggedIn = true $('#loginErrorMessage').animate({ opacity: '0' }, 500)