From 95959fb517a5fcad3b72816ab9631e143f0a86ef Mon Sep 17 00:00:00 2001 From: Martin Shetty <1972005+martukas@users.noreply.github.com> Date: Tue, 10 May 2022 03:15:24 -0700 Subject: [PATCH 1/3] "user db; updates #3" --- package-lock.json | 246 +++++++++++++++++++++++++++++++ package.json | 6 + src/client/App.js | 2 + src/client/pages/login.jsx | 35 +++++ src/client/pages/secret_page.jsx | 21 +++ src/server/database.js | 34 +++-- src/server/routes.js | 3 + src/server/user.js | 18 +++ 8 files changed, 351 insertions(+), 14 deletions(-) create mode 100644 src/client/pages/login.jsx create mode 100644 src/client/pages/secret_page.jsx create mode 100644 src/server/user.js diff --git a/package-lock.json b/package-lock.json index a7306ca..595460a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,9 @@ "license": "Apache2", "dependencies": { "axios": "^0.26.1", + "body-parser": "^1.20.0", "bootstrap": "^5.1.3", + "connect-ensure-login": "^0.1.1", "cookie-parser": "^1.4.6", "core-js": "^3.22.0", "cors": "^2.8.5", @@ -19,10 +21,14 @@ "dotenv": "^16.0.0", "express": "^4.17.3", "express-fileupload": "^1.3.1", + "express-session": "^1.17.2", "file-saver": "^2.0.5", "mongodb": "^4.5.0", "mongoose": "^6.3.2", "morgan": "^1.10.0", + "passport": "^0.5.2", + "passport-local": "^1.0.0", + "passport-local-mongoose": "^7.0.0", "react": "^18.0.0", "react-bootstrap": "^2.3.0", "react-dom": "^18.0.0", @@ -3596,6 +3602,14 @@ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", "dev": true }, + "node_modules/connect-ensure-login": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/connect-ensure-login/-/connect-ensure-login-0.1.1.tgz", + "integrity": "sha1-F03MUSQ7nqwj+NmCFa62aU4uihI=", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/connect-history-api-fallback": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", @@ -5014,6 +5028,56 @@ "node": ">=12.0.0" } }, + "node_modules/express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "dependencies": { + "cookie": "0.4.1", + "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.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "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=" + }, + "node_modules/express-session/node_modules/safe-buffer": { + "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/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -5331,6 +5395,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generaterr": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/generaterr/-/generaterr-1.5.0.tgz", + "integrity": "sha1-sM62zFFk3yoGEzjMNAqGFTlcUvw=" + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -7675,6 +7744,54 @@ "tslib": "^2.0.3" } }, + "node_modules/passport": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.2.tgz", + "integrity": "sha512-w9n/Ot5I7orGD4y+7V3EFJCQEznE5RxHamUxcqLT2QoJY0f2JdN8GyHonYFvN0Vz+L6lUJfVhrk2aZz2LbuREw==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-local-mongoose": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/passport-local-mongoose/-/passport-local-mongoose-7.0.0.tgz", + "integrity": "sha512-JwpMJJa7D73ri9vojXNOgsPT4pzw5UowH4QhdXQUW18noLngwJ+mtImo/5cMVlOuZEB6Q2tryy4YGqJRMZzC+g==", + "dependencies": { + "generaterr": "^1.5.0", + "passport-local": "^1.0.0", + "scmp": "^2.1.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -7719,6 +7836,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -8101,6 +8223,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -8703,6 +8833,11 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/scmp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", + "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -9679,6 +9814,17 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -13422,6 +13568,11 @@ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", "dev": true }, + "connect-ensure-login": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/connect-ensure-login/-/connect-ensure-login-0.1.1.tgz", + "integrity": "sha1-F03MUSQ7nqwj+NmCFa62aU4uihI=" + }, "connect-history-api-fallback": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", @@ -14513,6 +14664,41 @@ "busboy": "^0.3.1" } }, + "express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "requires": { + "cookie": "0.4.1", + "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.1", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -14719,6 +14905,11 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, + "generaterr": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/generaterr/-/generaterr-1.5.0.tgz", + "integrity": "sha1-sM62zFFk3yoGEzjMNAqGFTlcUvw=" + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -16459,6 +16650,38 @@ "tslib": "^2.0.3" } }, + "passport": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.2.tgz", + "integrity": "sha512-w9n/Ot5I7orGD4y+7V3EFJCQEznE5RxHamUxcqLT2QoJY0f2JdN8GyHonYFvN0Vz+L6lUJfVhrk2aZz2LbuREw==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "requires": { + "passport-strategy": "1.x.x" + } + }, + "passport-local-mongoose": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/passport-local-mongoose/-/passport-local-mongoose-7.0.0.tgz", + "integrity": "sha512-JwpMJJa7D73ri9vojXNOgsPT4pzw5UowH4QhdXQUW18noLngwJ+mtImo/5cMVlOuZEB6Q2tryy4YGqJRMZzC+g==", + "requires": { + "generaterr": "^1.5.0", + "passport-local": "^1.0.0", + "scmp": "^2.1.0" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -16494,6 +16717,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -16772,6 +17000,11 @@ "side-channel": "^1.0.4" } }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -17223,6 +17456,11 @@ "ajv-keywords": "^3.5.2" } }, + "scmp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", + "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -17991,6 +18229,14 @@ "is-typedarray": "^1.0.0" } }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index 385347d..4d65d7f 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "license": "Apache2", "dependencies": { "axios": "^0.26.1", + "body-parser": "^1.20.0", "bootstrap": "^5.1.3", + "connect-ensure-login": "^0.1.1", "cookie-parser": "^1.4.6", "core-js": "^3.22.0", "cors": "^2.8.5", @@ -23,10 +25,14 @@ "dotenv": "^16.0.0", "express": "^4.17.3", "express-fileupload": "^1.3.1", + "express-session": "^1.17.2", "file-saver": "^2.0.5", "mongodb": "^4.5.0", "mongoose": "^6.3.2", "morgan": "^1.10.0", + "passport": "^0.5.2", + "passport-local": "^1.0.0", + "passport-local-mongoose": "^7.0.0", "react": "^18.0.0", "react-bootstrap": "^2.3.0", "react-dom": "^18.0.0", diff --git a/src/client/App.js b/src/client/App.js index d2b7e7d..2166aab 100644 --- a/src/client/App.js +++ b/src/client/App.js @@ -5,6 +5,7 @@ import DataFileTable from './pages/data-file-table'; import UploadFile from './pages/upload-file'; import DataSet from './pages/dataset'; import NotFound from './pages/notfound'; +import Login from './pages/login'; export default function App() { return ( @@ -15,6 +16,7 @@ export default function App() { } /> } /> } /> + } /> } /> diff --git a/src/client/pages/login.jsx b/src/client/pages/login.jsx new file mode 100644 index 0000000..30834b4 --- /dev/null +++ b/src/client/pages/login.jsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Button, Container, Form } from 'react-bootstrap'; + +function Login() { + + + return ( +
+ +
+ + Email address + + + We will never share your email with anyone else. + + + + + Password + + + + + + +
+
+
+ ); +} + +export default Login; diff --git a/src/client/pages/secret_page.jsx b/src/client/pages/secret_page.jsx new file mode 100644 index 0000000..80999b0 --- /dev/null +++ b/src/client/pages/secret_page.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Button, Container, Form } from 'react-bootstrap'; + +function SecretPage() { + return ( +
+ +

Secret Page

+ +
+
+ ); +} + +export default SecretPage; diff --git a/src/server/database.js b/src/server/database.js index 6505a41..6d2c8e8 100644 --- a/src/server/database.js +++ b/src/server/database.js @@ -1,14 +1,12 @@ const { ServerApiVersion } = require('mongodb'); const mongoose = require('mongoose'); +const schemata = require('./user'); // See README for what to put in your `.env` file const { MONGO_URI } = process.env; -const databaseName = 'test-data'; -const collectionName = 'experiments'; - let instance = null; // https://www.digitalocean.com/community/tutorials/containerizing-a-node-js-application-for-development-with-docker-compose @@ -20,27 +18,35 @@ class DataBase { useUnifiedTopology: true, serverApi: ServerApiVersion.v1, /* mongoose options */ - dbName: databaseName, + dbName: null, autoIndex: false, // Don't build indexes maxPoolSize: 10, // Maintain up to 10 socket connections serverSelectionTimeoutMS: 5000, // Keep trying to send operations for 5 seconds socketTimeoutMS: 45000, // Close sockets after 45 seconds of inactivity family: 4 // Use IPv4, skip trying IPv6 }; - // this.client = null; - this.experiments_db = null; + this.experiments_conn = null; + this.experiments = null; + this.user_model = null; } async connect() { try { - await mongoose.connect(MONGO_URI, this.mongoose_options); + this.mongoose_options.dbName = 'test-data'; + this.experiments_conn = await mongoose.createConnection( + MONGO_URI, + this.mongoose_options + ).asPromise(); } catch (error) { console.log(`Failed to connect to database: ${error}`); } - mongoose.connection.on('error', (err) => { - console.log(`Database error: ${err}`); + mongoose.connection.on('error', (error) => { + console.log(`Database error: ${error}`); }); - this.experiments_db = mongoose.connection.db; + this.experiments = this.experiments_conn.db.collection('experiments'); + this.user_model = this.experiments_conn.model('userData', schemata.User, 'userData'); + console.log(`connection: ${this.experiments_conn}`); + console.log(`col: ${this.experiments}`); } static getInstance() { @@ -52,7 +58,7 @@ class DataBase { } async testConnectToMongo() { - return this.experiments_db.collection(collectionName).findOne({}, (err, result) => { + return this.experiments.findOne({}, (err, result) => { if (err) throw err; console.log(result); }); @@ -61,7 +67,7 @@ class DataBase { // TODO: make querying more flexible, return various fields async grabMetadata() { const retData = []; - await this.experiments_db.collection(collectionName).find().forEach( + await this.experiments.find().forEach( (e) => { const e2 = { unique_id: e.unique_id, @@ -77,11 +83,11 @@ class DataBase { async getFullExperimentData(uniqueId) { const query = { unique_id: uniqueId }; - return this.experiments_db.collection(collectionName).findOne(query); + return this.experiments.findOne(query); } async uploadExperiment(data, uniqueId) { - return this.experiments_db.collection(collectionName).insertOne({ + return this.experiments.insertOne({ unique_id: uniqueId, ...data, }); diff --git a/src/server/routes.js b/src/server/routes.js index 0bb1ff9..1b9bbd3 100644 --- a/src/server/routes.js +++ b/src/server/routes.js @@ -5,6 +5,9 @@ const { DataBase } = require('./database'); const router = express.Router(); router.get('/get-test-table-data', async (req, res) => { + // DataBase.getInstance().user_model.register({ username: 'candy', active: false }, 'cane'); + // DataBase.getInstance().user_model.register({ username: 'starbuck', active: false }, 'redeye'); + const tableData = await DataBase.getInstance().grabMetadata(); res.send({ msg: 'Worked!', diff --git a/src/server/user.js b/src/server/user.js new file mode 100644 index 0000000..57c8b2c --- /dev/null +++ b/src/server/user.js @@ -0,0 +1,18 @@ +// dependencies +const mongoose = require('mongoose'); +const passportLocalMongoose = require('passport-local-mongoose'); + +// Create Model +const { Schema } = mongoose; +const User = new Schema({ + // username: { type: String, required: true }, + // password: { type: String, required: true } + username: String, + password: String +}); +// Export Model +User.plugin(passportLocalMongoose); + +module.exports = { + User +}; From de0e0f771c49af668282ff557fb1f11e872381df Mon Sep 17 00:00:00 2001 From: Martin Shetty <1972005+martukas@users.noreply.github.com> Date: Wed, 11 May 2022 03:04:28 -0700 Subject: [PATCH 2/3] "local authentication strategies; updates #3" --- package-lock.json | 806 ++++++++++++++++++- package.json | 7 +- src/client/App.js | 8 +- src/client/context/UserContext.js | 17 + src/client/index.js | 5 +- src/client/pages/authorize.jsx | 55 ++ src/client/pages/login.jsx | 96 ++- src/client/pages/navbar.jsx | 3 + src/client/pages/register.jsx | 106 +++ src/client/pages/secret_page.jsx | 21 - src/client/pages/welcome.jsx | 91 +++ src/server/authenticate.js | 27 + src/server/database.js | 27 +- src/server/models/user.js | 50 ++ src/server/{routes.js => routes/dbRoutes.js} | 5 +- src/server/routes/userRoutes.js | 174 ++++ src/server/server.js | 64 +- src/server/strategies/JwtStrategy.js | 35 + src/server/strategies/LocalStrategy.js | 20 + src/server/user.js | 18 - 20 files changed, 1504 insertions(+), 131 deletions(-) create mode 100644 src/client/context/UserContext.js create mode 100644 src/client/pages/authorize.jsx create mode 100644 src/client/pages/register.jsx delete mode 100644 src/client/pages/secret_page.jsx create mode 100644 src/client/pages/welcome.jsx create mode 100644 src/server/authenticate.js create mode 100644 src/server/models/user.js rename src/server/{routes.js => routes/dbRoutes.js} (85%) create mode 100644 src/server/routes/userRoutes.js create mode 100644 src/server/strategies/JwtStrategy.js create mode 100644 src/server/strategies/LocalStrategy.js delete mode 100644 src/server/user.js diff --git a/package-lock.json b/package-lock.json index 595460a..c899dfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "license": "Apache2", "dependencies": { + "@blueprintjs/core": "^4.3.0", "axios": "^0.26.1", "body-parser": "^1.20.0", "bootstrap": "^5.1.3", @@ -23,12 +24,15 @@ "express-fileupload": "^1.3.1", "express-session": "^1.17.2", "file-saver": "^2.0.5", + "jsonwebtoken": "^8.5.1", "mongodb": "^4.5.0", "mongoose": "^6.3.2", "morgan": "^1.10.0", "passport": "^0.5.2", + "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "passport-local-mongoose": "^7.0.0", + "path": "^0.12.7", "react": "^18.0.0", "react-bootstrap": "^2.3.0", "react-dom": "^18.0.0", @@ -53,6 +57,7 @@ "file-loader": "^6.2.0", "html-webpack-plugin": "^5.5.0", "nodemon": "^2.0.15", + "react-error-overlay": "^6.0.11", "sass": "^1.50.0", "sass-loader": "^12.6.0", "style-loader": "^3.3.1", @@ -1748,6 +1753,74 @@ "node": ">=6.9.0" } }, + "node_modules/@blueprintjs/colors": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@blueprintjs/colors/-/colors-4.1.2.tgz", + "integrity": "sha512-wvq92hgRZZYrohI8GaN/pV0iQfxvWa2RI1cLYuItDvXM6i/u1riaw0RcsqqAIL1MH1fHsKFdH1O8i7Tj5a+lpQ==" + }, + "node_modules/@blueprintjs/core": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@blueprintjs/core/-/core-4.3.0.tgz", + "integrity": "sha512-0MoQjGr6IfoC4xAtZ8EWIaGeLihsC85Ylzarc/w/APsvUdvUw+DWyGhwy7X51r3f27skhuok6HrVOw2d3/V0eg==", + "dependencies": { + "@blueprintjs/colors": "^4.1.2", + "@blueprintjs/icons": "^4.2.5", + "@juggle/resize-observer": "^3.3.1", + "@types/dom4": "^2.0.1", + "classnames": "^2.2", + "dom4": "^2.1.5", + "normalize.css": "^8.0.1", + "popper.js": "^1.16.1", + "react-popper": "^1.3.7", + "react-transition-group": "^4.4.1", + "tslib": "~2.3.1" + }, + "bin": { + "upgrade-blueprint-2.0.0-rename": "scripts/upgrade-blueprint-2.0.0-rename.sh", + "upgrade-blueprint-3.0.0-rename": "scripts/upgrade-blueprint-3.0.0-rename.sh" + }, + "peerDependencies": { + "react": "^16.8 || 17 || 18", + "react-dom": "^16.8 || 17 || 18" + } + }, + "node_modules/@blueprintjs/core/node_modules/react-popper": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz", + "integrity": "sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "@hypnosphi/create-react-context": "^0.3.1", + "deep-equal": "^1.1.1", + "popper.js": "^1.14.4", + "prop-types": "^15.6.1", + "typed-styles": "^0.0.7", + "warning": "^4.0.2" + }, + "peerDependencies": { + "react": "0.14.x || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@blueprintjs/core/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "node_modules/@blueprintjs/icons": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@blueprintjs/icons/-/icons-4.2.5.tgz", + "integrity": "sha512-wovXczQwIMlKoakYVYbv03s20jRQD3zivrOhJayLX0dl5FccnEVcHx2Vnxi/7nq6FVztCCdjYyd5fIUHfDe7tg==", + "dependencies": { + "change-case": "^4.1.2", + "classnames": "^2.2", + "tslib": "~2.3.1" + } + }, + "node_modules/@blueprintjs/icons/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -1812,6 +1885,19 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@hypnosphi/create-react-context": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", + "integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==", + "dependencies": { + "gud": "^1.0.0", + "warning": "^4.0.3" + }, + "peerDependencies": { + "prop-types": "^15.0.0", + "react": ">=0.14.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", @@ -1859,6 +1945,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@juggle/resize-observer": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz", + "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz", @@ -1975,6 +2066,11 @@ "@types/node": "*" } }, + "node_modules/@types/dom4": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/dom4/-/dom4-2.0.2.tgz", + "integrity": "sha512-Rt4IC1T7xkCWa0OG1oSsPa0iqnxlDeQqKXZAHrQGLb7wFGncWm85MaxKUjAGejOrUynOgWlFi4c6S6IyJwoK4g==" + }, "node_modules/@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", @@ -3067,6 +3163,11 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3159,7 +3260,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" @@ -3193,6 +3293,16 @@ } ] }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -3207,6 +3317,25 @@ "node": ">=4" } }, + "node_modules/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -3619,6 +3748,16 @@ "node": ">=0.8" } }, + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -3919,6 +4058,22 @@ "node": ">=4" } }, + "node_modules/deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dependencies": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -3965,7 +4120,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, "dependencies": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -4107,6 +4261,11 @@ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, + "node_modules/dom4": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/dom4/-/dom4-2.1.6.tgz", + "integrity": "sha512-JkCVGnN4ofKGbjf5Uvc8mmxaATIErKQKSgACdBXpsQ3fY6DlIpAyWfiBSrGkttATssbDCp3psiAKWXk5gmjycA==" + }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -4152,7 +4311,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -4184,6 +4342,14 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5390,7 +5556,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5571,6 +5736,11 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "node_modules/gud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -5610,7 +5780,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.1" }, @@ -5633,7 +5802,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -5662,6 +5830,15 @@ "he": "bin/he" } }, + "node_modules/header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/history": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", @@ -6054,6 +6231,21 @@ "node": ">= 0.10" } }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -6140,7 +6332,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -6339,7 +6530,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -6566,6 +6756,35 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.0.tgz", @@ -6579,6 +6798,25 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.5.tgz", @@ -6711,12 +6949,47 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -6744,7 +7017,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, "dependencies": { "tslib": "^2.0.3" } @@ -7123,7 +7395,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" @@ -7245,6 +7516,11 @@ "node": ">=8" } }, + "node_modules/normalize.css": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", + "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==" + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -7294,11 +7570,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -7695,7 +7985,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -7738,7 +8027,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -7760,6 +8048,15 @@ "url": "https://github.com/sponsors/jaredhanson" } }, + "node_modules/passport-jwt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", + "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==", + "dependencies": { + "jsonwebtoken": "^8.2.0", + "passport-strategy": "^1.0.0" + } + }, "node_modules/passport-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", @@ -7792,6 +8089,24 @@ "node": ">= 0.4.0" } }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -7971,6 +8286,16 @@ "node": ">=8" } }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -8124,6 +8449,14 @@ "renderkid": "^3.0.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -8344,6 +8677,12 @@ "react": "^18.1.0" } }, + "node_modules/react-error-overlay": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", + "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==", + "dev": true + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -8490,7 +8829,6 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -8918,6 +9256,16 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -9146,6 +9494,15 @@ "npm": ">= 3.0.0" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -9766,8 +10123,7 @@ "node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "node_modules/type-check": { "version": "0.4.0", @@ -9805,6 +10161,11 @@ "node": ">= 0.6" } }, + "node_modules/typed-styles": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", + "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -10033,6 +10394,22 @@ "node": ">=8" } }, + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -10099,12 +10476,25 @@ "node": ">=4" } }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dependencies": { + "inherits": "2.0.3" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, "node_modules/utila": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", @@ -12073,6 +12463,67 @@ "to-fast-properties": "^2.0.0" } }, + "@blueprintjs/colors": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@blueprintjs/colors/-/colors-4.1.2.tgz", + "integrity": "sha512-wvq92hgRZZYrohI8GaN/pV0iQfxvWa2RI1cLYuItDvXM6i/u1riaw0RcsqqAIL1MH1fHsKFdH1O8i7Tj5a+lpQ==" + }, + "@blueprintjs/core": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@blueprintjs/core/-/core-4.3.0.tgz", + "integrity": "sha512-0MoQjGr6IfoC4xAtZ8EWIaGeLihsC85Ylzarc/w/APsvUdvUw+DWyGhwy7X51r3f27skhuok6HrVOw2d3/V0eg==", + "requires": { + "@blueprintjs/colors": "^4.1.2", + "@blueprintjs/icons": "^4.2.5", + "@juggle/resize-observer": "^3.3.1", + "@types/dom4": "^2.0.1", + "classnames": "^2.2", + "dom4": "^2.1.5", + "normalize.css": "^8.0.1", + "popper.js": "^1.16.1", + "react-popper": "^1.3.7", + "react-transition-group": "^4.4.1", + "tslib": "~2.3.1" + }, + "dependencies": { + "react-popper": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz", + "integrity": "sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==", + "requires": { + "@babel/runtime": "^7.1.2", + "@hypnosphi/create-react-context": "^0.3.1", + "deep-equal": "^1.1.1", + "popper.js": "^1.14.4", + "prop-types": "^15.6.1", + "typed-styles": "^0.0.7", + "warning": "^4.0.2" + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "@blueprintjs/icons": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@blueprintjs/icons/-/icons-4.2.5.tgz", + "integrity": "sha512-wovXczQwIMlKoakYVYbv03s20jRQD3zivrOhJayLX0dl5FccnEVcHx2Vnxi/7nq6FVztCCdjYyd5fIUHfDe7tg==", + "requires": { + "change-case": "^4.1.2", + "classnames": "^2.2", + "tslib": "~2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, "@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -12124,6 +12575,15 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@hypnosphi/create-react-context": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", + "integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==", + "requires": { + "gud": "^1.0.0", + "warning": "^4.0.3" + } + }, "@jridgewell/gen-mapping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", @@ -12162,6 +12622,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@juggle/resize-observer": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz", + "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==" + }, "@leichtgewicht/ip-codec": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz", @@ -12260,6 +12725,11 @@ "@types/node": "*" } }, + "@types/dom4": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/dom4/-/dom4-2.0.2.tgz", + "integrity": "sha512-Rt4IC1T7xkCWa0OG1oSsPa0iqnxlDeQqKXZAHrQGLb7wFGncWm85MaxKUjAGejOrUynOgWlFi4c6S6IyJwoK4g==" + }, "@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", @@ -13155,6 +13625,11 @@ "ieee754": "^1.1.13" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -13225,7 +13700,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, "requires": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" @@ -13243,6 +13717,16 @@ "integrity": "sha512-ddP1Tgm7z2iIxu6QTtbZUv6HJxSaV/PZeSrWFZtbY4JZ69tOeNhBCl3HyRQgeNZKE5AOn1kpV7fhljigy0Ty3w==", "dev": true }, + "capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -13254,6 +13738,25 @@ "supports-color": "^5.3.0" } }, + "change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "requires": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -13579,6 +14082,16 @@ "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", "dev": true }, + "constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -13785,6 +14298,19 @@ "mimic-response": "^1.0.0" } }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -13822,7 +14348,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, "requires": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -13930,6 +14455,11 @@ "entities": "^2.0.0" } }, + "dom4": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/dom4/-/dom4-2.1.6.tgz", + "integrity": "sha512-JkCVGnN4ofKGbjf5Uvc8mmxaATIErKQKSgACdBXpsQ3fY6DlIpAyWfiBSrGkttATssbDCp3psiAKWXk5gmjycA==" + }, "domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -13960,7 +14490,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -13986,6 +14515,14 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -14902,8 +15439,7 @@ "functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" }, "generaterr": { "version": "1.5.0", @@ -15041,6 +15577,11 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "gud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -15071,7 +15612,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, "requires": { "get-intrinsic": "^1.1.1" } @@ -15085,7 +15625,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -15102,6 +15641,15 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "requires": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, "history": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", @@ -15392,6 +15940,15 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -15454,7 +16011,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -15580,7 +16136,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -15746,6 +16301,30 @@ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "jsx-ast-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.0.tgz", @@ -15756,6 +16335,25 @@ "object.assign": "^4.1.2" } }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kareem": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.5.tgz", @@ -15864,12 +16462,47 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -15894,7 +16527,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, "requires": { "tslib": "^2.0.3" } @@ -16187,7 +16819,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, "requires": { "lower-case": "^2.0.2", "tslib": "^2.0.3" @@ -16281,6 +16912,11 @@ "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", "dev": true }, + "normalize.css": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", + "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==" + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -16315,11 +16951,19 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object.assign": { "version": "4.1.2", @@ -16610,7 +17254,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, "requires": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -16644,7 +17287,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -16659,6 +17301,15 @@ "pause": "0.0.1" } }, + "passport-jwt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", + "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==", + "requires": { + "jsonwebtoken": "^8.2.0", + "passport-strategy": "^1.0.0" + } + }, "passport-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", @@ -16682,6 +17333,24 @@ "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" }, + "path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", + "requires": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -16815,6 +17484,11 @@ } } }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -16922,6 +17596,11 @@ "renderkid": "^3.0.0" } }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -17092,6 +17771,12 @@ "scheduler": "^0.22.0" } }, + "react-error-overlay": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", + "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==", + "dev": true + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -17211,7 +17896,6 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -17533,6 +18217,16 @@ } } }, + "sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -17719,6 +18413,15 @@ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" }, + "snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -18193,8 +18896,7 @@ "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "type-check": { "version": "0.4.0", @@ -18220,6 +18922,11 @@ "mime-types": "~2.1.24" } }, + "typed-styles": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", + "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -18390,6 +19097,22 @@ } } }, + "upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "requires": { + "tslib": "^2.0.3" + } + }, + "upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "requires": { + "tslib": "^2.0.3" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -18432,6 +19155,21 @@ "prepend-http": "^2.0.0" } }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 4d65d7f..6b23bdb 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,16 @@ "main": "src/server/server.js", "scripts": { "build": "webpack --mode production", - "start": "npm run build && node src/server/server.js", + "start": "export NODE_ENV=production && npm run build && node src/server/server.js", "client": "webpack-dev-server --mode development --devtool inline-source-map --hot", "server": "nodemon src/server/server.js", - "dev": "concurrently \"npm run server\" \"npm run client\"" + "dev": "export NODE_ENV=dev && concurrently \"npm run server\" \"npm run client\"" }, "author": "RespiraWorks", "license": "Apache2", "dependencies": { "axios": "^0.26.1", + "@blueprintjs/core": "^4.3.0", "body-parser": "^1.20.0", "bootstrap": "^5.1.3", "connect-ensure-login": "^0.1.1", @@ -27,10 +28,12 @@ "express-fileupload": "^1.3.1", "express-session": "^1.17.2", "file-saver": "^2.0.5", + "jsonwebtoken": "^8.5.1", "mongodb": "^4.5.0", "mongoose": "^6.3.2", "morgan": "^1.10.0", "passport": "^0.5.2", + "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "passport-local-mongoose": "^7.0.0", "react": "^18.0.0", diff --git a/src/client/App.js b/src/client/App.js index 2166aab..384836e 100644 --- a/src/client/App.js +++ b/src/client/App.js @@ -1,11 +1,13 @@ -import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { + BrowserRouter, Routes, Route +} from 'react-router-dom'; import React from 'react'; import MyNavbar from './pages/navbar'; import DataFileTable from './pages/data-file-table'; import UploadFile from './pages/upload-file'; import DataSet from './pages/dataset'; import NotFound from './pages/notfound'; -import Login from './pages/login'; +import Authorize from './pages/authorize'; export default function App() { return ( @@ -16,7 +18,7 @@ export default function App() { } /> } /> } /> - } /> + } /> } /> diff --git a/src/client/context/UserContext.js b/src/client/context/UserContext.js new file mode 100644 index 0000000..85915af --- /dev/null +++ b/src/client/context/UserContext.js @@ -0,0 +1,17 @@ +import React, { useState } from 'react'; + +const UserContext = React.createContext([{}, p => {}]); + +let initialState = {}; + +function UserProvider(props) { + const [state, setState] = useState(initialState); + + return ( + + { props.children } + + ); +} + +export { UserContext, UserProvider }; diff --git a/src/client/index.js b/src/client/index.js index 317fb4b..6669e6f 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -4,12 +4,15 @@ import './App.scss'; import App from './App'; import 'core-js/stable'; import 'regenerator-runtime/runtime'; +import { UserProvider } from './context/UserContext'; const container = document.getElementById('root'); const root = createRoot(container); root.render( - + + + ); diff --git a/src/client/pages/authorize.jsx b/src/client/pages/authorize.jsx new file mode 100644 index 0000000..1bdfab9 --- /dev/null +++ b/src/client/pages/authorize.jsx @@ -0,0 +1,55 @@ +import React, { + useCallback, useContext, useEffect, useState +} from 'react'; +import { + Container, Spinner, Tab, Tabs +} from 'react-bootstrap'; +import { UserContext } from '../context/UserContext'; +import Login from './login'; +import Register from './register'; +import Welcome from './welcome'; + +function Authorize() { + const [userContext, setUserContext] = useContext(UserContext); + + const verifyUser = useCallback(() => { + fetch('/users/refreshToken', { + method: 'POST', + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + }).then(async (response) => { + if (response.ok) { + const data = await response.json(); + setUserContext((oldValues) => ({ ...oldValues, token: data.token })); + } else { + setUserContext((oldValues) => ({ ...oldValues, token: null })); + } + // TODO: pick better refresh time - 5 min? + // call refreshToken every 1 minute to renew the authentication token. + setTimeout(verifyUser, 1 * 60 * 1000); + }); + }, [setUserContext]); + + useEffect(() => { + verifyUser(); + }, [verifyUser]); + + return userContext.token === null ? ( + + + + + + + + + + + ) : userContext.token ? ( + + ) : ( + + ); +} + +export default Authorize; diff --git a/src/client/pages/login.jsx b/src/client/pages/login.jsx index 30834b4..4b0df02 100644 --- a/src/client/pages/login.jsx +++ b/src/client/pages/login.jsx @@ -1,33 +1,81 @@ -import React from 'react'; -import { Button, Container, Form } from 'react-bootstrap'; +import { + Button, Callout, FormGroup, InputGroup +} from '@blueprintjs/core'; +import React, { useContext, useState } from 'react'; +import { UserContext } from '../context/UserContext'; function Login() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(''); + const [userContext, setUserContext] = useContext(UserContext); + const formSubmitHandler = (e) => { + e.preventDefault(); + setIsSubmitting(true); + setError(''); + + const genericErrorMessage = 'Something went wrong! Please try again later.'; + + fetch('/users/login', { + method: 'POST', + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: email, password }), + }) + .then(async (response) => { + setIsSubmitting(false); + if (!response.ok) { + if (response.status === 400) { + setError('Please fill all the fields correctly!'); + } else if (response.status === 401) { + setError('Invalid email and password combination.'); + } else { + setError(genericErrorMessage); + } + } else { + const data = await response.json(); + setUserContext((oldValues) => ({ ...oldValues, token: data.token })); + console.log(`received context.token: ${userContext}`); + } + }) + .catch((error) => { + setIsSubmitting(false); + setError(genericErrorMessage); + }); + }; return (
- -
- - Email address - - - We will never share your email with anyone else. - - - - - Password - - - - - - -
-
+ {error && {error}} +
+ + setEmail(e.target.value)} + /> + + + setPassword(e.target.value)} + /> + +
); } diff --git a/src/client/pages/navbar.jsx b/src/client/pages/navbar.jsx index 077d839..b64ec68 100644 --- a/src/client/pages/navbar.jsx +++ b/src/client/pages/navbar.jsx @@ -18,6 +18,9 @@ function MyNavbar() {

UPLOAD

+ +

USER

+
diff --git a/src/client/pages/register.jsx b/src/client/pages/register.jsx new file mode 100644 index 0000000..3462b96 --- /dev/null +++ b/src/client/pages/register.jsx @@ -0,0 +1,106 @@ +import { + Button, Callout, FormGroup, InputGroup +} from '@blueprintjs/core'; +import React, { useContext, useState } from 'react'; +import { UserContext } from '../context/UserContext'; + +function Register() { + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(''); + const [userContext, setUserContext] = useContext(UserContext); + + const formSubmitHandler = (e) => { + e.preventDefault(); + setIsSubmitting(true); + setError(''); + + const genericErrorMessage = 'Something went wrong! Please try again later.'; + + fetch('/users/signup', { + method: 'POST', + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + firstName, lastName, username: email, password + }), + }) + .then(async (response) => { + setIsSubmitting(false); + if (!response.ok) { + if (response.status === 400) { + setError('Please fill all the fields correctly!'); + } else if (response.status === 401) { + setError('Invalid email and password combination.'); + } else if (response.status === 500) { + console.log(response); + const data = await response.json(); + if (data.message) setError(data.message || genericErrorMessage); + } else { + setError(genericErrorMessage); + } + } else { + const data = await response.json(); + setUserContext((oldValues) => ({ ...oldValues, token: data.token })); + } + }) + .catch((error) => { + setIsSubmitting(false); + setError(genericErrorMessage); + }); + }; + + return ( +
+ {error && {error}} +
+ + setFirstName(e.target.value)} + value={firstName} + /> + + + setLastName(e.target.value)} + value={lastName} + /> + + + setEmail(e.target.value)} + value={email} + /> + + + setPassword(e.target.value)} + value={password} + /> + +
+ ); +} + +export default Register; diff --git a/src/client/pages/secret_page.jsx b/src/client/pages/secret_page.jsx deleted file mode 100644 index 80999b0..0000000 --- a/src/client/pages/secret_page.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { Button, Container, Form } from 'react-bootstrap'; - -function SecretPage() { - return ( -
- -

Secret Page

- -
-
- ); -} - -export default SecretPage; diff --git a/src/client/pages/welcome.jsx b/src/client/pages/welcome.jsx new file mode 100644 index 0000000..1c40cc2 --- /dev/null +++ b/src/client/pages/welcome.jsx @@ -0,0 +1,91 @@ +import { + Button, Container, Spinner +} from 'react-bootstrap'; +import React, { useCallback, useContext, useEffect } from 'react'; +import { UserContext } from '../context/UserContext'; + +function Welcome() { + const [userContext, setUserContext] = useContext(UserContext); + + const fetchUserDetails = useCallback(() => { + fetch('/users/me', { + method: 'GET', + credentials: 'include', + // Pass authentication token as bearer token in header + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${userContext.token}`, + }, + }).then(async (response) => { + if (response.ok) { + const data = await response.json(); + setUserContext((oldValues) => ({ ...oldValues, details: data })); + } else if (response.status === 401) { + // Edge case: when the token has expired. + // This could happen if the refreshToken calls have failed due to network error or + // User has had the tab open from previous day and tries to click on the Fetch button + window.location.reload(); + } else { + setUserContext((oldValues) => ({ ...oldValues, details: null })); + } + }); + }, [setUserContext, userContext.token]); + + useEffect(() => { + // fetch only when user details are not present + if (!userContext.details) { + fetchUserDetails() + } + }, [userContext.details, fetchUserDetails]); + + const logoutHandler = () => { + fetch('/users/logout', { + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${userContext.token}`, + }, + }).then(async () => { + setUserContext((oldValues) => ({ ...oldValues, details: undefined, token: null })); + window.localStorage.setItem('logout', Date.now()); + }); + }; + + const refetchHandler = () => { + // set details to undefined so that spinner will be displayed and + // fetchUserDetails will be invoked from useEffect + setUserContext((oldValues) => ({ ...oldValues, details: undefined })); + }; + + return userContext.details === null ? ( + 'Error Loading User details' + ) : !userContext.details ? ( + + ) : ( + +
+
+

+ Welcome  + + {userContext.details.firstName} + {userContext.details.lastName && ` ${userContext.details.lastName}`} + + ! +

+

+ Your reward points: + {' '} + {userContext.details.points} +

+
+
+ + +
+
+
+ ); +} + +export default Welcome; diff --git a/src/server/authenticate.js b/src/server/authenticate.js new file mode 100644 index 0000000..7a65fb9 --- /dev/null +++ b/src/server/authenticate.js @@ -0,0 +1,27 @@ +const passport = require('passport'); +const jwt = require('jsonwebtoken'); + +const dev = process.env.NODE_ENV !== 'production'; + +exports.COOKIE_OPTIONS = { + httpOnly: true, + // Since localhost is not having https protocol, + // secure cookies do not work correctly (in postman) + secure: !dev, + signed: true, + maxAge: eval(process.env.REFRESH_TOKEN_EXPIRY) * 1000, + sameSite: 'none', +}; + +exports.getToken = (user) => jwt.sign(user, process.env.JWT_SECRET, { + expiresIn: eval(process.env.SESSION_EXPIRY), +}); + +exports.getRefreshToken = (user) => { + const refreshToken = jwt.sign(user, process.env.REFRESH_TOKEN_SECRET, { + expiresIn: eval(process.env.REFRESH_TOKEN_EXPIRY), + }); + return refreshToken; +}; + +exports.verifyUser = passport.authenticate('JwtStrategy', { session: false }); diff --git a/src/server/database.js b/src/server/database.js index 6d2c8e8..64e88a7 100644 --- a/src/server/database.js +++ b/src/server/database.js @@ -1,6 +1,6 @@ const { ServerApiVersion } = require('mongodb'); const mongoose = require('mongoose'); -const schemata = require('./user'); +const schemata = require('./models/user'); // See README for what to put in your `.env` file const { @@ -25,15 +25,15 @@ class DataBase { socketTimeoutMS: 45000, // Close sockets after 45 seconds of inactivity family: 4 // Use IPv4, skip trying IPv6 }; - this.experiments_conn = null; - this.experiments = null; - this.user_model = null; + this.experiments_connection = null; + this.experiments_collection = null; + this.UserModel = null; } async connect() { try { this.mongoose_options.dbName = 'test-data'; - this.experiments_conn = await mongoose.createConnection( + this.experiments_connection = await mongoose.createConnection( MONGO_URI, this.mongoose_options ).asPromise(); @@ -43,10 +43,11 @@ class DataBase { mongoose.connection.on('error', (error) => { console.log(`Database error: ${error}`); }); - this.experiments = this.experiments_conn.db.collection('experiments'); - this.user_model = this.experiments_conn.model('userData', schemata.User, 'userData'); - console.log(`connection: ${this.experiments_conn}`); - console.log(`col: ${this.experiments}`); + this.experiments_collection = this.experiments_connection.db.collection('experiments'); + // (Name, schema, collection) + this.UserModel = this.experiments_connection.model('User', schemata.UserSchema, 'userData'); + console.log(`connection: ${this.experiments_connection}`); + console.log(`col: ${this.experiments_collection}`); } static getInstance() { @@ -58,7 +59,7 @@ class DataBase { } async testConnectToMongo() { - return this.experiments.findOne({}, (err, result) => { + return this.experiments_collection.findOne({}, (err, result) => { if (err) throw err; console.log(result); }); @@ -67,7 +68,7 @@ class DataBase { // TODO: make querying more flexible, return various fields async grabMetadata() { const retData = []; - await this.experiments.find().forEach( + await this.experiments_collection.find().forEach( (e) => { const e2 = { unique_id: e.unique_id, @@ -83,11 +84,11 @@ class DataBase { async getFullExperimentData(uniqueId) { const query = { unique_id: uniqueId }; - return this.experiments.findOne(query); + return this.experiments_collection.findOne(query); } async uploadExperiment(data, uniqueId) { - return this.experiments.insertOne({ + return this.experiments_collection.insertOne({ unique_id: uniqueId, ...data, }); diff --git a/src/server/models/user.js b/src/server/models/user.js new file mode 100644 index 0000000..3ea4732 --- /dev/null +++ b/src/server/models/user.js @@ -0,0 +1,50 @@ +// dependencies +const mongoose = require('mongoose'); +const passportLocalMongoose = require('passport-local-mongoose'); + +const { Schema } = mongoose; + +const SessionSchema = new Schema({ + refreshToken: { + type: String, + default: '', + }, +}); + +const UserSchema = new Schema({ + firstName: { + type: String, + default: '', + }, + lastName: { + type: String, + default: '', + }, + authStrategy: { + type: String, + default: 'local', + }, + points: { + type: Number, + default: 50, + }, + refreshToken: { + type: [SessionSchema], + }, +}); + +// Remove refreshToken from the response +UserSchema.set('toJSON', { + transform: function (doc, ret, options) { + delete ret.refreshToken; + return ret; + }, +}); + +UserSchema.plugin(passportLocalMongoose); + +// module.exports = mongoose.model('User', User); + +module.exports = { + UserSchema +}; diff --git a/src/server/routes.js b/src/server/routes/dbRoutes.js similarity index 85% rename from src/server/routes.js rename to src/server/routes/dbRoutes.js index 1b9bbd3..f82cdf6 100644 --- a/src/server/routes.js +++ b/src/server/routes/dbRoutes.js @@ -1,13 +1,10 @@ const express = require('express'); const path = require('path'); -const { DataBase } = require('./database'); +const { DataBase } = require('../database'); const router = express.Router(); router.get('/get-test-table-data', async (req, res) => { - // DataBase.getInstance().user_model.register({ username: 'candy', active: false }, 'cane'); - // DataBase.getInstance().user_model.register({ username: 'starbuck', active: false }, 'redeye'); - const tableData = await DataBase.getInstance().grabMetadata(); res.send({ msg: 'Worked!', diff --git a/src/server/routes/userRoutes.js b/src/server/routes/userRoutes.js new file mode 100644 index 0000000..a059a84 --- /dev/null +++ b/src/server/routes/userRoutes.js @@ -0,0 +1,174 @@ +const express = require('express'); +const passport = require('passport'); +const jwt = require('jsonwebtoken'); +const { DataBase } = require('../database'); +const { + // getToken, + // COOKIE_OPTIONS, + // getRefreshToken, + verifyUser, +} = require('../authenticate'); + +const router = express.Router(); + +const { getToken, COOKIE_OPTIONS, getRefreshToken } = require('../authenticate'); + +router.post('/signup', (req, res, next) => { + // Verify that first name is not empty + if (!req.body.firstName) { + res.statusCode = 500; + res.send({ + name: 'FirstNameError', + message: 'The first name is required', + }); + } else { + const { UserModel } = DataBase.getInstance(); + UserModel.register( + new UserModel({ username: req.body.username }), + req.body.password, + (err, user) => { + if (err) { + res.statusCode = 500; + res.send(err); + } else { + user.firstName = req.body.firstName; + user.lastName = req.body.lastName || ''; + const token = getToken({ _id: user._id }); + const refreshToken = getRefreshToken({ _id: user._id }); + user.refreshToken.push({ refreshToken }); + user.save((err, user) => { + if (err) { + res.statusCode = 500; + res.send(err); + } else { + res.cookie('refreshToken', refreshToken, COOKIE_OPTIONS); + res.send({ success: true, token }); + } + }); + } + } + ); + } +}); + +router.post('/login', passport.authenticate('LocalStrategy'), (req, res, next) => { + const token = getToken({ _id: req.user._id }); + const refreshToken = getRefreshToken({ _id: req.user._id }); + const { UserModel } = DataBase.getInstance(); + UserModel.findById(req.user._id).then( + (user) => { + user.refreshToken.push({ refreshToken }); + user.save((err, user) => { + if (err) { + res.statusCode = 500; + res.send(err); + } else { + res.cookie('refreshToken', refreshToken, COOKIE_OPTIONS); + res.send({ success: true, token }); + } + }); + }, + (err) => next(err) + ); +}); + +router.post('/refreshToken', (req, res, next) => { + const { signedCookies = {} } = req; + const { refreshToken } = signedCookies; + + if (refreshToken) { + try { + const payload = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET); + const userId = payload._id; + const { UserModel } = DataBase.getInstance(); + UserModel.findOne({ _id: userId }).then( + (user) => { + if (user) { + // Find the refresh token against the user record in database + const tokenIndex = user.refreshToken.findIndex( + (item) => item.refreshToken === refreshToken + ); + + if (tokenIndex === -1) { + res.statusCode = 401; + res.send('Unauthorized'); + } else { + const token = getToken({ _id: userId }); + // If the refresh token exists, then create new one and replace it. + const newRefreshToken = getRefreshToken({ _id: userId }); + user.refreshToken[tokenIndex] = { refreshToken: newRefreshToken }; + user.save((err, user) => { + if (err) { + res.statusCode = 500; + res.send(err); + } else { + res.cookie('refreshToken', newRefreshToken, COOKIE_OPTIONS); + res.send({ success: true, token }); + } + }); + } + } else { + res.statusCode = 401; + res.send('Unauthorized'); + } + }, + (err) => next(err) + ); + } catch (err) { + res.statusCode = 401; + res.send('Unauthorized'); + } + } else { + res.statusCode = 401; + res.send('Unauthorized'); + } +}); + +router.get('/me', verifyUser, (req, res, next) => { + res.send(req.user); +}); + +router.get('/logout', verifyUser, (req, res, next) => { + const { signedCookies = {} } = req; + const { refreshToken } = signedCookies; + const { UserModel } = DataBase.getInstance(); + UserModel.findById(req.user._id).then( + (user) => { + const tokenIndex = user.refreshToken.findIndex( + (item) => item.refreshToken === refreshToken + ); + + if (tokenIndex !== -1) { + user.refreshToken.id(user.refreshToken[tokenIndex]._id).remove(); + } + + user.save((err, user) => { + if (err) { + res.statusCode = 500; + res.send(err); + } else { + res.clearCookie('refreshToken', COOKIE_OPTIONS); + res.send({ success: true }); + } + }); + }, + (err) => next(err) + ); +}); + +// router.post('/login', passport.authenticate('local', { +// successReturnToOrRedirect: '/', +// failureRedirect: '/login', +// failureMessage: true +// }), function(req, res) { +// console.log(`Logged in user: ${req.user}`) +// res.redirect('/dashboard'); +// }); +// +// // Route to Log out +// router.post('/logout', function(req, res) { +// req.logout(); +// res.redirect('/login'); +// }); + +module.exports = router; diff --git a/src/server/server.js b/src/server/server.js index 0b77281..c931e21 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -1,39 +1,81 @@ require('dotenv').config(); -const express = require('express'); +const express = require('express'); // server software const path = require('path'); const cookieParser = require('cookie-parser'); const logger = require('morgan'); const fileUpload = require('express-fileupload'); const cors = require('cors'); +// const session = require('express-session'); // session middleware +const passport = require('passport'); // authentication const { DataBase } = require('./database'); - -const port = process.env.PORT || 8080; +const { ConfigureJwtStrategy } = require('./strategies/JwtStrategy'); +const { ConfigureLocalStrategy } = require('./strategies/LocalStrategy'); +require('./authenticate'); const app = express(); -// allow other domains to access this server -app.use(cors()); +app.use(logger('dev')); + +console.log(`node env = ${process.env.NODE_ENV}`); +const dev = process.env.NODE_ENV !== 'production'; + +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +if (dev) { + app.use(cookieParser()); +} else { + app.use(cookieParser(process.env.COOKIE_SECRET)); +} + +// Add the client URL to the CORS policy +const whitelist = process.env.WHITELISTED_DOMAINS + ? process.env.WHITELISTED_DOMAINS.split(',') + : []; +const corsOptions = { + origin: function (origin, callback) { + if (!origin || whitelist.indexOf(origin) !== -1) { + callback(null, true); + } else { + callback(new Error('Not allowed by CORS')); + } + }, + credentials: true +}; +if (dev) { + app.use(cors()); +} else { + app.use(cors(corsOptions)); +} + app.use(fileUpload({ createParentPath: true })); -app.use(logger('dev')); -app.use(express.json()); -app.use(express.urlencoded({ extended: false })); -app.use(cookieParser()); app.use(express.static(path.resolve('dist'))); -const mongoRouter = require('./routes'); +const dataRouter = require('./routes/dbRoutes'); +const userRouter = require('./routes/userRoutes'); -app.use('/dbaccess', mongoRouter); +app.use('/dbaccess', dataRouter); +app.use('/users', userRouter); // TODO: proper error handlers app.get('*', (req, res) => res.sendFile(path.resolve('dist/index.html'))); +const port = process.env.PORT || 8080; + async function startServer() { const singleton = DataBase.getInstance(); await singleton.connect(); + + ConfigureJwtStrategy(); + ConfigureLocalStrategy(); + + // Configure More Middleware + app.use(passport.initialize()); + // app.use(passport.session()); + app.listen(port, () => console.log(`Listening on port ${port}`)); } diff --git a/src/server/strategies/JwtStrategy.js b/src/server/strategies/JwtStrategy.js new file mode 100644 index 0000000..87d8a02 --- /dev/null +++ b/src/server/strategies/JwtStrategy.js @@ -0,0 +1,35 @@ +const passport = require('passport'); +const { Strategy, ExtractJwt } = require('passport-jwt'); +const { DataBase } = require('../database'); + +function ConfigureJwtStrategy() { + const opts = {}; + opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); + opts.secretOrKey = process.env.JWT_SECRET; + + // Used by the authenticated requests to deserialize the user, + // i.e., to fetch user details from the JWT. + passport.use( + 'JwtStrategy', + new Strategy(opts, ((payload, done) => { + // Check against the DB only if necessary. + // This can be avoided if you don't want to fetch user details in each request. + DataBase.getInstance().UserModel.findOne({ _id: payload._id }, (err, user) => { + if (err) { + return done(err, false); + } + if (user) { + return done(null, user); + } + return done(null, false); + // or you could create a new account + }); + })) + ); +} + +// export default ConfigureJwtStrategy; + +module.exports = { + ConfigureJwtStrategy +}; diff --git a/src/server/strategies/LocalStrategy.js b/src/server/strategies/LocalStrategy.js new file mode 100644 index 0000000..641a7dd --- /dev/null +++ b/src/server/strategies/LocalStrategy.js @@ -0,0 +1,20 @@ +const passport = require('passport'); +const LocalStrategy = require('passport-local').Strategy; +const { DataBase } = require('../database'); + +function ConfigureLocalStrategy() { + // Called during login/sign up + passport.use( + 'LocalStrategy', + new LocalStrategy(DataBase.getInstance().UserModel.authenticate()) + ); + + // Called while after logging in / signing up to set user details in req.user + passport.serializeUser(DataBase.getInstance().UserModel.serializeUser()); +} + +// export default ConfigureLocalStrategy; + +module.exports = { + ConfigureLocalStrategy +}; diff --git a/src/server/user.js b/src/server/user.js deleted file mode 100644 index 57c8b2c..0000000 --- a/src/server/user.js +++ /dev/null @@ -1,18 +0,0 @@ -// dependencies -const mongoose = require('mongoose'); -const passportLocalMongoose = require('passport-local-mongoose'); - -// Create Model -const { Schema } = mongoose; -const User = new Schema({ - // username: { type: String, required: true }, - // password: { type: String, required: true } - username: String, - password: String -}); -// Export Model -User.plugin(passportLocalMongoose); - -module.exports = { - User -}; From 62fdc9f0e8ec750b105a5fdf8c1f2b243ef6701d Mon Sep 17 00:00:00 2001 From: Martin Shetty <1972005+martukas@users.noreply.github.com> Date: Mon, 30 May 2022 03:45:37 -0700 Subject: [PATCH 3/3] "reimplemented auth using bootstrap components; updates #3" --- package.json | 1 - src/client/pages/dataset.jsx | 2 +- src/client/pages/login.jsx | 102 ++++++++++++++++++++++------------ src/client/pages/register.jsx | 76 +++++++++++++------------ 4 files changed, 110 insertions(+), 71 deletions(-) diff --git a/package.json b/package.json index 6b23bdb..a1c37e5 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "license": "Apache2", "dependencies": { "axios": "^0.26.1", - "@blueprintjs/core": "^4.3.0", "body-parser": "^1.20.0", "bootstrap": "^5.1.3", "connect-ensure-login": "^0.1.1", diff --git a/src/client/pages/dataset.jsx b/src/client/pages/dataset.jsx index a5e7ac3..a9decaa 100644 --- a/src/client/pages/dataset.jsx +++ b/src/client/pages/dataset.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Button, Col, Container, Row, Spinner, Stack, Table, -} from 'react-bootstrap' +} from 'react-bootstrap'; import dateFormat from 'dateformat'; import { saveAs } from 'file-saver'; import { downloadFile } from '../api'; diff --git a/src/client/pages/login.jsx b/src/client/pages/login.jsx index 4b0df02..5c415a7 100644 --- a/src/client/pages/login.jsx +++ b/src/client/pages/login.jsx @@ -1,7 +1,8 @@ import { - Button, Callout, FormGroup, InputGroup -} from '@blueprintjs/core'; + Alert, Button, Container, Form, +} from 'react-bootstrap'; import React, { useContext, useState } from 'react'; +// import axios from 'axios'; import { UserContext } from '../context/UserContext'; function Login() { @@ -11,18 +12,51 @@ function Login() { const [error, setError] = useState(''); const [userContext, setUserContext] = useContext(UserContext); - const formSubmitHandler = (e) => { - e.preventDefault(); + function validateForm() { + return email.length > 0 && password.length > 0; + } + + function handleSubmit(event) { + event.preventDefault(); setIsSubmitting(true); setError(''); + const route = '/users/login'; + const data = { username: email, password }; const genericErrorMessage = 'Something went wrong! Please try again later.'; - fetch('/users/login', { + // axios.post(route, data, { + // headers: { + // Accept: { 'Content-Type': 'application/json' }, + // }, + // withCredentials: true + // }).then((response) => { + // setIsSubmitting(false); + // const responseOK = response && response.status === 200 && response.statusText === 'OK'; + // if (!responseOK) { + // if (response.status === 400) { + // setError('Please fill all the fields correctly!'); + // } else if (response.status === 401) { + // setError('Invalid email and password combination.'); + // } else { + // setError(genericErrorMessage); + // } + // } else { + // const responseData = response.data; + // setUserContext((oldValues) => ({ ...oldValues, token: responseData.token })); + // } + // }).catch((caughtError) => { + // setIsSubmitting(false); + // console.log('Axios error caught'); + // console.log(caughtError.toJSON()); + // setError(caughtError); + // }); + + fetch(route, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username: email, password }), + body: JSON.stringify(data), }) .then(async (response) => { setIsSubmitting(false); @@ -35,48 +69,48 @@ function Login() { setError(genericErrorMessage); } } else { - const data = await response.json(); - setUserContext((oldValues) => ({ ...oldValues, token: data.token })); - console.log(`received context.token: ${userContext}`); + const responseData = await response.json(); + setUserContext((oldValues) => ({ ...oldValues, token: responseData.token })); } }) - .catch((error) => { + .catch((caughtError) => { setIsSubmitting(false); - setError(genericErrorMessage); + setError(caughtError); }); - }; + } return ( -
- {error && {error}} -
- - + {error + && ( + + Oh snap! You got an error! +

{error}

+
+ )} + + + Email + setEmail(e.target.value)} /> -
- - + + Password + setPassword(e.target.value)} /> - -
+ + + + ); } diff --git a/src/client/pages/register.jsx b/src/client/pages/register.jsx index 3462b96..5071bec 100644 --- a/src/client/pages/register.jsx +++ b/src/client/pages/register.jsx @@ -1,6 +1,6 @@ import { - Button, Callout, FormGroup, InputGroup -} from '@blueprintjs/core'; + Alert, Button, Container, Form, +} from 'react-bootstrap'; import React, { useContext, useState } from 'react'; import { UserContext } from '../context/UserContext'; @@ -13,8 +13,12 @@ function Register() { const [error, setError] = useState(''); const [userContext, setUserContext] = useContext(UserContext); - const formSubmitHandler = (e) => { - e.preventDefault(); + function validateForm() { + return email.length > 0 && password.length > 0; + } + + function handleSubmit(event) { + event.preventDefault(); setIsSubmitting(true); setError(''); @@ -47,59 +51,61 @@ function Register() { setUserContext((oldValues) => ({ ...oldValues, token: data.token })); } }) - .catch((error) => { + .catch((caughtError) => { setIsSubmitting(false); - setError(genericErrorMessage); + setError(caughtError); }); - }; + } return ( -
- {error && {error}} -
- - + {error + && ( + + Oh snap! You got an error! +

{error}

+
+ )} + + + First Name + setFirstName(e.target.value)} value={firstName} /> -
- - + + Last Name + setLastName(e.target.value)} value={lastName} /> - - - + + Email + setEmail(e.target.value)} value={email} /> - - - + + Password + setPassword(e.target.value)} value={password} /> - -
+ + + + ); }