From 0009ca092a7427989c851fa6d1f8adefea4f500b Mon Sep 17 00:00:00 2001 From: LouieK22 Date: Mon, 11 Feb 2019 21:08:27 -0600 Subject: [PATCH] Initial Commit --- .editorconfig | 16 + .gitignore | 16 + .vscode/launch.json | 18 + .vscode/tasks.json | 28 + LICENSE | 21 + package-lock.json | 1154 ++++++++++++++++++++++++++ package.json | 47 ++ src/app.ts | 60 ++ src/config/app.example.ini | 14 + src/middleware/authentication.ts | 49 ++ src/middleware/requireAuth.ts | 19 + src/models/device.ts | 21 + src/routes/api.ts | 13 + src/routes/api/device.ts | 60 ++ src/routes/api/login.ts | 28 + src/routes/api/logout.ts | 17 + src/routes/dashboard.ts | 22 + src/routes/dashboard/home.ts | 16 + src/routes/dashboard/login.ts | 11 + src/server.ts | 9 + src/types/csprng.d.ts | 5 + src/types/express.d.ts | 11 + src/util/asyncWrapper.ts | 9 + src/util/config.ts | 30 + src/util/db.ts | 16 + static/img/favicon.ico | Bin 0 -> 101307 bytes static/img/first-logo-horizontal.png | Bin 0 -> 105383 bytes static/js/home.js | 18 + static/js/login.js | 22 + tsconfig.json | 21 + tslint.json | 53 ++ views/pages/404.ejs | 18 + views/pages/home.ejs | 33 + views/pages/login.ejs | 50 ++ views/partials/head.ejs | 8 + 35 files changed, 1933 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 LICENSE create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/app.ts create mode 100644 src/config/app.example.ini create mode 100644 src/middleware/authentication.ts create mode 100644 src/middleware/requireAuth.ts create mode 100644 src/models/device.ts create mode 100644 src/routes/api.ts create mode 100644 src/routes/api/device.ts create mode 100644 src/routes/api/login.ts create mode 100644 src/routes/api/logout.ts create mode 100644 src/routes/dashboard.ts create mode 100644 src/routes/dashboard/home.ts create mode 100644 src/routes/dashboard/login.ts create mode 100644 src/server.ts create mode 100644 src/types/csprng.d.ts create mode 100644 src/types/express.d.ts create mode 100644 src/util/asyncWrapper.ts create mode 100644 src/util/config.ts create mode 100644 src/util/db.ts create mode 100644 static/img/favicon.ico create mode 100644 static/img/first-logo-horizontal.png create mode 100644 static/js/home.js create mode 100644 static/js/login.js create mode 100644 tsconfig.json create mode 100644 tslint.json create mode 100644 views/pages/404.ejs create mode 100644 views/pages/home.ejs create mode 100644 views/pages/login.ejs create mode 100644 views/partials/head.ejs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ef5e4de --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.ts] +indent_style = tab +indent_size = 2 +tab_width = 2 + +[{package.json,*.yml,*.cjson}] +indent_style = tab +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bdac3a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Dependency directories +node_modules/ + +# Compiled Typescript files +dist/ + +# Config files +src/config/*.ini +!src/config/*.example.ini diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9ab90f5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceFolder}\\dist\\server.js", + "outFiles": [ + "${workspaceFolder}/**/*.js" + ], + "console": "integratedTerminal" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..f00fb93 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "type": "typescript", + "tsconfig": "tsconfig.json", + "problemMatcher": [ + "$tsc" + ], + "label": "build" + }, + { + "type": "typescript", + "tsconfig": "tsconfig.json", + "option": "watch", + "problemMatcher": [ + "$tsc-watch" + ], + "label": "watch", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b02e62d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 FRC-3313-Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e0bb1eb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1154 @@ +{ + "name": "scouting-back", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/body-parser": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", + "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bson": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-1.0.11.tgz", + "integrity": "sha512-j+UcCWI+FsbI5/FQP/Kj2CXyplWAz39ktHFkXk84h7dNblKRSoNJs95PZFRd96NQGqsPEPgeclqnznWZr14ZDA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.32", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", + "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/cookie-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.1.tgz", + "integrity": "sha512-iJY6B3ZGufLiDf2OCAgiAAQuj1sMKC/wz/7XCEjZ+/MDuultfFJuSwrBKcLSmJ5iYApLzCCYBYJZs0Ws8GPmwA==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/ejs": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-2.6.1.tgz", + "integrity": "sha512-WD8015V8Y+uDfFIjiVHU7t9SgHptqTGGN8w0A2LcrL0NLtqColM15cswkHZMUfodyuTf35sup8vW0hpWRHu0dQ==", + "dev": true + }, + "@types/events": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", + "dev": true + }, + "@types/express": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.0.tgz", + "integrity": "sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz", + "integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/node": "*", + "@types/range-parser": "*" + } + }, + "@types/ini": { + "version": "1.3.30", + "resolved": "https://registry.npmjs.org/@types/ini/-/ini-1.3.30.tgz", + "integrity": "sha512-2+iF8zPSbpU83UKE+PNd4r/MhwNAdyGpk3H+VMgEH3EhjFZq1kouLgRoZrmIcmoGX97xFvqdS44DkICR5Nz3tQ==", + "dev": true + }, + "@types/jsonwebtoken": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", + "integrity": "sha512-YKnUTR4VxwljbPORPrRon9E3uel1aD8nUdvzqArCCdMTWPvo0gnI2UZkwIHN2QATdj6HYXV/Iq3/KcecAO42Ww==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/mime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz", + "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==", + "dev": true + }, + "@types/mongodb": { + "version": "3.1.15", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.1.15.tgz", + "integrity": "sha512-JSvtmrdrh88WH0Lo8Hq7sB1FkEChkrt6+fAZdFhEsRXcUetnrdU7wd2yar40tPg5wfRI2t31yduQgPiMUvgEEA==", + "dev": true, + "requires": { + "@types/bson": "*", + "@types/node": "*" + } + }, + "@types/mongoose": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.3.4.tgz", + "integrity": "sha512-VHP6aUswXIaFlLfF+noeMYf4NdkgylGlrAujr5g6aFcX1fYt3PpBsI+LGFnuK11HL7XPYQR3IigiqDq1rnBjeg==", + "dev": true, + "requires": { + "@types/mongodb": "*", + "@types/node": "*" + } + }, + "@types/morgan": { + "version": "1.7.35", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.7.35.tgz", + "integrity": "sha512-E9qFi0seOkdlQnCTPv54brNfGWeFdRaEhI5tSue4pdx/V+xfxvMETsxXhOEcj1cYL+0n/jcTEmj/jD2gjzCwMg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/node": { + "version": "10.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.12.tgz", + "integrity": "sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "@types/uuid": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.4.tgz", + "integrity": "sha512-tPIgT0GUmdJQNSHxp0X2jnpQfBSTfGxUMc/2CXBU2mnyTFVYVa2ojpoQ74w0U2yn2vw3jnC640+77lkFFpdVDw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "requires": { + "lodash": "^4.17.10" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + } + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "bson": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.0.tgz", + "integrity": "sha512-9Aeai9TacfNtWXOYarkFJRW2CWo+dRon+fuLZYJmvLV3+MiUp0bEI6IAZfXEIg7/Pl/7IWlLaDnhzTsD81etQA==" + }, + "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=" + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6" + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "csprng": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/csprng/-/csprng-0.1.2.tgz", + "integrity": "sha1-S8aPEvo2jSUqWYQcusqXSxirReI=", + "requires": { + "sequin": "*" + } + }, + "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" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "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", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "ejs": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", + "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsonwebtoken": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz", + "integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==", + "requires": { + "jws": "^3.1.5", + "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" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "jwa": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.10", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", + "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "requires": { + "jwa": "^1.1.5", + "safe-buffer": "^5.0.1" + } + }, + "kareem": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.0.tgz", + "integrity": "sha512-6hHxsp9e6zQU8nXsP+02HGWXwTkOEw6IROhF2ZA28cYbUk4eJ6QbtZvdqZOdD9YPKghG3apk5eOCvs+tLl3lRg==" + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "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.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memory-pager": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.1.0.tgz", + "integrity": "sha512-Mf9OHV/Y7h6YWDxTzX/b4ZZ4oh9NSXblQL8dtPCOomOtZciEHxePR78+uHFLLlsk01A6jVHhHsQZZ/WcIPpnzg==", + "optional": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "requires": { + "mime-db": "~1.37.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mongodb": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.1.10.tgz", + "integrity": "sha512-Uml42GeFxhTGQVml1XQ4cD0o/rp7J2ROy0fdYUcVitoE7vFqEhKH4TYVqRDpQr/bXtCJVxJdNQC1ntRxNREkPQ==", + "requires": { + "mongodb-core": "3.1.9", + "safe-buffer": "^5.1.2" + } + }, + "mongodb-core": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.1.9.tgz", + "integrity": "sha512-MJpciDABXMchrZphh3vMcqu8hkNf/Mi+Gk6btOimVg1XMxLXh87j6FAvRm+KmwD1A9fpu3qRQYcbQe4egj23og==", + "requires": { + "bson": "^1.1.0", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, + "mongoose": { + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.3.15.tgz", + "integrity": "sha512-51PchMQn2kFZYPSe0vfr8RcJxm6g7RokfcVl2xVORogtT5GUKu6RgnRXsUV7v9vZ+b3Wh//ph53kuIyhtXi56g==", + "requires": { + "async": "2.6.1", + "bson": "~1.1.0", + "kareem": "2.3.0", + "lodash.get": "4.4.2", + "mongodb": "3.1.10", + "mongodb-core": "3.1.9", + "mongoose-legacy-pluralize": "1.0.2", + "mpath": "0.5.1", + "mquery": "3.2.0", + "ms": "2.0.0", + "regexp-clone": "0.0.1", + "safe-buffer": "5.1.2", + "sliced": "1.0.1" + } + }, + "mongoose-legacy-pluralize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", + "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" + }, + "morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "requires": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + } + }, + "mpath": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.5.1.tgz", + "integrity": "sha512-H8OVQ+QEz82sch4wbODFOz+3YQ61FYz/z3eJ5pIdbMEaUzDqA268Wd+Vt4Paw9TJfvDgVKaayC0gBzMIw2jhsg==" + }, + "mquery": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.0.tgz", + "integrity": "sha512-qPJcdK/yqcbQiKoemAt62Y0BAc0fTEKo1IThodBD+O5meQRJT/2HSe5QpBNwaa4CjskoGrYWsEyjkqgiE0qjhg==", + "requires": { + "bluebird": "3.5.1", + "debug": "3.1.0", + "regexp-clone": "0.0.1", + "safe-buffer": "5.1.2", + "sliced": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "regexp-clone": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-0.0.1.tgz", + "integrity": "sha1-p8LgmJH9vzj7sQ03b7cwA+aKxYk=" + }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + } + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saslprep": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.2.tgz", + "integrity": "sha512-4cDsYuAjXssUSjxHKRe4DTZC0agDwsCqcMqtJAQPzC74nJ7LfAJflAtC1Zed5hMzEQKj82d3tuzqdGNRsLJ4Gw==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "sequin": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/sequin/-/sequin-0.1.1.tgz", + "integrity": "sha1-XC04nWajg3NOqvvEXt6ywcsb5wE=" + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "sliced": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", + "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" + }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", + "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.27.2" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c44dee2 --- /dev/null +++ b/package.json @@ -0,0 +1,47 @@ +{ + "name": "scouting-back", + "version": "0.1.0", + "description": "", + "main": "./src/app.js", + "scripts": { + "build": "tsc", + "dev": "ts-node ./src/server.ts", + "start": "node ./dist/server.js", + "prod": "npm run build && npm start", + "lint": "tslint -p tsconfig.json" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/FRC-3313-Team/scouting-back.git" + }, + "author": "FRC Team 3313", + "license": "MIT", + "bugs": { + "url": "https://github.com/FRC-3313-Team/scouting-back/issues" + }, + "homepage": "https://github.com/FRC-3313-Team/scouting-back#readme", + "dependencies": { + "body-parser": "^1.18.3", + "cookie-parser": "^1.4.3", + "csprng": "^0.1.2", + "ejs": "^2.6.1", + "express": "^4.16.4", + "ini": "^1.3.5", + "jsonwebtoken": "^8.4.0", + "mongoose": "^5.3.15", + "morgan": "^1.9.1", + "uuid": "^3.3.2" + }, + "devDependencies": { + "@types/body-parser": "^1.17.0", + "@types/cookie-parser": "^1.4.1", + "@types/ejs": "^2.6.1", + "@types/express": "^4.16.0", + "@types/ini": "^1.3.30", + "@types/jsonwebtoken": "^8.3.0", + "@types/mongoose": "^5.3.4", + "@types/morgan": "^1.7.35", + "@types/uuid": "^3.4.4", + "tslint": "^5.11.0" + } +} \ No newline at end of file diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..f07c5fd --- /dev/null +++ b/src/app.ts @@ -0,0 +1,60 @@ +import * as path from "path"; + +import * as express from "express"; +import * as bodyParser from "body-parser"; +import * as cookieParser from "cookie-parser"; +import * as morgan from "morgan"; + +import { authentication } from "./middleware/authentication"; + +import api from "./routes/api"; +import dashboard from "./routes/dashboard"; + +class App { + public app: express.Application; + + constructor() { + this.app = express(); + + this.config(); + this.middleware(); + this.routes(); + } + + private config(): void { + this.app.set("view engine", "ejs"); + this.app.set("views", path.join(__dirname, "../views")); + } + + private middleware(): void { + this.app.use(morgan("dev")); + this.app.use(bodyParser.json()); + this.app.use(bodyParser.urlencoded({ extended: false })); + this.app.use(cookieParser()); + + this.app.use(authentication); + } + + private routes(): void { + this.app.use(express.static("static")); + this.app.use("/favicon.ico", express.static("static/img/favicon.ico")); + + this.app.use("/dash", dashboard); + this.app.use("/api", api); + + this.app.use("/", (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (req.url === "/") { + res.redirect("/dash"); + } else { + next(); + } + }); + + this.app.use("*", (req: express.Request, res: express.Response) => { + res.status(404) + .render("pages/404"); + }); + } +} + +export default new App().app; diff --git a/src/config/app.example.ini b/src/config/app.example.ini new file mode 100644 index 0000000..2dfa997 --- /dev/null +++ b/src/config/app.example.ini @@ -0,0 +1,14 @@ +[server] +port=3000 + +[database] +url=mongodb://localhost:27017/ ; URL for connecting to MongoDB server +mainDatabase=dev-frc ; Name of database used for storing all event-agnostic data, e.g. devices +eventDatabase=dev-frc- ; Name of database used for storing event-specific data, the event code (e.g. 2018ndgf) is appended to this + +[device] +activationCodeLength=6 ; Length of activation codes for new devices + +[dashboard] +password=password ; Password for the web dashboard +jwtSigningKey=Please Change This In Production ; Key used for sign JWTs for dashboard logins diff --git a/src/middleware/authentication.ts b/src/middleware/authentication.ts new file mode 100644 index 0000000..9c6a10c --- /dev/null +++ b/src/middleware/authentication.ts @@ -0,0 +1,49 @@ +import * as express from "express"; + +import config from "../util/config"; +import asyncWrapper from "../util/asyncWrapper"; +import * as jwt from "jsonwebtoken"; + +import { Device } from "../models/device"; + +export enum AuthorizationType { + Dashboard, + Device, + None, +} + +export const authentication = asyncWrapper(async (req: express.Request, res: express.Response, next: express.NextFunction) => { + req.user = { + authorizationType: AuthorizationType.None, + }; + + // Device authentication + const deviceToken = req.headers["device-token"] as string; + + if (deviceToken) { + const device = await Device.findOne({ token: deviceToken }); + + if (device && device.active) { + req.user.authorizationType = AuthorizationType.Device; + return next(); + } + } + + // Dashboard authentication + const dashboardToken = req.cookies["dashboard-token"] as string; + + if (dashboardToken) { + jwt.verify(dashboardToken, config.dashboard.jwtSigningKey, (err, decoded) => { + if (err) { + return next(); + } + + // @ts-ignore: this is 100% fine, I promise + req.user = decoded; + + return next(); + }); + } else { + return next(); + } +}); diff --git a/src/middleware/requireAuth.ts b/src/middleware/requireAuth.ts new file mode 100644 index 0000000..15d58d9 --- /dev/null +++ b/src/middleware/requireAuth.ts @@ -0,0 +1,19 @@ +import * as express from "express"; + +import { AuthorizationType } from "./authentication"; +import asyncWrapper from "../util/asyncWrapper"; + +const requireAuth = +(authorizationType: AuthorizationType): (req: express.Request, res: express.Response, next: express.NextFunction) => void => { + return asyncWrapper(async (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (req.user.authorizationType === authorizationType) { + return next(); + } else { + res.status(403) + .contentType("text/plain") + .send(`${AuthorizationType[authorizationType]} authorization required to access this endpoint`); + } + }); +}; + +export default requireAuth; diff --git a/src/models/device.ts b/src/models/device.ts new file mode 100644 index 0000000..6147645 --- /dev/null +++ b/src/models/device.ts @@ -0,0 +1,21 @@ +import { Document, Schema, Model, model } from "mongoose"; + +import { connections } from "../util/db"; + +interface IDevice { + active: boolean; + name: string; + activationCode?: string; + token?: string; +} + +interface IDeviceModel extends IDevice, Document { } + +export const DeviceSchema: Schema = new Schema({ + active: Boolean, + name: String, + activationCode: String, + token: String, +}); + +export const Device: Model = connections.main.model("Device", DeviceSchema); diff --git a/src/routes/api.ts b/src/routes/api.ts new file mode 100644 index 0000000..6d39474 --- /dev/null +++ b/src/routes/api.ts @@ -0,0 +1,13 @@ +import * as express from "express"; + +import device from "./api/device"; +import login from "./api/login"; +import logout from "./api/logout"; + +const router = express.Router(); + +router.use("/device", device); +router.use("/login", login); +router.use("/logout", logout); + +export default router; diff --git a/src/routes/api/device.ts b/src/routes/api/device.ts new file mode 100644 index 0000000..8095cfd --- /dev/null +++ b/src/routes/api/device.ts @@ -0,0 +1,60 @@ +import * as express from "express"; +import * as rand from "csprng"; +import * as uuid from "uuid"; + +import config from "../../util/config"; +import asyncWrapper from "../../util/asyncWrapper"; +import requireAuth from "../../middleware/requireAuth"; + +import { Device } from "../../models/device"; +import { AuthorizationType } from "../../middleware/authentication"; + +const router = express.Router(); + +router.post("/new", + requireAuth(AuthorizationType.Dashboard), + asyncWrapper(async (req: express.Request, res: express.Response, next: express.NextFunction) => { + const deviceName = req.body.name ? req.body.name : rand(12, 36); + + const checkForExistingName = await Device.findOne({ name: deviceName }); + if (checkForExistingName) { + return res.status(409) + .contentType("text/plain") + .send("a device with this name already exists"); + } + + let newDevice = new Device({ + active: false, + name: deviceName, + activationCode: rand(config.device.activationCodeLength * 3, 10), + }); + + newDevice = await newDevice.save(); + + res.status(200).send({ + code: newDevice.activationCode, + name: newDevice.name, + }); +})); + +router.post("/register", asyncWrapper(async (req: express.Request, res: express.Response, next: express.NextFunction) => { + let device = await Device.findOne({ activationCode: req.body.activationCode }); + + if (device === null) { + return res.status(400) + .contentType("text/plain") + .send("invalid code"); + } + + device.activationCode = undefined; + device.active = true; + device.token = uuid.v4(); + + device = await device.save(); + + res.status(200).send({ + token: device.token, + }); +})); + +export default router; diff --git a/src/routes/api/login.ts b/src/routes/api/login.ts new file mode 100644 index 0000000..9f12677 --- /dev/null +++ b/src/routes/api/login.ts @@ -0,0 +1,28 @@ +import * as express from "express"; +import * as jwt from "jsonwebtoken"; + +import config from "../../util/config"; +import asyncWrapper from "../../util/asyncWrapper"; +import { AuthorizationType } from "../../middleware/authentication"; + +const router = express.Router(); + +router.post("/", asyncWrapper(async (req: express.Request, res: express.Response, next: express.NextFunction) => { + const password = req.body.password; + + if (password === config.dashboard.password) { + const payload = { + authorizationType: AuthorizationType.Dashboard, + }; + const signedToken = jwt.sign(payload, config.dashboard.jwtSigningKey, { expiresIn: "1h" }); + + res.status(200) + .cookie("dashboard-token", signedToken) + .send(); + } else { + res.status(403) + .send(); + } +})); + +export default router; diff --git a/src/routes/api/logout.ts b/src/routes/api/logout.ts new file mode 100644 index 0000000..e3811d4 --- /dev/null +++ b/src/routes/api/logout.ts @@ -0,0 +1,17 @@ +import * as express from "express"; + +import asyncWrapper from "../../util/asyncWrapper"; +import requireAuth from "../../middleware/requireAuth"; + +import { AuthorizationType } from "../../middleware/authentication"; + +const router = express.Router(); + +router.post("/", + requireAuth(AuthorizationType.Dashboard), + asyncWrapper(async (req: express.Request, res: express.Response, next: express.NextFunction) => { + res.clearCookie("dashboard-token") + .send(); +})); + +export default router; diff --git a/src/routes/dashboard.ts b/src/routes/dashboard.ts new file mode 100644 index 0000000..33ad12b --- /dev/null +++ b/src/routes/dashboard.ts @@ -0,0 +1,22 @@ +import * as express from "express"; + +import { AuthorizationType } from "../middleware/authentication"; +import asyncWrapper from "../util/asyncWrapper"; + +import login from "./dashboard/login"; +import home from "./dashboard/home"; + +const router = express.Router(); + +router.use("/login", login); +router.use("/home", home); + +router.get("/", asyncWrapper(async (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (req.user.authorizationType === AuthorizationType.Dashboard) { + res.redirect("/dash/home"); + } else { + res.redirect("/dash/login"); + } +})); + +export default router; diff --git a/src/routes/dashboard/home.ts b/src/routes/dashboard/home.ts new file mode 100644 index 0000000..a327740 --- /dev/null +++ b/src/routes/dashboard/home.ts @@ -0,0 +1,16 @@ +import * as express from "express"; + +import asyncWrapper from "../../util/asyncWrapper"; +import { AuthorizationType } from "../../middleware/authentication"; + +const router = express.Router(); + +router.get("/", asyncWrapper(async (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (req.user.authorizationType !== AuthorizationType.Dashboard) { + return res.redirect("/dash/login"); + } + + res.render("pages/home"); +})); + +export default router; diff --git a/src/routes/dashboard/login.ts b/src/routes/dashboard/login.ts new file mode 100644 index 0000000..039007e --- /dev/null +++ b/src/routes/dashboard/login.ts @@ -0,0 +1,11 @@ +import * as express from "express"; + +import asyncWrapper from "../../util/asyncWrapper"; + +const router = express.Router(); + +router.get("/", asyncWrapper(async (req: express.Request, res: express.Response, next: express.NextFunction) => { + res.render("pages/login"); +})); + +export default router; diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..3e39f5b --- /dev/null +++ b/src/server.ts @@ -0,0 +1,9 @@ +import config from "./util/config"; + +import app from "./app"; + +const PORT = config.server.port; + +app.listen(PORT, () => { + console.log(`Server listening on port: ${PORT}`); +}); diff --git a/src/types/csprng.d.ts b/src/types/csprng.d.ts new file mode 100644 index 0000000..105e10d --- /dev/null +++ b/src/types/csprng.d.ts @@ -0,0 +1,5 @@ +declare module "csprng" { + function csprng(bit: number, radix: number): string; + namespace csprng { } + export = csprng; +} diff --git a/src/types/express.d.ts b/src/types/express.d.ts new file mode 100644 index 0000000..3e60b1c --- /dev/null +++ b/src/types/express.d.ts @@ -0,0 +1,11 @@ +import { AuthorizationType } from "../middleware/authentication"; + +declare global { + namespace Express { + export interface Request { + user: { + authorizationType: AuthorizationType + } + } + } +} diff --git a/src/util/asyncWrapper.ts b/src/util/asyncWrapper.ts new file mode 100644 index 0000000..433eccf --- /dev/null +++ b/src/util/asyncWrapper.ts @@ -0,0 +1,9 @@ +import * as express from "express"; + +const asyncWrapper = (fn: any) => + (req: express.Request, res: express.Response, next: express.NextFunction) => { + Promise.resolve(fn(req, res, next)) + .catch(next); +}; + +export default asyncWrapper; diff --git a/src/util/config.ts b/src/util/config.ts new file mode 100644 index 0000000..1e51b35 --- /dev/null +++ b/src/util/config.ts @@ -0,0 +1,30 @@ +import * as ini from "ini"; +import * as fs from "fs"; + +interface IConfig { + server: { + port: number, + }; + + database: { + url: string, + mainDatabase: string, + eventDatabase: string, + }; + + device: { + activationCodeLength: number, + }; + + dashboard: { + password: string, + jwtSigningKey: string, + }; +} + +const appConfig = ini.parse(fs.readFileSync("./src/config/app.ini", "utf-8")) as IConfig; +const fallbackConfig = ini.parse(fs.readFileSync("./src/config/app.example.ini", "utf-8")) as IConfig; + +const mergedConfig: IConfig = { ...fallbackConfig, ...appConfig }; + +export default mergedConfig; diff --git a/src/util/db.ts b/src/util/db.ts new file mode 100644 index 0000000..bf6a22a --- /dev/null +++ b/src/util/db.ts @@ -0,0 +1,16 @@ +import * as mongoose from "mongoose"; + +import config from "./config"; + +interface IConnections { + [propName: string]: mongoose.Connection; +} + +function createConnection(dbName: string): mongoose.Connection { + return mongoose.createConnection(`${config.database.url}${dbName}`, { useNewUrlParser: true }); +} + +export const connections: IConnections = { + main: createConnection(config.database.mainDatabase), + // event: createConnection(config.database.eventDatabase), TODO: Connect to correct event database given a TBA event key +}; diff --git a/static/img/favicon.ico b/static/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..bb7c3bd09ae044058986db3532173bc75a40ba1f GIT binary patch literal 101307 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 literal 0 HcmV?d00001 diff --git a/static/img/first-logo-horizontal.png b/static/img/first-logo-horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..c65d9d750f5112c42cdcebb9600da266a804212f GIT binary patch literal 105383 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 { + window.location.href = "/dash/login" + }) + .catch(err => { + console.error(err) + }); + } + } +}); diff --git a/static/js/login.js b/static/js/login.js new file mode 100644 index 0000000..6343d06 --- /dev/null +++ b/static/js/login.js @@ -0,0 +1,22 @@ +new Vue({ + el: '#app', + data: { + password: '', + hasError: false, + }, + methods: { + login: function(event){ + axios.post("../../api/login", { + password: this.password, + }) + .then(res => { + this.hasError = false; + + window.location.href = "/dash/home" + }) + .catch(err => { + this.hasError = true; + }); + } + } +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..05ac5be --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "lib": ["es2018"], + "module": "commonjs", + "moduleResolution": "node", + "pretty": true, + "sourceMap": true, + "target": "es2018", + "outDir": "./dist", + "baseUrl": "./src", + "strict": true, + "strictNullChecks": true + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..bc1105f --- /dev/null +++ b/tslint.json @@ -0,0 +1,53 @@ +{ + "extends": "tslint:recommended", + "rules": { + "array-type": [true, "generic"], + "arrow-parens": false, + "class-name": true, + "curly": true, + "forin": false, + "indent": [true, "tabs", 4], + "no-namespace": false, + "max-classes-per-file": false, + "max-line-length": [true, 135], + "no-bitwise": false, + "no-console": false, + "no-empty-interface": false, + "no-empty": false, + "no-var-requires": true, + "no-require-imports": true, + "no-string-literal": true, + "no-unnecessary-qualifier": true, + "no-unused-variable": true, + "no-var-keyword": true, + "ordered-imports": false, + "object-literal-sort-keys": false, + "prefer-const": true, + "quotemark": [true, "double", "avoid-escape" ], + "semicolon": [true, "always", "ignore-bound-class-methods"], + "triple-equals": true, + "member-ordering": [true, { + "order": [ + "private-static-field", + "protected-static-field", + "public-static-field", + + "private-instance-field", + "protected-instance-field", + "public-instance-field", + + "private-constructor", + "protected-constructor", + "public-constructor", + + "private-static-method", + "protected-static-method", + "public-static-method", + + "private-instance-method", + "protected-instance-method", + "public-instance-method" + ] + }] + } +} diff --git a/views/pages/404.ejs b/views/pages/404.ejs new file mode 100644 index 0000000..ee14043 --- /dev/null +++ b/views/pages/404.ejs @@ -0,0 +1,18 @@ + + + + <% include ../partials/head %> + + FRC Scout | 404 + + +
+
+
+

Error: 404

+

We couldn't find this page

+
+
+
+ + diff --git a/views/pages/home.ejs b/views/pages/home.ejs new file mode 100644 index 0000000..0260a74 --- /dev/null +++ b/views/pages/home.ejs @@ -0,0 +1,33 @@ + + + + <% include ../partials/head %> + + + + FRC Scout | Dashboard + + +
+ +
+ + diff --git a/views/pages/login.ejs b/views/pages/login.ejs new file mode 100644 index 0000000..111273a --- /dev/null +++ b/views/pages/login.ejs @@ -0,0 +1,50 @@ + + + + <% include ../partials/head %> + + + + FRC Scout | Login + + +
+
+
+

Login

+

FRC Scout 2019

+
+ + +
+
+
+
+ +
+ +

Incorrect password

+
+ +
+
+ +
+
+
+
+
+
+
+ + diff --git a/views/partials/head.ejs b/views/partials/head.ejs new file mode 100644 index 0000000..65fc798 --- /dev/null +++ b/views/partials/head.ejs @@ -0,0 +1,8 @@ + + + + + + + +