From 4da4c4b81d469105287094de2030842e9555d915 Mon Sep 17 00:00:00 2001 From: JonathanLeeWH Date: Mon, 9 Nov 2020 23:07:34 +0800 Subject: [PATCH 01/11] Reimplemented existing source code for firebase Auth migration Removed previous implementation of custom JWT Authentication All existing controllers such as softwareController and usersController are reimplemented with firebase Authentication implementation Added disabledAPIEndPoint middleware to allow for disabling of API endpoints Disabled /api/auth API endpoint (Possible removing of auth api endpoint in future commits) with the new disabledAPIEndPoint middleware Disabled auth.test.js test suite and moved to disabledAPINotTested directory (with jest.config.js configuration) Refactored API endpoint from /api/softwareds/ to /api/software/ for consistency Amended software model to change platforms field from array to string (Plan for project page in future to support software with multiple platforms) Amended user model's passwordHash field to password field for consistency (It should still store password hash) Refactored all existing requests rest API test cases with the new software API endpoints route amendments, changes to request body and Authorization header Reimplemented all test cases for software.test.js and users.test.js (including their test utilities) with firebase Authentication integration Note: Some test cases are removed from previous test suites as it is handled by firebase Authentication such as email validation. Added firebaseConfig.js firebaseTestUtils.js Removed unused environmental variables from config.js --- app.js | 8 +- controllers/api/authController.js | 7 +- ...resController.js => softwareController.js} | 22 +- controllers/api/usersController.js | 31 +- jest.config.js | 1 + models/software.js | 12 +- models/user.js | 11 +- package-lock.json | 1051 +++++++++++++++-- package.json | 8 +- ...oftwares.rest => delete_softwareById.rest} | 2 +- requests/api/softwares/get_software.rest | 2 +- requests/api/softwares/get_softwareById.rest | 1 + requests/api/softwares/get_softwares.rest | 1 - ...softwares.rest => patch_softwareById.rest} | 6 +- ...post_softwares.rest => post_software.rest} | 6 +- requests/api/users/post_users.rest | 4 +- routes/api/auth.js | 5 +- routes/api/software.js | 27 + routes/api/softwares.js | 27 - .../auth/auth.test.js | 6 +- .../softwares.test.js => software.test.js} | 86 +- tests/api/users.test.js | 268 +++++ tests/api/users/users.test.js | 312 ----- ...waresTestUtils.js => softwareTestUtils.js} | 12 +- tests/utils/api/usersTestUtils.js | 40 +- tests/utils/databaseSetup.js | 47 +- tests/utils/firebaseTestUtils.js | 18 + utils/config.js | 6 +- utils/firebaseConfig.js | 13 + utils/jwtUtils.js | 32 +- utils/middleware.js | 42 +- 31 files changed, 1492 insertions(+), 622 deletions(-) rename controllers/api/{softwaresController.js => softwareController.js} (89%) rename requests/api/softwares/{delete_softwares.rest => delete_softwareById.rest} (74%) create mode 100644 requests/api/softwares/get_softwareById.rest delete mode 100644 requests/api/softwares/get_softwares.rest rename requests/api/softwares/{put_softwares.rest => patch_softwareById.rest} (80%) rename requests/api/softwares/{post_softwares.rest => post_software.rest} (81%) create mode 100644 routes/api/software.js delete mode 100644 routes/api/softwares.js rename tests/api/{ => disabledAPINotTested}/auth/auth.test.js (92%) rename tests/api/{software/softwares.test.js => software.test.js} (70%) create mode 100644 tests/api/users.test.js delete mode 100644 tests/api/users/users.test.js rename tests/utils/api/{softwaresTestUtils.js => softwareTestUtils.js} (80%) create mode 100644 tests/utils/firebaseTestUtils.js create mode 100644 utils/firebaseConfig.js diff --git a/app.js b/app.js index ba877b8..71183de 100644 --- a/app.js +++ b/app.js @@ -10,8 +10,8 @@ const usersRouter = require('./routes/api/users'); const middleware = require('./utils/middleware'); const logger = require('./utils/logger'); -const loginRouter = require('./routes/api/auth'); -const softwaresRouter = require('./routes/api/softwares'); +const authRouter = require('./routes/api/auth'); +const softwareRouter = require('./routes/api/software'); const app = express(); @@ -59,8 +59,8 @@ app.use( // Routes app.use(rootRouter); app.use('/api/users', usersRouter); -app.use('/api/auth', loginRouter); -app.use('/api/softwares', softwaresRouter); +app.use('/api/auth', authRouter); +app.use('/api/software', softwareRouter); app.use(middleware.unknownEndPoint); app.use(middleware.errorHandler); diff --git a/controllers/api/authController.js b/controllers/api/authController.js index 36aa0a1..d4aad2f 100644 --- a/controllers/api/authController.js +++ b/controllers/api/authController.js @@ -15,10 +15,9 @@ const postAuth = async (req, res) => { }, ); - const passwordCorrect = - user === null - ? false - : await bcrypt.compare(body.password, user.passwordHash); + const passwordCorrect = user === null + ? false + : await bcrypt.compare(body.password, user.passwordHash); if (!(user && passwordCorrect)) { return res.status('401').json({ diff --git a/controllers/api/softwaresController.js b/controllers/api/softwareController.js similarity index 89% rename from controllers/api/softwaresController.js rename to controllers/api/softwareController.js index cb7fd65..9a9fb40 100644 --- a/controllers/api/softwaresController.js +++ b/controllers/api/softwareController.js @@ -3,7 +3,7 @@ const Software = require('../../models/software'); const User = require('../../models/user'); const databaseUtils = require('../../utils/databaseUtils'); -const getSoftwares = async (req, res) => { +const getSoftware = async (req, res) => { const softwares = await Software.find({}) .populate('meta.addedByUser', { username: 1, @@ -16,9 +16,9 @@ const getSoftwares = async (req, res) => { res.status(200).json(softwares); }; -const postSoftwares = async (req, res) => { +const postSoftware = async (req, res) => { const { body } = req; - const userId = body.decodedToken.id; + const userId = body.decodedToken.backendId; // Refer to software Model for required parameters. const softwareObject = { @@ -60,10 +60,10 @@ const postSoftwares = async (req, res) => { return res.status(201).json(saved); }; -const putSoftware = async (req, res) => { +const patchSoftwareById = async (req, res) => { const { id } = req.params; const { body } = req; - const userId = body.decodedToken.id; + const userId = body.decodedToken.backendId; // To configure dotObject transformation to not modify how the array is represented. dotObject.keepArray = true; @@ -103,7 +103,7 @@ const putSoftware = async (req, res) => { return res.status(200).json(updated); }; -const getSoftware = async (req, res) => { +const getSoftwareById = async (req, res) => { const { id } = req.params; const software = await Software.findById(id); @@ -120,7 +120,7 @@ const getSoftware = async (req, res) => { return res.status(200).json(response); }; -const deleteSoftware = async (req, res) => { +const deleteSoftwareById = async (req, res) => { const { id } = req.params; const response = await Software.findByIdAndDelete(id); @@ -136,9 +136,9 @@ const deleteSoftware = async (req, res) => { }; module.exports = { - getSoftwares, - postSoftwares, - putSoftware, getSoftware, - deleteSoftware, + postSoftware, + patchSoftwareById, + getSoftwareById, + deleteSoftwareById, }; diff --git a/controllers/api/usersController.js b/controllers/api/usersController.js index 889ad3d..0d2611c 100644 --- a/controllers/api/usersController.js +++ b/controllers/api/usersController.js @@ -1,6 +1,6 @@ -const bcrypt = require('bcrypt'); const User = require('../../models/user'); -const config = require('../../utils/config'); +const firebaseAdmin = require('../../utils/firebaseConfig'); +const jwtUtils = require('../../utils/jwtUtils'); const getUsers = async (req, res) => { const users = await User.find({}) @@ -16,30 +16,27 @@ const getUsers = async (req, res) => { const postUsers = async (req, res) => { const { body } = req; - if (!body.password) { - return res.status(400).json({ - error: '`password` is required.', - }); - } + const authToken = jwtUtils.getReqAuthToken(req); + const decodedToken = await jwtUtils.verifyAuthToken(authToken, false); - if (body.password.length < 8) { - return res.status(400).json({ - error: 'Password has to be at least 8 characters long.', - }); - } + const response = await firebaseAdmin.auth().getUser(decodedToken.uid); - const saltRounds = config.BCRYPT_SALT_ROUNDS; - const passwordHash = await bcrypt.hash(body.password, saltRounds); + const userRecord = response.toJSON(); const user = new User({ username: body.username, - name: body.name, - email: body.email, - passwordHash, + name: userRecord.displayName, + email: userRecord.email, + firebaseUid: userRecord.uid, }); const savedUser = await user.save(); + // Set custom claim with backend user id (different from firebase user id) + await firebaseAdmin + .auth() + .setCustomUserClaims(decodedToken.uid, { backendId: savedUser.id }); + return res.status(201).json(savedUser); }; diff --git a/jest.config.js b/jest.config.js index 25c9bac..6f30e29 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,4 @@ module.exports = { testEnvironment: 'node', + testPathIgnorePatterns: ['/tests/api/disabledAPINotTested'], }; diff --git a/models/software.js b/models/software.js index 4901ddc..c05c58d 100644 --- a/models/software.js +++ b/models/software.js @@ -41,14 +41,10 @@ const softwareSchema = new mongoose.Schema( ], required: [true, 'Software homepage url is required'], }, - platforms: { - type: [ - { - type: String, - trim: true, - lowercase: true, - }, - ], + platform: { + type: String, + trim: true, + lowercase: true, required: [true, 'Software platform is required'], }, isActiveDevelopment: { diff --git a/models/user.js b/models/user.js index e2b3885..3172859 100644 --- a/models/user.js +++ b/models/user.js @@ -32,9 +32,14 @@ const userSchema = new mongoose.Schema( validate: [isEmail, 'A valid email address is required'], set: (value) => normalizeEmail(value), }, - passwordHash: { + password: { type: String, - required: [true, 'Password hash is required'], + default: '', + }, + firebaseUid: { + type: String, + required: [true, 'firebase user id is required.'], + unique: true, }, roles: { type: [ @@ -110,7 +115,7 @@ userSchema.set('toJSON', { returnedObject.id = returnedObject._id.toString(); delete returnedObject._id; delete returnedObject.__v; - delete returnedObject.passwordHash; + delete returnedObject.password; }, }); diff --git a/package-lock.json b/package-lock.json index 584c07c..0d2c6e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -480,6 +480,210 @@ } } }, + "@firebase/app-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", + "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==" + }, + "@firebase/auth-interop-types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", + "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==" + }, + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "@firebase/database": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", + "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", + "requires": { + "@firebase/auth-interop-types": "0.1.5", + "@firebase/component": "0.1.19", + "@firebase/database-types": "0.5.2", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.2", + "faye-websocket": "0.11.3", + "tslib": "^1.11.1" + } + }, + "@firebase/database-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", + "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", + "requires": { + "@firebase/app-types": "0.6.1" + } + }, + "@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==" + }, + "@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "requires": { + "tslib": "^1.11.1" + } + }, + "@google-cloud/common": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.4.1.tgz", + "integrity": "sha512-e5z0CwsM0RXky+PnyPtQ3QK46ksqm+kE7kX8pm8X+ddBwZJipHchKeazMM5fLlGCS+AALalzXb+uYmH72TRnpQ==", + "optional": true, + "requires": { + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^6.1.1", + "retry-request": "^4.1.1", + "teeny-request": "^7.0.0" + } + }, + "@google-cloud/firestore": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.7.0.tgz", + "integrity": "sha512-srkT0LxbKBEo3hWlgjJenT6+bPJK4D+vuKiV/EZFc6sWhDNQwgOgKF6Rf16mggHwKOL9Sx08Veu0BUaX1uyh4g==", + "optional": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^2.2.0" + } + }, + "@google-cloud/paginator": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.5.tgz", + "integrity": "sha512-N4Uk4BT1YuskfRhKXBs0n9Lg2YTROZc6IMpkO/8DIHODtm5s3xY8K5vVBo23v/2XulY3azwITQlYWgT4GdLsUw==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, + "@google-cloud/projectify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz", + "integrity": "sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==", + "optional": true + }, + "@google-cloud/promisify": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", + "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==", + "optional": true + }, + "@google-cloud/storage": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.4.0.tgz", + "integrity": "sha512-3sp1y6og1vEUgiN7s9SkP1zKwjsFKqBSz9y8KJKblyy2gfZnMLm4Sdw4oeZrQsDG6UKAbKq1KHe36b72SHeMIg==", + "optional": true, + "requires": { + "@google-cloud/common": "^3.3.0", + "@google-cloud/paginator": "^3.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.0", + "compressible": "^2.0.12", + "date-and-time": "^0.14.0", + "duplexify": "^4.0.0", + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "gcs-resumable-upload": "^3.1.0", + "get-stream": "^6.0.0", + "hash-stream-validation": "^0.2.2", + "mime": "^2.2.0", + "mime-types": "^2.0.8", + "onetime": "^5.1.0", + "p-limit": "^3.0.1", + "pumpify": "^2.0.0", + "snakeize": "^0.1.0", + "stream-events": "^1.0.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "get-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", + "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", + "optional": true + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "optional": true + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "optional": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "@grpc/grpc-js": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.8.tgz", + "integrity": "sha512-64hg5rmEm6F/NvlWERhHmmgxbWU8nD2TMWE+9TvG7/WcOrFT3fzg/Uu631pXRFwmJ4aWO/kp9vVSlr8FUjBDLA==", + "optional": true, + "requires": { + "@grpc/proto-loader": "^0.6.0-pre14", + "@types/node": "^12.12.47", + "google-auth-library": "^6.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "@grpc/proto-loader": { + "version": "0.6.0-pre9", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.0-pre9.tgz", + "integrity": "sha512-oM+LjpEjNzW5pNJjt4/hq1HYayNeQT+eGrOPABJnYHv7TyNPDNzkQ76rDYZF86X5swJOa4EujEMzQ9iiTdPgww==", + "optional": true, + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.9.0", + "yargs": "^15.3.1" + } + }, + "@types/node": { + "version": "12.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.3.tgz", + "integrity": "sha512-8Jduo8wvvwDzEVJCOvS/G6sgilOLvvhn1eMmK3TW8/T217O7u1jdrK6ImKLv80tVryaPSVeKu6sjDEiFjd4/eg==", + "optional": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "optional": true + } + } + }, + "@grpc/proto-loader": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.5.tgz", + "integrity": "sha512-WwN9jVNdHRQoOBo9FDH7qU+mgfjPc8GygPYms3M+y3fbQLfnCe/Kv/E01t7JRgnrsOHH8euvSbed3mIalXhwqQ==", + "optional": true, + "requires": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -841,6 +1045,70 @@ } } }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", + "optional": true + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "optional": true + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "optional": true + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "optional": true + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "optional": true, + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", + "optional": true + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", + "optional": true + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", + "optional": true + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", + "optional": true + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "optional": true + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -874,6 +1142,12 @@ "defer-to-connect": "^1.0.1" } }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true + }, "@types/babel__core": { "version": "7.1.12", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", @@ -918,8 +1192,7 @@ "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "@types/graceful-fs": { "version": "4.1.4", @@ -960,6 +1233,12 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", + "optional": true + }, "@types/node": { "version": "14.14.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz", @@ -1010,6 +1289,15 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "optional": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -1047,6 +1335,32 @@ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, "ajv": { "version": "6.12.4", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", @@ -1114,7 +1428,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -1245,6 +1558,12 @@ } } }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -1296,6 +1615,14 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", "dev": true }, + "axios": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", + "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, "babel-jest": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", @@ -1454,6 +1781,12 @@ } } }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "optional": true + }, "basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -1480,6 +1813,12 @@ "tweetnacl": "^0.14.3" } }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", + "optional": true + }, "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", @@ -1636,6 +1975,16 @@ } } }, + "call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1645,8 +1994,7 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "capture-exit": { "version": "2.0.0", @@ -1762,7 +2110,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -1772,14 +2119,12 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -1826,7 +2171,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -1834,8 +2178,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "combined-stream": { "version": "1.0.8", @@ -1857,6 +2200,15 @@ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "dev": true }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "optional": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1866,7 +2218,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, "requires": { "dot-prop": "^5.2.0", "graceful-fs": "^4.1.2", @@ -1972,8 +2323,7 @@ "crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" }, "cssom": { "version": "0.4.4", @@ -2018,6 +2368,12 @@ "whatwg-url": "^8.0.0" } }, + "date-and-time": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.1.tgz", + "integrity": "sha512-M4RggEH5OF2ZuCOxgOU67R6Z9ohjKbxGvAQz48vj53wLmL0bAgumkBvycR32f30pK+Og9pIR+RFDyChbaE4oLA==", + "optional": true + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2029,8 +2385,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decimal.js": { "version": "10.2.1", @@ -2163,6 +2518,14 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, + "dicer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "requires": { + "streamsearch": "0.1.2" + } + }, "diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", @@ -2208,7 +2571,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, "requires": { "is-obj": "^2.0.0" } @@ -2224,6 +2586,31 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "optional": true, + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -2268,7 +2655,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -2282,6 +2668,12 @@ "ansi-colors": "^4.1.1" } }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "optional": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2353,9 +2745,9 @@ } }, "eslint": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.12.1.tgz", - "integrity": "sha512-HlMTEdr/LicJfN08LB3nM1rRYliDXOmfoO4vj39xN6BLpFzF00hbwBoqHk8UcJ2M/3nlARZWy/mslvGEuZFvsg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.13.0.tgz", + "integrity": "sha512-uCORMuOO8tUzJmsdRtrvcGq5qposf7Rw0LwkTJkoDbOycVQtQjmnhZSuLQnozLE4TmAzlMVV45eCHmQ1OpDKUQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -2515,14 +2907,28 @@ } }, "eslint-config-airbnb-base": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz", - "integrity": "sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz", + "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==", "dev": true, "requires": { - "confusing-browser-globals": "^1.0.9", - "object.assign": "^4.1.0", + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", "object.entries": "^1.1.2" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } } }, "eslint-import-resolver-node": { @@ -2817,6 +3223,12 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "optional": true + }, "exec-sh": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", @@ -2983,8 +3395,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -3081,8 +3492,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-json-stable-stringify": { "version": "2.1.0", @@ -3102,6 +3512,20 @@ "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", "dev": true }, + "fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==", + "optional": true + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, "fb-watchman": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", @@ -3147,12 +3571,33 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, + "firebase-admin": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.3.0.tgz", + "integrity": "sha512-qMUITOp2QKLLc2o0/wSiDC2OO2knejjieZN/8Or9AzfFk8ftTcUKq5ALNlQXu+7aUzGe0IwSJq9TVnkIU0h1xw==", + "requires": { + "@firebase/database": "^0.6.10", + "@firebase/database-types": "^0.5.2", + "@google-cloud/firestore": "^4.0.0", + "@google-cloud/storage": "^5.3.0", + "@types/node": "^10.10.0", + "dicer": "^0.3.0", + "jsonwebtoken": "^8.5.1", + "node-forge": "^0.10.0" + }, + "dependencies": { + "@types/node": { + "version": "10.17.44", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.44.tgz", + "integrity": "sha512-vHPAyBX1ffLcy4fQHmDyIUMUb42gHZjPHU66nhvbMzAWJqHnySGZ6STwN3rwrnSd1FHB0DI/RWgGELgKSYRDmw==" + } + } + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -3181,6 +3626,11 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, + "follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -3258,8 +3708,7 @@ "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, "gauge": { "version": "2.7.4", @@ -3309,6 +3758,73 @@ } } }, + "gaxios": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.0.1.tgz", + "integrity": "sha512-jOin8xRZ/UytQeBpSXFqIzqU7Fi5TqgPNLlUsSB8kjJ76+FiGBfImF8KJu++c6J4jOldfJUtt0YmkRj2ZpSHTQ==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "optional": true + } + } + }, + "gcp-metadata": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", + "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", + "optional": true, + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "gcs-resumable-upload": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.1.tgz", + "integrity": "sha512-RS1osvAicj9+MjCc6jAcVL1Pt3tg7NK2C2gXM5nqD1Gs0klF2kj5nnAFSBy97JrtslMIQzpb7iSuxaG8rFWd2A==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "configstore": "^5.0.0", + "extend": "^3.0.2", + "gaxios": "^3.0.0", + "google-auth-library": "^6.0.0", + "pumpify": "^2.0.0", + "stream-events": "^1.0.4" + }, + "dependencies": { + "gaxios": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", + "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "optional": true + } + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3318,8 +3834,18 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } }, "get-package-type": { "version": "0.1.0", @@ -3388,6 +3914,73 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "google-auth-library": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.3.tgz", + "integrity": "sha512-m9mwvY3GWbr7ZYEbl61isWmk+fvTmOt0YNUfPOUY2VH8K5pZlAIWJjxEi0PqR3OjMretyiQLI6GURMrPSwHQ2g==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } + } + }, + "google-gax": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.9.2.tgz", + "integrity": "sha512-Pve4osEzNKpBZqFXMfGKBbKCtgnHpUe5IQMh5Ou+Xtg8nLcba94L3gF0xgM5phMdGRRqJn0SMjcuEVmOYu7EBg==", + "optional": true, + "requires": { + "@grpc/grpc-js": "~1.1.1", + "@grpc/proto-loader": "^0.5.1", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^6.1.3", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "protobufjs": "^6.9.0", + "retry-request": "^4.0.0" + } + }, + "google-p12-pem": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "optional": true, + "requires": { + "node-forge": "^0.10.0" + } + }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -3410,8 +4003,7 @@ "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "growly": { "version": "1.3.0", @@ -3420,6 +4012,47 @@ "dev": true, "optional": true }, + "gtoken": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.5.tgz", + "integrity": "sha512-wvjkecutFh8kVfbcdBdUWqDRrXb+WrgD79DBDEYf1Om8S1FluhylhtFjrL7Tx69vNhh259qA3Q1P4sPtb+kUYw==", + "optional": true, + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0", + "mime": "^2.2.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "optional": true + } + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -3520,6 +4153,12 @@ "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "dev": true }, + "hash-stream-validation": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", + "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", + "optional": true + }, "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", @@ -3559,6 +4198,39 @@ "toidentifier": "1.0.0" } }, + "http-parser-js": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", + "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==" + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -3570,6 +4242,33 @@ "sshpk": "^1.7.0" } }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "optional": true, + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, "human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -3605,9 +4304,9 @@ } }, "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", + "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -3641,8 +4340,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "inflight": { "version": "1.0.6", @@ -3854,8 +4552,7 @@ "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, "is-path-inside": { "version": "3.0.2", @@ -3893,6 +4590,12 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, "is-string": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", @@ -3911,8 +4614,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-windows": { "version": "1.0.2", @@ -4987,6 +5689,15 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "optional": true, + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", @@ -5177,7 +5888,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -5188,6 +5898,12 @@ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "optional": true + }, "lodash.foreach": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", @@ -5239,17 +5955,39 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "optional": true + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, "requires": { "semver": "^6.0.0" }, @@ -5257,8 +5995,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -5344,8 +6081,7 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "mimic-response": { "version": "1.0.1", @@ -5413,9 +6149,9 @@ } }, "mongodb": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.2.tgz", - "integrity": "sha512-sSZOb04w3HcnrrXC82NEh/YGCmBuRgR+C1hZgmmv4L6dBz4BkRse6Y8/q/neXer9i95fKUBbFi4KgeceXmbsOA==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.3.tgz", + "integrity": "sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w==", "requires": { "bl": "^2.2.1", "bson": "^1.1.4", @@ -5426,13 +6162,13 @@ } }, "mongoose": { - "version": "5.10.12", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.12.tgz", - "integrity": "sha512-BoWagkWYEfpNCBPzSbfTlLHEtiGziSXbH/YHibrZbhvH1t+zM98X/guL7Ieeh35el4ilf+XFruK84nF5k0QpSw==", + "version": "5.10.13", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.13.tgz", + "integrity": "sha512-lvZzTj449sVWijY76StOuTKt5oP5kyy70VdM3DMgPpKNqZfkAseHxekmqBbd9YQQDVIgrIYDar9vSlxKqc75MQ==", "requires": { "bson": "^1.1.4", "kareem": "2.3.1", - "mongodb": "3.6.2", + "mongodb": "3.6.3", "mongoose-legacy-pluralize": "1.0.2", "mpath": "0.7.0", "mquery": "3.2.2", @@ -5586,6 +6322,17 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz", "integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==" }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "optional": true + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5962,7 +6709,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -6022,7 +6768,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -6031,7 +6776,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -6039,8 +6783,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "package-json": { "version": "6.5.0", @@ -6103,8 +6846,7 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "path-is-absolute": { "version": "1.0.1", @@ -6231,6 +6973,35 @@ "sisteransi": "^1.0.5" } }, + "protobufjs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.1.tgz", + "integrity": "sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ==", + "optional": true, + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "13.13.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.30.tgz", + "integrity": "sha512-HmqFpNzp3TSELxU/bUuRK+xzarVOAsR00hzcvM0TXrMlt/+wcSLa5q6YhTb6/cA6wqDCZLDcfd8fSL95x5h7AA==", + "optional": true + } + } + }, "proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", @@ -6256,12 +7027,22 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, + "pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "optional": true, + "requires": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -6513,14 +7294,12 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "require_optional": { "version": "1.0.1", @@ -6584,6 +7363,32 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "retry-request": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", + "integrity": "sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ==", + "optional": true, + "requires": { + "debug": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -6959,6 +7764,12 @@ "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" }, + "snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", + "optional": true + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -7229,6 +8040,26 @@ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "requires": { + "stubs": "^3.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "optional": true + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, "string-length": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", @@ -7260,7 +8091,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7270,26 +8100,22 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -7356,6 +8182,12 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "optional": true + }, "superagent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", @@ -7523,6 +8355,19 @@ "yallist": "^3.0.3" } }, + "teeny-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", + "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", + "optional": true, + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, "term-size": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", @@ -7684,6 +8529,11 @@ } } }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -7733,7 +8583,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "requires": { "is-typedarray": "^1.0.0" } @@ -7763,7 +8612,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, "requires": { "crypto-random-string": "^2.0.0" } @@ -7878,13 +8726,12 @@ "version": "8.3.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", - "dev": true, "optional": true }, "v8-compile-cache": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", - "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", "dev": true }, "v8-to-istanbul": { @@ -7970,6 +8817,21 @@ "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", "dev": true }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + }, "whatwg-encoding": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", @@ -8007,8 +8869,7 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "wide-align": { "version": "1.1.3", @@ -8061,7 +8922,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8071,14 +8931,12 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -8103,7 +8961,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, "requires": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -8120,8 +8977,7 @@ "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" }, "xml-name-validator": { "version": "3.0.0", @@ -8138,8 +8994,7 @@ "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, "yallist": { "version": "3.1.1", @@ -8150,7 +9005,6 @@ "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, "requires": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -8169,7 +9023,6 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" diff --git a/package.json b/package.json index 7364688..199fa71 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "homepage": "https://github.com/learnsoftwaredevelopment/SoftwareRepository#readme", "dependencies": { + "axios": "^0.21.0", "bcrypt": "^5.0.0", "cors": "^2.8.5", "cross-env": "^7.0.2", @@ -38,15 +39,16 @@ "dotenv": "^8.2.0", "express": "^4.17.1", "express-async-errors": "^3.1.1", + "firebase-admin": "^9.3.0", "jsonwebtoken": "^8.5.1", - "mongoose": "^5.10.12", + "mongoose": "^5.10.13", "mongoose-unique-validator": "^2.0.3", "morgan": "^1.10.0", "validator": "^13.1.17" }, "devDependencies": { - "eslint": "^7.12.1", - "eslint-config-airbnb-base": "^14.2.0", + "eslint": "^7.13.0", + "eslint-config-airbnb-base": "^14.2.1", "eslint-plugin-import": "^2.22.1", "jest": "^26.6.3", "nodemon": "^2.0.6", diff --git a/requests/api/softwares/delete_softwares.rest b/requests/api/softwares/delete_softwareById.rest similarity index 74% rename from requests/api/softwares/delete_softwares.rest rename to requests/api/softwares/delete_softwareById.rest index fa43706..5bb89a8 100644 --- a/requests/api/softwares/delete_softwares.rest +++ b/requests/api/softwares/delete_softwareById.rest @@ -1,2 +1,2 @@ -DELETE http://localhost:3001/api/softwares/5f997876dde20a5a60ce1c64 +DELETE http://localhost:3001/api/software/5f997876dde20a5a60ce1c64 Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNhbXBsZSIsImlkIjoiNWY4ZGEzODcwODlmZmY0ZGIwNmJjMGFmIiwiaWF0IjoxNjAzODkzMzExfQ.ARy4EKDMzJqd4DcZyWXP5hygSQ9A2Bw1YfMQ-Nb03JU \ No newline at end of file diff --git a/requests/api/softwares/get_software.rest b/requests/api/softwares/get_software.rest index 26bd5d1..05eea26 100644 --- a/requests/api/softwares/get_software.rest +++ b/requests/api/softwares/get_software.rest @@ -1 +1 @@ -GET http://localhost:3001/api/softwares/5f5e01ad5b0c9840c0d7f717 \ No newline at end of file +GET http://localhost:3001/api/software/ \ No newline at end of file diff --git a/requests/api/softwares/get_softwareById.rest b/requests/api/softwares/get_softwareById.rest new file mode 100644 index 0000000..c3f55bf --- /dev/null +++ b/requests/api/softwares/get_softwareById.rest @@ -0,0 +1 @@ +GET http://localhost:3001/api/software/5f5e01ad5b0c9840c0d7f717 \ No newline at end of file diff --git a/requests/api/softwares/get_softwares.rest b/requests/api/softwares/get_softwares.rest deleted file mode 100644 index eca92c7..0000000 --- a/requests/api/softwares/get_softwares.rest +++ /dev/null @@ -1 +0,0 @@ -GET http://localhost:3001/api/softwares/ \ No newline at end of file diff --git a/requests/api/softwares/put_softwares.rest b/requests/api/softwares/patch_softwareById.rest similarity index 80% rename from requests/api/softwares/put_softwares.rest rename to requests/api/softwares/patch_softwareById.rest index af659fe..8dd5822 100644 --- a/requests/api/softwares/put_softwares.rest +++ b/requests/api/softwares/patch_softwareById.rest @@ -1,13 +1,11 @@ -PUT http://localhost:3001/api/softwares/5f997876dde20a5a60ce1c64 +PUT http://localhost:3001/api/software/5f997876dde20a5a60ce1c64 Content-Type: application/json Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNhbXBsZSIsImlkIjoiNWY4ZGEzODcwODlmZmY0ZGIwNmJjMGFmIiwiaWF0IjoxNjAzODkzMzExfQ.ARy4EKDMzJqd4DcZyWXP5hygSQ9A2Bw1YfMQ-Nb03JU { "description": "A sample software made by a happy person love", "homePage": "http://apple.com.sg", - "platforms": [ - "Windows", "MacOS" - ], + "platform": "MacOS", "meta": { "tags": ["hello", "world"], "votes": 1 diff --git a/requests/api/softwares/post_softwares.rest b/requests/api/softwares/post_software.rest similarity index 81% rename from requests/api/softwares/post_softwares.rest rename to requests/api/softwares/post_software.rest index a3f5a59..eca31cf 100644 --- a/requests/api/softwares/post_softwares.rest +++ b/requests/api/softwares/post_software.rest @@ -1,4 +1,4 @@ -POST http://localhost:3001/api/softwares/ +POST http://localhost:3001/api/software/ Content-Type: application/json Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNhbXBsZSIsImlkIjoiNWY4ZGEzODcwODlmZmY0ZGIwNmJjMGFmIiwiaWF0IjoxNjAzODkzMzExfQ.ARy4EKDMzJqd4DcZyWXP5hygSQ9A2Bw1YfMQ-Nb03JU @@ -6,8 +6,6 @@ Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNhb "name": "SampleSoftware8", "description": "A sample software", "homePage": "http://example.com", - "platforms": [ - "Windows" - ], + "platform": "Windows", "isActiveDevelopment": true } \ No newline at end of file diff --git a/requests/api/users/post_users.rest b/requests/api/users/post_users.rest index 45b2fe4..fb40483 100644 --- a/requests/api/users/post_users.rest +++ b/requests/api/users/post_users.rest @@ -1,9 +1,7 @@ POST http://localhost:3001/api/users/ Content-Type: application/json +Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNhbXBsZSIsImlkIjoiNWY4ZGEzODcwODlmZmY0ZGIwNmJjMGFmIiwiaWF0IjoxNjAzODkzMzExfQ.ARy4EKDMzJqd4DcZyWXP5hygSQ9A2Bw1YfMQ-Nb03JU { "username": "Sample", - "name": "SampleName", - "email": "sample@example.com", - "password": "SamplePassword" } \ No newline at end of file diff --git a/routes/api/auth.js b/routes/api/auth.js index c6cc96d..d4ebf50 100644 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -1,8 +1,9 @@ const authRouter = require('express').Router(); const authController = require('../../controllers/api/authController'); +const { disabledAPIEndPoint } = require('../../utils/middleware'); -authRouter.post('/', authController.postAuth); +authRouter.post('/', disabledAPIEndPoint, authController.postAuth); -authRouter.get('/', authController.getAuth); +authRouter.get('/', disabledAPIEndPoint, authController.getAuth); module.exports = authRouter; diff --git a/routes/api/software.js b/routes/api/software.js new file mode 100644 index 0000000..7e5fdb7 --- /dev/null +++ b/routes/api/software.js @@ -0,0 +1,27 @@ +const softwareRouter = require('express').Router(); +const middleware = require('../../utils/middleware'); +const softwareController = require('../../controllers/api/softwareController'); + +softwareRouter.get('/', softwareController.getSoftware); + +softwareRouter.post( + '/', + middleware.tokenValidation, + softwareController.postSoftware, +); + +softwareRouter.patch( + '/:id', + middleware.tokenValidation, + softwareController.patchSoftwareById, +); + +softwareRouter.get('/:id', softwareController.getSoftwareById); + +softwareRouter.delete( + '/:id', + middleware.tokenValidation, + softwareController.deleteSoftwareById, +); + +module.exports = softwareRouter; diff --git a/routes/api/softwares.js b/routes/api/softwares.js deleted file mode 100644 index 63c15eb..0000000 --- a/routes/api/softwares.js +++ /dev/null @@ -1,27 +0,0 @@ -const softwaresRouter = require('express').Router(); -const middleware = require('../../utils/middleware'); -const softwaresControllers = require('../../controllers/api/softwaresController'); - -softwaresRouter.get('/', softwaresControllers.getSoftwares); - -softwaresRouter.post( - '/', - middleware.tokenValidation, - softwaresControllers.postSoftwares, -); - -softwaresRouter.put( - '/:id', - middleware.tokenValidation, - softwaresControllers.putSoftware, -); - -softwaresRouter.get('/:id', softwaresControllers.getSoftware); - -softwaresRouter.delete( - '/:id', - middleware.tokenValidation, - softwaresControllers.deleteSoftware, -); - -module.exports = softwaresRouter; diff --git a/tests/api/auth/auth.test.js b/tests/api/disabledAPINotTested/auth/auth.test.js similarity index 92% rename from tests/api/auth/auth.test.js rename to tests/api/disabledAPINotTested/auth/auth.test.js index d061dc3..11c3e78 100644 --- a/tests/api/auth/auth.test.js +++ b/tests/api/disabledAPINotTested/auth/auth.test.js @@ -1,8 +1,8 @@ const supertest = require('supertest'); const mongoose = require('mongoose'); -const app = require('../../../app'); -const databaseSetupTestUtils = require('../../utils/databaseSetup'); -const { initialiseADefaultUserInDb } = require('../../utils/databaseSetup'); +const app = require('../../../../app'); +const databaseSetupTestUtils = require('../../../utils/databaseSetup'); +const { initialiseADefaultUserInDb } = require('../../../utils/databaseSetup'); const api = supertest(app); diff --git a/tests/api/software/softwares.test.js b/tests/api/software.test.js similarity index 70% rename from tests/api/software/softwares.test.js rename to tests/api/software.test.js index 91c9822..e617dd3 100644 --- a/tests/api/software/softwares.test.js +++ b/tests/api/software.test.js @@ -1,28 +1,34 @@ const supertest = require('supertest'); const mongoose = require('mongoose'); -const app = require('../../../app'); -const databaseSetupTestUtils = require('../../utils/databaseSetup'); -const softwareTestUtils = require('../../utils/api/softwaresTestUtils'); +const app = require('../../app'); +const databaseSetup = require('../utils/databaseSetup'); +const usersTestUtils = require('../utils/api/usersTestUtils'); +const softwareTestUtils = require('../utils/api/softwareTestUtils'); +const firebaseTestUtils = require('../utils/firebaseTestUtils'); const api = supertest(app); let defaultUser; -let token; beforeEach(async () => { - await databaseSetupTestUtils.resetDatabase(); - defaultUser = await databaseSetupTestUtils.initialiseADefaultUserInDb(); - token = databaseSetupTestUtils.loginUserToken(defaultUser); + await databaseSetup.resetDatabase(); + defaultUser = await usersTestUtils.addUserToDb( + usersTestUtils.sampleUserCredential1, + ); + await databaseSetup.setBackendIdOfDefaultUser( + usersTestUtils.sampleUserCredential1, + defaultUser.id, + ); }); describe('Software Controller', () => { - describe('GET request to /api/softwares/', () => { + describe('GET request to /api/software/', () => { test('When there is no software in database, return status code 200 and json with an empty array of softwares', async () => { - const initialSoftwaresInDb = await softwareTestUtils.softwaresInDb(); - expect(initialSoftwaresInDb).toHaveLength(0); + const initialSoftwareInDb = await databaseSetup.softwareInDb(); + expect(initialSoftwareInDb).toHaveLength(0); const response = await api - .get('/api/softwares') + .get('/api/software') .expect(200) .expect('Content-Type', /application\/json/); @@ -38,11 +44,11 @@ describe('Software Controller', () => { defaultUser._id, ); - const initialSoftwaresInDb = await softwareTestUtils.softwaresInDb(); - expect(initialSoftwaresInDb).toHaveLength(1); + const initialSoftwareInDb = await databaseSetup.softwareInDb(); + expect(initialSoftwareInDb).toHaveLength(1); const response = await api - .get('/api/softwares') + .get('/api/software') .expect(200) .expect('Content-Type', /application\/json/); @@ -50,7 +56,7 @@ describe('Software Controller', () => { ...softwareToAdd, // Since the name and platform values are stored in lowercase in the database. name: softwareToAdd.name.toLowerCase(), - platforms: softwareToAdd.platforms.map((platform) => platform.toLowerCase()), + platform: softwareToAdd.platform.toLowerCase(), meta: { addedByUser: { username: defaultUser.username, @@ -83,11 +89,11 @@ describe('Software Controller', () => { defaultUser._id, ); - const initialSoftwaresInDb = await softwareTestUtils.softwaresInDb(); - expect(initialSoftwaresInDb).toHaveLength(2); + const initialSoftwareInDb = await databaseSetup.softwareInDb(); + expect(initialSoftwareInDb).toHaveLength(2); const response = await api - .get('/api/softwares') + .get('/api/software') .expect(200) .expect('Content-Type', /application\/json/); @@ -95,7 +101,7 @@ describe('Software Controller', () => { ...softwareToAdd1, // Since the name and platform values are stored in lowercase in the database. name: softwareToAdd1.name.toLowerCase(), - platforms: softwareToAdd1.platforms.map((platform) => platform.toLowerCase()), + platform: softwareToAdd1.platform.toLowerCase(), meta: { addedByUser: { username: defaultUser.username, @@ -112,7 +118,7 @@ describe('Software Controller', () => { ...softwareToAdd2, // Since the name and platform values are stored in lowercase in the database. name: softwareToAdd2.name.toLowerCase(), - platforms: softwareToAdd2.platforms.map((platform) => platform.toLowerCase()), + platform: softwareToAdd2.platform.toLowerCase(), meta: { addedByUser: { username: defaultUser.username, @@ -135,9 +141,9 @@ describe('Software Controller', () => { }); describe('Software Controller', () => { - describe('POST request to /api/softwares/', () => { + describe('POST request to /api/software/', () => { test('When missing Authorisation token, return with status 401 with json Missing or Invalid Token error message, no change in the number of softwares in database', async () => { - const initialSoftwaresInDb = await softwareTestUtils.softwaresInDb(); + const initialSoftwareInDb = await databaseSetup.softwareInDb(); const softwareToAdd = { ...softwareTestUtils.sampleSoftwareInDb1, @@ -154,18 +160,18 @@ describe('Software Controller', () => { }; const response = await api - .post('/api/softwares') + .post('/api/software/') .send(softwareToAdd) .expect(401) .expect('Content-Type', /application\/json/); - const softwaresInDb = await softwareTestUtils.softwaresInDb(); - expect(softwaresInDb).toHaveLength(initialSoftwaresInDb.length); - expect(response.body.error).toBe('Missing or Invalid Token'); + const softwareInDb = await databaseSetup.softwareInDb(); + expect(softwareInDb).toHaveLength(initialSoftwareInDb.length); + expect(response.body.error).toBe('Missing Token'); }); test('When invalid Authorisation token, return with status 401 with json Token missing or invalid error message, no change in the number of softwares in database', async () => { - const initialSoftwaresInDb = await softwareTestUtils.softwaresInDb(); + const initialSoftwareInDb = await databaseSetup.softwareInDb(); const softwareToAdd = { ...softwareTestUtils.sampleSoftwareInDb1, @@ -182,19 +188,25 @@ describe('Software Controller', () => { }; const response = await api - .post('/api/softwares') + .post('/api/software/') .set('Authorization', 'bearer invalid token') .send(softwareToAdd) .expect(401) .expect('Content-Type', /application\/json/); - const softwaresInDb = await softwareTestUtils.softwaresInDb(); - expect(softwaresInDb).toHaveLength(initialSoftwaresInDb.length); + const softwareInDb = await databaseSetup.softwareInDb(); + expect(softwareInDb).toHaveLength(initialSoftwareInDb.length); expect(response.body.error).toBe('Invalid Token'); }); test('When request is valid, number of softwares in database increments by 1', async () => { - const initialSoftwaresInDb = await softwareTestUtils.softwaresInDb(); + const { email, password } = usersTestUtils.sampleUserCredential1; + const { idToken } = await firebaseTestUtils.loginFireBase( + email, + password, + ); + + const initialSoftwareInDb = await databaseSetup.softwareInDb(); const softwareToAdd = { ...softwareTestUtils.sampleSoftwareInDb1, @@ -211,8 +223,8 @@ describe('Software Controller', () => { }; const response = await api - .post('/api/softwares') - .set('Authorization', databaseSetupTestUtils.formattedToken(token)) + .post('/api/software/') + .set('Authorization', `bearer ${idToken}`) .send(softwareToAdd) .expect(201) .expect('Content-Type', /application\/json/); @@ -221,17 +233,17 @@ describe('Software Controller', () => { ...softwareToAdd, // Since the name and platform values are stored in lowercase in the database. name: softwareToAdd.name.toLowerCase(), - platforms: softwareToAdd.platforms.map((platform) => platform.toLowerCase()), + platform: softwareToAdd.platform.toLowerCase(), }; - const softwaresInDb = await softwareTestUtils.softwaresInDb(); - expect(softwaresInDb).toHaveLength(initialSoftwaresInDb.length + 1); + const softwareInDb = await databaseSetup.softwareInDb(); + expect(softwareInDb).toHaveLength(initialSoftwareInDb.length + 1); expect(response.body).toMatchObject(expectedSoftware); }); }); }); afterAll(async () => { - await databaseSetupTestUtils.resetDatabase(); + await databaseSetup.resetDatabase(); await mongoose.connection.close(); }); diff --git a/tests/api/users.test.js b/tests/api/users.test.js new file mode 100644 index 0000000..738de70 --- /dev/null +++ b/tests/api/users.test.js @@ -0,0 +1,268 @@ +const supertest = require('supertest'); +const mongoose = require('mongoose'); +const app = require('../../app'); +const databaseSetup = require('../utils/databaseSetup'); +const usersTestUtils = require('../utils/api/usersTestUtils'); +const firebaseTestUtils = require('../utils/firebaseTestUtils'); + +const api = supertest(app); + +beforeEach(async () => { + await databaseSetup.resetDatabase(); +}); + +describe('Users Controller', () => { + describe('GET request to /api/users/', () => { + test('When there is no user in database, return status code 200 and json with an empty array of users', async () => { + const initialUsersInDb = await databaseSetup.usersInDb(); + expect(initialUsersInDb).toHaveLength(0); + + const response = await api + .get('/api/users') + .expect(200) + .expect('Content-Type', /application\/json/); + + expect(response.body).toHaveLength(0); + }); + + test('When there is one user in database, return status code 200 and json with only that user in the array', async () => { + const userToAdd = usersTestUtils.sampleUserCredential1; + await usersTestUtils.addUserToDb(userToAdd); + + const initialUsersInDb = await databaseSetup.usersInDb(); + expect(initialUsersInDb).toHaveLength(1); + + const response = await api + .get('/api/users') + .expect(200) + .expect('Content-Type', /application\/json/); + + const expectedUser = usersTestUtils.sanitizeUserObject( + usersTestUtils.sampleUserInDb1, + ); + + expect(response.body).toHaveLength(1); + expect(response.body[0]).toMatchObject(expectedUser); + }); + + test('When there are two users in database, return status code 200 and json with only those two users in the array', async () => { + const user1ToAdd = usersTestUtils.sampleUserCredential1; + const user2ToAdd = usersTestUtils.sampleUserCredential2; + await usersTestUtils.addUserToDb(user1ToAdd); + await usersTestUtils.addUserToDb(user2ToAdd); + + const initialUsersInDb = await databaseSetup.usersInDb(); + expect(initialUsersInDb).toHaveLength(2); + + const response = await api + .get('/api/users') + .expect(200) + .expect('Content-Type', /application\/json/); + + const expectedUser1 = usersTestUtils.sanitizeUserObject( + usersTestUtils.sampleUserInDb1, + ); + + const expectedUser2 = usersTestUtils.sanitizeUserObject( + usersTestUtils.sampleUserInDb2, + ); + + expect(response.body).toHaveLength(2); + expect(response.body[0]).toMatchObject(expectedUser1); + expect(response.body[1]).toMatchObject(expectedUser2); + }); + }); + + /** + * Note some test cases for validation which was present in the previous commits + * were removed as validation such as email, firebaseUid, etc + * are handled by firebase Authentication + */ + describe('POST request to /api/users/', () => { + test('(Test one mongodb buildin validator) When username is missing, return json with error missing username message', async () => { + const { email, password } = usersTestUtils.sampleUserCredential1; + const { idToken } = await firebaseTestUtils.loginFireBase( + email, + password, + ); + + const reqBody = {}; + + const initialUsersInDb = await databaseSetup.usersInDb(); + + const response = await api + .post('/api/users/') + .set('Authorization', `bearer ${idToken}`) + .send(reqBody) + .expect(400) + .expect('Content-Type', /application\/json/); + + const usersInDb = await databaseSetup.usersInDb(); + + expect(usersInDb).toHaveLength(initialUsersInDb.length); + expect(response.body.error).toEqual({ + username: 'Missing username', + }); + }); + + test('(Test one mongodb buildin validator) When username is less than 6 characters long , return json with error the username should be at least 6 characters long', async () => { + const { email, password } = usersTestUtils.sampleUserCredential1; + const { idToken } = await firebaseTestUtils.loginFireBase( + email, + password, + ); + + const reqBody = { + username: '123', + }; + + const initialUsersInDb = await databaseSetup.usersInDb(); + + const response = await api + .post('/api/users/') + .set('Authorization', `bearer ${idToken}`) + .send(reqBody) + .expect(400) + .expect('Content-Type', /application\/json/); + + const usersInDb = await databaseSetup.usersInDb(); + + expect(usersInDb).toHaveLength(initialUsersInDb.length); + expect(response.body.error).toEqual({ + username: 'The username should be at least 6 characters long', + }); + }); + + test('(Test custom validator) When username is containing unsupported characters like spaces, return json with error A valid username is required message', async () => { + const { email, password } = usersTestUtils.sampleUserCredential1; + const { idToken } = await firebaseTestUtils.loginFireBase( + email, + password, + ); + + const reqBody = { + username: 'Sample username', + }; + + const initialUsersInDb = await databaseSetup.usersInDb(); + + const response = await api + .post('/api/users/') + .set('Authorization', `bearer ${idToken}`) + .send(reqBody) + .expect(400) + .expect('Content-Type', /application\/json/); + + const usersInDb = await databaseSetup.usersInDb(); + + expect(usersInDb).toHaveLength(initialUsersInDb.length); + + const expectedError = { + username: 'A valid username is required', + }; + + expect(response.body.error).toEqual(expectedError); + }); + }); + + test("(Test unique validator) When username ('sample') already exists in database, return json with error username must be unique. message", async () => { + const { email, password } = usersTestUtils.sampleUserCredential2; + const { idToken } = await firebaseTestUtils.loginFireBase(email, password); + + const reqBody = { + username: 'Sample', + }; + + await usersTestUtils.addUserToDb(usersTestUtils.sampleUserCredential1); + + const initialUsersInDb = await databaseSetup.usersInDb(); + + const response = await api + .post('/api/users/') + .set('Authorization', `bearer ${idToken}`) + .send(reqBody) + .expect(400) + .expect('Content-Type', /application\/json/); + + const usersInDb = await databaseSetup.usersInDb(); + + expect(usersInDb).toHaveLength(initialUsersInDb.length); + + const expectedError = { + username: 'username must be unique.', + }; + + expect(response.body.error).toEqual(expectedError); + }); + + test("(Test multiple unique validators) When email ('sample@example.com') and firebase uid already exists in database, return json with error email must be unique. and firebaseUid must be unique messages", async () => { + const { email, password } = usersTestUtils.sampleUserCredential1; + const { idToken } = await firebaseTestUtils.loginFireBase(email, password); + + const reqBody = { + username: 'Sample2', + }; + + await usersTestUtils.addUserToDb(usersTestUtils.sampleUserCredential1); + + const initialUsersInDb = await databaseSetup.usersInDb(); + + const response = await api + .post('/api/users/') + .set('Authorization', `bearer ${idToken}`) + .send(reqBody) + .expect(400) + .expect('Content-Type', /application\/json/); + + const usersInDb = await databaseSetup.usersInDb(); + + expect(usersInDb).toHaveLength(initialUsersInDb.length); + + const expectedContainedError = { + email: 'email must be unique.', + firebaseUid: 'firebaseUid must be unique.', + }; + + expect(response.body.error).toEqual(expectedContainedError); + }); + + test('When request is valid, number of users in database increment by 1', async () => { + const { email, password } = usersTestUtils.sampleUserCredential1; + const { idToken, uid } = await firebaseTestUtils.loginFireBase( + email, + password, + ); + + const reqBody = { + username: 'Sample', + }; + + const initialUsersInDb = await databaseSetup.usersInDb(); + + const response = await api + .post('/api/users/') + .send(reqBody) + .set('Authorization', `bearer ${idToken}`) + .expect(201) + .expect('Content-Type', /application\/json/); + + const usersInDb = await databaseSetup.usersInDb(); + expect(usersInDb).toHaveLength(initialUsersInDb.length + 1); + + // Lowercase as the username and email fields + // are converted to lowercase before inserting into database. + const expectedUser = { + username: 'sample', + name: 'SampleName', + email: 'sample@example.com', + firebaseUid: uid, + }; + + expect(response.body).toMatchObject(expectedUser); + }); +}); + +afterAll(async () => { + await databaseSetup.resetDatabase(); + await mongoose.connection.close(); +}); diff --git a/tests/api/users/users.test.js b/tests/api/users/users.test.js deleted file mode 100644 index bc2dc3c..0000000 --- a/tests/api/users/users.test.js +++ /dev/null @@ -1,312 +0,0 @@ -const supertest = require('supertest'); -const mongoose = require('mongoose'); -const app = require('../../../app'); -const databaseSetupTestUtils = require('../../utils/databaseSetup'); -const usersTestUtils = require('../../utils/api/usersTestUtils'); - -const api = supertest(app); - -beforeEach(async () => { - await databaseSetupTestUtils.resetDatabase(); -}); - -describe('Users Controller', () => { - describe('GET request to /api/users/', () => { - test('When there is no user in database, return status code 200 and json with an empty array of users', async () => { - const initialUsersInDb = await usersTestUtils.usersInDb(); - expect(initialUsersInDb).toHaveLength(0); - - const response = await api - .get('/api/users') - .expect(200) - .expect('Content-Type', /application\/json/); - - expect(response.body).toHaveLength(0); - }); - - test('When there is one user in database, return status code 200 and json with only that user in the array', async () => { - const userToAdd = usersTestUtils.sampleUserInDb1; - await usersTestUtils.addUserToDb(userToAdd); - - const initialUsersInDb = await usersTestUtils.usersInDb(); - expect(initialUsersInDb).toHaveLength(1); - - const response = await api - .get('/api/users') - .expect(200) - .expect('Content-Type', /application\/json/); - - const expectedUser = usersTestUtils.sanitizeUserObject(userToAdd); - - expect(response.body).toHaveLength(1); - expect(response.body[0]).toMatchObject(expectedUser); - }); - - test('When there are two users in database, return status code 200 and json with only those two users in the array', async () => { - const user1ToAdd = usersTestUtils.sampleUserInDb1; - const user2ToAdd = usersTestUtils.sampleUserInDb2; - await usersTestUtils.addUserToDb(user1ToAdd); - await usersTestUtils.addUserToDb(user2ToAdd); - - const initialUsersInDb = await usersTestUtils.usersInDb(); - expect(initialUsersInDb).toHaveLength(2); - - const response = await api - .get('/api/users') - .expect(200) - .expect('Content-Type', /application\/json/); - - const expectedUser1 = usersTestUtils.sanitizeUserObject(user1ToAdd); - const expectedUser2 = usersTestUtils.sanitizeUserObject(user2ToAdd); - - expect(response.body).toHaveLength(2); - expect(response.body[0]).toMatchObject(expectedUser1); - expect(response.body[1]).toMatchObject(expectedUser2); - }); - }); - - describe('POST request to /api/users/', () => { - test('(Test one mongodb buildin validator) When username is missing, return json with error missing username message', async () => { - const userToAdd = { - name: 'SampleName', - email: 'sample@example.com', - password: 'SamplePassword', - }; - - const initialUsersInDb = await usersTestUtils.usersInDb(); - - const response = await api - .post('/api/users/') - .send(userToAdd) - .expect(400) - .expect('Content-Type', /application\/json/); - - const usersInDb = await usersTestUtils.usersInDb(); - - expect(usersInDb).toHaveLength(initialUsersInDb.length); - expect(response.body.error).toEqual({ - username: 'Missing username', - }); - }); - - test('(Test custom validator) When username is containing unsupported characters like spaces, return json with error A valid username is required message', async () => { - const userToAdd = { - username: 'Sample username', - name: 'SampleName', - email: 'sample@example.com', - password: 'SamplePassword', - }; - - const initialUsersInDb = await usersTestUtils.usersInDb(); - - const response = await api - .post('/api/users/') - .send(userToAdd) - .expect(400) - .expect('Content-Type', /application\/json/); - - const usersInDb = await usersTestUtils.usersInDb(); - - expect(usersInDb).toHaveLength(initialUsersInDb.length); - - const expectedError = { - username: 'A valid username is required', - }; - - expect(response.body.error).toEqual(expectedError); - }); - - test('(Test custom validator) When email is invalid, return json with error A valid email address is required message', async () => { - const userToAdd = { - username: 'Sample', - name: 'SampleName', - email: 'sample@.com', - password: 'SamplePassword', - }; - - const initialUsersInDb = await usersTestUtils.usersInDb(); - - const response = await api - .post('/api/users/') - .send(userToAdd) - .expect(400) - .expect('Content-Type', /application\/json/); - - const usersInDb = await usersTestUtils.usersInDb(); - - expect(usersInDb).toHaveLength(initialUsersInDb.length); - - const expectedError = { - email: 'A valid email address is required', - }; - - expect(response.body.error).toEqual(expectedError); - }); - - test('(Test more than one custom validator violations) When username is containing unsupported characters like spaces and email address is invalid, return json with error A valid username is required message and a valid email address is required message', async () => { - const userToAdd = { - username: 'Sample username', - name: 'SampleName', - email: 'sample@.com', - password: 'SamplePassword', - }; - - const initialUsersInDb = await usersTestUtils.usersInDb(); - - const response = await api - .post('/api/users/') - .send(userToAdd) - .expect(400) - .expect('Content-Type', /application\/json/); - - const usersInDb = await usersTestUtils.usersInDb(); - - expect(usersInDb).toHaveLength(initialUsersInDb.length); - - const expectedError = { - username: 'A valid username is required', - email: 'A valid email address is required', - }; - - expect(response.body.error).toEqual(expectedError); - }); - - test("(Test unique validator) When username ('sample') already exists in database, return json with error username must be unique. message", async () => { - const userToAdd = { - username: 'Sample', - name: 'SampleName2', - email: 'sample2@example.com', - password: 'SamplePassword2', - }; - - await usersTestUtils.addUserToDb(usersTestUtils.sampleUserInDb1); - - const initialUsersInDb = await usersTestUtils.usersInDb(); - - const response = await api - .post('/api/users/') - .send(userToAdd) - .expect(400) - .expect('Content-Type', /application\/json/); - - const usersInDb = await usersTestUtils.usersInDb(); - - expect(usersInDb).toHaveLength(initialUsersInDb.length); - - const expectedError = { - username: 'username must be unique.', - }; - - expect(response.body.error).toEqual(expectedError); - }); - - test("(Test unique validator) When username ('sample@example.com') already exists in database, return json with error email must be unique. message", async () => { - const userToAdd = { - username: 'Sample2', - name: 'SampleName2', - email: 'sample@example.com', - password: 'SamplePassword2', - }; - - await usersTestUtils.addUserToDb(usersTestUtils.sampleUserInDb1); - - const initialUsersInDb = await usersTestUtils.usersInDb(); - - const response = await api - .post('/api/users/') - .send(userToAdd) - .expect(400) - .expect('Content-Type', /application\/json/); - - const usersInDb = await usersTestUtils.usersInDb(); - - expect(usersInDb).toHaveLength(initialUsersInDb.length); - - const expectedError = { - email: 'email must be unique.', - }; - - expect(response.body.error).toEqual(expectedError); - }); - - test('When password is missing, return json with error `password` is required message', async () => { - const userToAdd = { - username: 'Sample', - name: 'SampleName', - email: 'sample@example.com', - }; - - const initialUsersInDb = await usersTestUtils.usersInDb(); - - const response = await api - .post('/api/users/') - .send(userToAdd) - .expect(400) - .expect('Content-Type', /application\/json/); - - const usersInDb = await usersTestUtils.usersInDb(); - - expect(usersInDb).toHaveLength(initialUsersInDb.length); - expect(response.body.error).toBe('`password` is required.'); - }); - - test('When password is less than 8 characters long, return json with error Password has to be at least 8 characters long message', async () => { - const userToAdd = { - username: 'Sample', - name: 'SampleName', - email: 'sample@example.com', - password: '123456', - }; - - const initialUsersInDb = await usersTestUtils.usersInDb(); - - const response = await api - .post('/api/users/') - .send(userToAdd) - .expect(400) - .expect('Content-Type', /application\/json/); - - const usersInDb = await usersTestUtils.usersInDb(); - - expect(usersInDb).toHaveLength(initialUsersInDb.length); - expect(response.body.error).toBe( - 'Password has to be at least 8 characters long.', - ); - }); - - test('When request is valid, number of users in database increment by 1', async () => { - const userToAdd = { - username: 'Sample', - name: 'SampleName', - email: 'sample@example.com', - password: 'SamplePassword', - }; - - const initialUsersInDb = await usersTestUtils.usersInDb(); - - const response = await api - .post('/api/users/') - .send(userToAdd) - .expect(201) - .expect('Content-Type', /application\/json/); - - const usersInDb = await usersTestUtils.usersInDb(); - expect(usersInDb).toHaveLength(initialUsersInDb.length + 1); - - // Lowercase as the username and email fields - // are converted to lowercase before inserting into database. - const expectedUser = { - username: 'sample', - name: 'SampleName', - email: 'sample@example.com', - }; - - expect(response.body).toMatchObject(expectedUser); - }); - }); -}); - -afterAll(async () => { - await databaseSetupTestUtils.resetDatabase(); - await mongoose.connection.close(); -}); diff --git a/tests/utils/api/softwaresTestUtils.js b/tests/utils/api/softwareTestUtils.js similarity index 80% rename from tests/utils/api/softwaresTestUtils.js rename to tests/utils/api/softwareTestUtils.js index f20aad9..e69f9dc 100644 --- a/tests/utils/api/softwaresTestUtils.js +++ b/tests/utils/api/softwareTestUtils.js @@ -4,7 +4,7 @@ const sampleSoftwareInDb1 = { name: 'SampleSoftware', description: 'A sample software 1', homePage: 'http://example.com', - platforms: ['Windows'], + platform: 'Windows', isActiveDevelopment: true, }; @@ -12,7 +12,7 @@ const sampleSoftwareInDb2 = { name: 'SampleSoftware2', description: 'A sample software 1', homePage: 'http://apple.com', - platforms: ['MacOS'], + platform: 'MacOS', isActiveDevelopment: false, }; @@ -27,13 +27,13 @@ const addSoftwareToDb = async (softwareObject, addedByUser, updatedByUser) => { await softwareToAdd.save(); }; -const softwaresInDb = async () => { - const softwares = await Software.find({}); - return softwares; +const softwareInDb = async () => { + const software = await Software.find({}); + return software; }; module.exports = { - softwaresInDb, + softwareInDb, addSoftwareToDb, sampleSoftwareInDb1, sampleSoftwareInDb2, diff --git a/tests/utils/api/usersTestUtils.js b/tests/utils/api/usersTestUtils.js index be89d5e..5798a3f 100644 --- a/tests/utils/api/usersTestUtils.js +++ b/tests/utils/api/usersTestUtils.js @@ -1,33 +1,46 @@ const User = require('../../../models/user'); - -const usersInDb = async () => { - const users = await User.find({}); - return users; -}; +const { loginFireBase } = require('../firebaseTestUtils'); const sampleUserInDb1 = { username: 'Sample', name: 'SampleName', email: 'sample@example.com', - passwordHash: 'SamplePasswordHash', +}; + +const sampleUserCredential1 = { + ...sampleUserInDb1, + password: 'SamplePassword', }; const sampleUserInDb2 = { username: 'Sample2', name: 'SampleName2', email: 'sample2@example.com', - passwordHash: 'SamplePasswordHash2', }; -const addUserToDb = async (userObject) => { - const userToAdd = new User(userObject); +const sampleUserCredential2 = { + ...sampleUserInDb2, + password: 'SamplePassword2', +}; + +const addUserToDb = async (userCredential) => { + const { email, password } = userCredential; + + const withFirebaseId = { + ...userCredential, + firebaseUid: (await loginFireBase(email, password)).uid, + }; + delete withFirebaseId.password; + + const userToAdd = new User(withFirebaseId); const response = await userToAdd.save(); return response; }; /** - * Santize user object for expected comparison with user object returned from database. - * In other words, passwordHash is removed and the username and email address are lowercase. + * Santizes user object for expected comparison + * with user object returned from database (for testing purposes). + * In other words, the username and email address are lowercase. * @param {Object} userObject */ const sanitizeUserObject = (userObject) => { @@ -35,8 +48,6 @@ const sanitizeUserObject = (userObject) => { ...userObject, }; - delete expectedUserObject.passwordHash; - // As the username and email are stored in lowercase when inserting to database. expectedUserObject.username = expectedUserObject.username.toLowerCase(); expectedUserObject.email = expectedUserObject.email.toLowerCase(); @@ -46,8 +57,9 @@ const sanitizeUserObject = (userObject) => { module.exports = { sampleUserInDb1, + sampleUserCredential1, sampleUserInDb2, + sampleUserCredential2, addUserToDb, sanitizeUserObject, - usersInDb, }; diff --git a/tests/utils/databaseSetup.js b/tests/utils/databaseSetup.js index d746e0c..4ad98d0 100644 --- a/tests/utils/databaseSetup.js +++ b/tests/utils/databaseSetup.js @@ -1,47 +1,34 @@ -const bcrypt = require('bcrypt'); -const jwt = require('jsonwebtoken'); const User = require('../../models/user'); const Software = require('../../models/software'); -const config = require('../../utils/config'); -const usersTestUtils = require('./api/usersTestUtils'); +const firebaseTestUtils = require('./firebaseTestUtils'); +const firebaseAdmin = require('../../utils/firebaseConfig'); const resetDatabase = async () => { await User.deleteMany({}); await Software.deleteMany({}); }; -const initialiseADefaultUserInDb = async () => { - const saltRounds = config.BCRYPT_SALT_ROUNDS; - const passwordHash = await bcrypt.hash('SamplePassword', saltRounds); - - const userToAdd = { - username: 'Sample', - name: 'SampleName', - email: 'sample@example.com', - passwordHash, - }; - - const defaultUser = await usersTestUtils.addUserToDb(userToAdd); - - return defaultUser; +const usersInDb = async () => { + const users = await User.find({}); + return users; }; -const loginUserToken = (testUser) => { - const userForToken = { - username: testUser.username, - id: testUser._id, - }; +const softwareInDb = async () => { + const software = await Software.find({}); + return software; +}; - const token = jwt.sign(userForToken, config.JWT_SECRET); +const setBackendIdOfDefaultUser = async (userCredential, backendId) => { + const { email, password } = userCredential; + const { uid } = await firebaseTestUtils.loginFireBase(email, password); - return token; + // Set custom claim with backend user id (different from firebase user id) + await firebaseAdmin.auth().setCustomUserClaims(uid, { backendId }); }; -const formattedToken = (token) => `bearer ${token}`; - module.exports = { resetDatabase, - initialiseADefaultUserInDb, - loginUserToken, - formattedToken, + usersInDb, + softwareInDb, + setBackendIdOfDefaultUser, }; diff --git a/tests/utils/firebaseTestUtils.js b/tests/utils/firebaseTestUtils.js new file mode 100644 index 0000000..6b7cc6d --- /dev/null +++ b/tests/utils/firebaseTestUtils.js @@ -0,0 +1,18 @@ +const axios = require('axios'); +const config = require('../../utils/config'); + +const loginFireBase = async (email, password) => { + const response = await axios.post( + `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${config.TEST_FIREBASE_CLIENT_API_KEY}`, + { email, password, returnSecureToken: true }, + ); + + return { + idToken: response.data.idToken, + uid: response.data.localId, + }; +}; + +module.exports = { + loginFireBase, +}; diff --git a/utils/config.js b/utils/config.js index b7ce73d..5350627 100644 --- a/utils/config.js +++ b/utils/config.js @@ -8,13 +8,11 @@ const NODE_ENVIRONMENT = process.env.NODE_ENV; const MONGODB_URI = NODE_ENVIRONMENT === 'test' ? process.env.TEST_MONGODB_URI : process.env.MONGODB_URI; -const BCRYPT_SALT_ROUNDS = Number(process.env.BCRYPT_SALT_ROUNDS); -const { JWT_SECRET } = process.env; +const { TEST_FIREBASE_CLIENT_API_KEY } = process.env; module.exports = { PORT, NODE_ENVIRONMENT, MONGODB_URI, - BCRYPT_SALT_ROUNDS, - JWT_SECRET, + TEST_FIREBASE_CLIENT_API_KEY, }; diff --git a/utils/firebaseConfig.js b/utils/firebaseConfig.js new file mode 100644 index 0000000..0828e2c --- /dev/null +++ b/utils/firebaseConfig.js @@ -0,0 +1,13 @@ +/** + * Handles firebase configuration and implementation + */ + +require('dotenv').config(); +const firebaseAdmin = require('firebase-admin'); + +firebaseAdmin.initializeApp({ + credential: firebaseAdmin.credential.applicationDefault(), + databaseURL: 'https://auth-impl-dev.firebaseio.com', +}); + +module.exports = firebaseAdmin; diff --git a/utils/jwtUtils.js b/utils/jwtUtils.js index 557aeb1..76d48e8 100644 --- a/utils/jwtUtils.js +++ b/utils/jwtUtils.js @@ -1,5 +1,4 @@ -const jwt = require('jsonwebtoken'); -const config = require('./config'); +const firebaseAdmin = require('./firebaseConfig'); const User = require('../models/user'); const getReqAuthToken = (req) => { @@ -13,18 +12,29 @@ const getReqAuthToken = (req) => { }; /** - * Verify the input jwt token. + * Verify the input jwt Authorisation bearer token. * Returns null if invalid/missing token or the user id in the payload is not found in database. - * If the token is valid and the user id is valid, returns the decoded token. - * @param {String} authToken The jwt user token + * If checkDatabase parameter is set to true, there is + * additional check whether the firebase user id is in database. + * @param {String} authToken The jwt Authorisation bearer token + * @param {Boolean} checkDatabase Whether to check database if + * the firebase user id is present in the database. */ -const verifyAuthToken = async (authToken) => { - const decodedAuthToken = !authToken - ? null - : jwt.verify(authToken, config.JWT_SECRET); +const verifyAuthToken = async (authToken, checkDatabase) => { + // Return null, it is missing token + if (!authToken) { + return null; + } + + const decodedAuthToken = await firebaseAdmin + .auth() + .verifyIdToken(authToken, true); + + const user = await User.findOne({ firebaseUid: decodedAuthToken.uid }); - // Return null, it is an invalid token or the user id is not found in database. - if (!authToken || !(await User.findById(decodedAuthToken.id))) { + // If the checkDatabase parameter is set to true + // and the user id is not found in database, return null. + if (checkDatabase && !user) { return null; } diff --git a/utils/middleware.js b/utils/middleware.js index e04c340..2a961d4 100644 --- a/utils/middleware.js +++ b/utils/middleware.js @@ -14,18 +14,31 @@ const { getReqAuthToken, verifyAuthToken } = require('./jwtUtils'); * @param {Function} next Function which can be called to pass controls to the next handler */ const tokenValidation = async (req, res, next) => { - const token = getReqAuthToken(req); - const decodedToken = await verifyAuthToken(token); + try { + const token = getReqAuthToken(req); - if (!decodedToken) { + const decodedToken = await verifyAuthToken(token, true); + + if (!decodedToken) { + return res.status(401).json({ + error: 'Missing Token', + }); + } + + req.body.decodedToken = decodedToken; + + next(); + } catch (err) { + let error = null; + if (err.code === 'auth/id-token-revoked') { + error = 'Token has been revoked. Please reauthenticate.'; + } else { + error = 'Invalid Token'; + } return res.status(401).json({ - error: 'Missing or Invalid Token', + error, }); } - - req.body.decodedToken = decodedToken; - - next(); }; const unknownEndPoint = (req, res) => { @@ -34,6 +47,12 @@ const unknownEndPoint = (req, res) => { }); }; +const disabledAPIEndPoint = (req, res) => { + res.status(403).json({ + error: 'API endpoint disabled', + }); +}; + const errorHandler = (error, req, res, next) => { if (error.name === 'ValidationError') { const errorObject = {}; @@ -42,11 +61,7 @@ const errorHandler = (error, req, res, next) => { }); return res.status(400).json({ error: errorObject }); } - if (error.name === 'JsonWebTokenError') { - return res.status(401).json({ - error: 'Invalid Token', - }); - } + if (error.name === 'CastError') { return res.status(400).json({ error: 'Malformatted id', @@ -61,4 +76,5 @@ module.exports = { unknownEndPoint, errorHandler, tokenValidation, + disabledAPIEndPoint, }; From c74a737034b2fdffccd24cb38ff26a5396d01633 Mon Sep 17 00:00:00 2001 From: JonathanLeeWH Date: Tue, 10 Nov 2020 14:08:57 +0800 Subject: [PATCH 02/11] Amended continuous integration script to support firebase Authentication Improved app-test-container.yml GitHub Actions to support firebase Authentication Added new environmental variables introduced in migration to firebase Authentication in Dockerfile and remove unused environmental variables Improved .gitignore to avoid committing Google service account file for security purposes Added test-ci script in package.json for continuous integration usage Improved comments in software.test.js Added firebase.gpg Added Shell script descrypt_firebase_secret.sh for firebase.gpg decrpytion (This is for continuous integration only) Added Dockerfile-ci which is a separate dockerfile used only by continuous integration Improved docker-compose-test-ci with firebase Authentication support Refactor docker ci files to .github directory --- .github/ci/Dockerfile-ci | 34 ++++++++++++++++++ .../ci/docker-compose-test-ci.yml | 13 ++++--- .github/ci/firebase.gpg | Bin 0 -> 1736 bytes .github/scripts/decrypt_firebase_secret.sh | 7 ++++ .github/workflows/app-test-container.yml | 10 +++++- .gitignore | 3 ++ Dockerfile | 4 +-- package.json | 1 + tests/api/software.test.js | 4 +-- 9 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 .github/ci/Dockerfile-ci rename docker-compose-test-ci.yml => .github/ci/docker-compose-test-ci.yml (50%) create mode 100644 .github/ci/firebase.gpg create mode 100644 .github/scripts/decrypt_firebase_secret.sh diff --git a/.github/ci/Dockerfile-ci b/.github/ci/Dockerfile-ci new file mode 100644 index 0000000..d4457db --- /dev/null +++ b/.github/ci/Dockerfile-ci @@ -0,0 +1,34 @@ +# SoftwareRepository's Dockerfile +# With comments to aid in my learning of docker. + +# To use official nodejs base docker image. +FROM node:12 + +# The working directory where any subsequent instructions in the Dockerfile will be executed on. +WORKDIR /app + +# I want to install dependencies first so they can be cache. +# Hence, I copy the package.json file first to the work directory for installing the dependencies in the subsequent command (npm install). +COPY package.json . + +# Run npm install to install the dependencies specified in package.json +RUN npm install + +# After the dependencies are installed, I copy over the source code of the web application to the current working directory. +# Note: In this case, I add .dockerignore file (similar to .gitignore) and add node_modules to the .dockerignore file so that my local node_modules directory will not be copied to the container's working directory. +COPY [".", "${HOME}/secrets", "./"] + +# The container environmental variables. +ENV PORT=8080 +ENV MONGODB_URI=mongodb://root:password@mongo:27017/softwareRepository?authSource=admin +ENV TEST_MONGODB_URI=mongodb://root:password@mongo:27017/softwareRepositoryTest?authSource=admin +ENV TEST_FIREBASE_CLIENT_API_KEY=YOUR_TEST_FIREBASE_CLIENT_API_KEY +ENV GOOGLE_APPLICATION_CREDENTIALS=YOUR_GOOGLE_SERVICE_ACCOUNT_FILE_PATH + +# The container listens on port 8080. +EXPOSE 8080 + +# Should only have one cmd in a Dockerfile. Tells container how to run the application. +# In this case, the command is npm start. +# An exec form (Array of strings). It does not start up a shell session unlike run. +CMD ["npm", "start"] \ No newline at end of file diff --git a/docker-compose-test-ci.yml b/.github/ci/docker-compose-test-ci.yml similarity index 50% rename from docker-compose-test-ci.yml rename to .github/ci/docker-compose-test-ci.yml index 50cd910..da9993e 100644 --- a/docker-compose-test-ci.yml +++ b/.github/ci/docker-compose-test-ci.yml @@ -1,4 +1,4 @@ -version: "3.8" +version: '3.8' services: mongo: @@ -9,14 +9,19 @@ services: MONGO_INITDB_ROOT_PASSWORD: password volumes: - db-data:/data/db - + app: depends_on: - mongo - build: . + build: + context: './.github/ci' + dockerfile: Dockerfile-ci + environment: + - TEST_FIREBASE_CLIENT_API_KEY=${TEST_FIREBASE_CLIENT_API_KEY} + - GOOGLE_APPLICATION_CREDENTIALS=${GOOGLE_APPLICATION_CREDENTIALS} ports: - 8080:8080 - command: "npm test" + command: 'npm run test-ci' volumes: db-data: diff --git a/.github/ci/firebase.gpg b/.github/ci/firebase.gpg new file mode 100644 index 0000000000000000000000000000000000000000..7a2e0df01624b8158d8727eb09e1eabd3104a1ba GIT binary patch literal 1736 zcmV;(1~>VP4Fm}T0^vLpHD5xxeE-tw0W|4GwA7g!{5A57%zmopG^+jG@_k|d1|WU> zq2$PvG_(5uaa>JBE0J<*4X9Ew5nhE6=q+eIi!jSZ4HaY0t`&bz!U!g>{y8RkvlSz4 zTQHF)@!bLzN&Ga6ynszEi;&#NVm$fGz7f+W=QQ6GXljXfs5m*sv2MtL=f*Ll2)7JX zLm1uP(OTxpx<0sxlJR-d0YmlvC4ggT4pygN{@F{21BtQ~wBs$qis;n{tJJt}y+c)r zwVchQAy-dJQ|Uji7Pfl{G^1=Sb06_4MklxS75+Ch#>VzbS+;^E;;+#23$5{W&p=Ju zsLOA!D7!nU6+UyN!33+-g4pk$r2k#eu)^66MtpV&>fGRm(_)bClcY6RIW`PXK?NKt zu0Vz3O=4O0vT@%TTz5;iJ;&$oP@{nOB9fcdLMdH=(r;S|JfE@Sz1@6oO)e#MpK46p zar3!H8q*;!&hdrS*+sfgZby_QS-TAfO3EkH@;9Dmi{*XpHr^-n!vkajEP>n z>VyyM5k--QOw6sZ)krF#N}iD@xzGBwhgeh20hyn3T5k~SlA-^nGyQ zp-L?ptHU#PupZ9jfA?V$zxAidBYGw7?Wb4%O=Q6%*Yf3X#rE67uOfz1E8a2Ze8545 z&ZgPvgm(}sDS*p_CRni#Eo3npg;aWT`6xp^Xs1KQe`csyO?UB+pCjQH~fr=YJVb#l3>hElJjq z_yQ)muNg{@7k8s2v0L6Qfkb zY;A=g$FPb2`?ymA z&onVy0_x^kZ0jY_cCdh7kPyes9c_L5=JrskLbA@FiA%n6!88WC41dC26JO1WV6_oe zN$reOXpII9yn1sh@xO;&OIRKyJb zKXlamAc`qMj=<~P?>tIY(2fHGnbNGZ_)k^=4QcIS^%O;Vk`!q3%hW>Ha3|9YCw$Sc zSrDrY{=4Oj_qnoK!jEclUZd}yA={-YUT7<>$$Tf2pSKu5mbuOufa9BLL{a^*0+VRtS7H=f;^7n6;hDW;;7HC(m96 zKQD#~d9`B|>0fTYM~0{)3_r4IRDYk%Hy4kXGU%=LSq*ED^{2~-3;I9DUrqR8Z{}TC en3Jv$O}yfTfmPE^rL<~{f+OF8(VjF5K6IU#DPEfZ literal 0 HcmV?d00001 diff --git a/.github/scripts/decrypt_firebase_secret.sh b/.github/scripts/decrypt_firebase_secret.sh new file mode 100644 index 0000000..77a7a4a --- /dev/null +++ b/.github/scripts/decrypt_firebase_secret.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# Script to decrypt firebase secret + +# Create the environment and decrypt the file +mkdir $HOME/secrets +gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTION_PASSPHRASE" --output $HOME/secrets/firebase_secret.json ./.github/ci/firebase.gpg \ No newline at end of file diff --git a/.github/workflows/app-test-container.yml b/.github/workflows/app-test-container.yml index f972f5a..83b2a47 100644 --- a/.github/workflows/app-test-container.yml +++ b/.github/workflows/app-test-container.yml @@ -22,5 +22,13 @@ jobs: - name: docker layer caching uses: satackey/action-docker-layer-caching@v0.0.8 continue-on-error: true + - name: descrypt firebase secret + run: ./.github/scripts/decrypt_firebase_secret.sh + env: + DECRYPTION_PASSPHRASE: ${{ secrets.DECRYPTION_PASSPHRASE }} - name: Run test in container - run: docker-compose --file docker-compose-test-ci.yml up --build --exit-code-from app + shell: bash + env: + TEST_FIREBASE_CLIENT_API_KEY: ${{ secrets.TEST_FIREBASE_CLIENT_API_KEY }} + GOOGLE_APPLICATION_CREDENTIALS: ./.github/ci/firebase.gpg + run: docker-compose --file ./.github/ci/docker-compose-test.yml up --build --exit-code-from app diff --git a/.gitignore b/.gitignore index 6704566..2bddac9 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,6 @@ dist # TernJS port file .tern-port + +# custom +*firebase-adminsdk*.json \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1209681..4798ca7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,9 +21,9 @@ COPY . . # The container environmental variables. ENV PORT=8080 ENV MONGODB_URI=mongodb://root:password@mongo:27017/softwareRepository?authSource=admin -ENV BCRYPT_SALT_ROUNDS=YOUR_BCRYPT_SALT_ROUNDS ENV TEST_MONGODB_URI=mongodb://root:password@mongo:27017/softwareRepositoryTest?authSource=admin -ENV JWT_SECRET=YOUR_JWT_SECRET +ENV TEST_FIREBASE_CLIENT_API_KEY=YOUR_TEST_FIREBASE_CLIENT_API_KEY +ENV GOOGLE_APPLICATION_CREDENTIALS=YOUR_GOOGLE_SERVICE_ACCOUNT_FILE_PATH # The container listens on port 8080. EXPOSE 8080 diff --git a/package.json b/package.json index 199fa71..12c1f5f 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "test": "cross-env NODE_ENV=test jest --verbose --runInBand", "test-debug": "cross-env NODE_ENV=test jest --verbose --runInBand --detectOpenHandles", "test-coverage": "cross-env CI=true npm test -- --coverage", + "test-ci": "cross-env NODE_ENV=test jest --verbose --runInBand --forceExit", "lint": "eslint . --fix" }, "repository": { diff --git a/tests/api/software.test.js b/tests/api/software.test.js index e617dd3..c21ba8e 100644 --- a/tests/api/software.test.js +++ b/tests/api/software.test.js @@ -142,7 +142,7 @@ describe('Software Controller', () => { describe('Software Controller', () => { describe('POST request to /api/software/', () => { - test('When missing Authorisation token, return with status 401 with json Missing or Invalid Token error message, no change in the number of softwares in database', async () => { + test('When missing Authorisation token, return with status 401 with json Missing Token error message, no change in the number of softwares in database', async () => { const initialSoftwareInDb = await databaseSetup.softwareInDb(); const softwareToAdd = { @@ -170,7 +170,7 @@ describe('Software Controller', () => { expect(response.body.error).toBe('Missing Token'); }); - test('When invalid Authorisation token, return with status 401 with json Token missing or invalid error message, no change in the number of softwares in database', async () => { + test('When invalid Authorisation token, return with status 401 with json Invalid Token error message, no change in the number of softwares in database', async () => { const initialSoftwareInDb = await databaseSetup.softwareInDb(); const softwareToAdd = { From 9838092c3b8931f2c2085648aef0e9cf4c7fab87 Mon Sep 17 00:00:00 2001 From: JonathanLeeWH Date: Tue, 10 Nov 2020 14:28:54 +0800 Subject: [PATCH 03/11] Add execute bit for decrypt_firebase_secret.sh file --- .github/scripts/decrypt_firebase_secret.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .github/scripts/decrypt_firebase_secret.sh diff --git a/.github/scripts/decrypt_firebase_secret.sh b/.github/scripts/decrypt_firebase_secret.sh old mode 100644 new mode 100755 From d37787bfd7ede24369d0a37006185bf37d471a25 Mon Sep 17 00:00:00 2001 From: JonathanLeeWH Date: Tue, 10 Nov 2020 14:32:37 +0800 Subject: [PATCH 04/11] Resolved a bug with docker-compose file name used in continuous integration --- .github/workflows/app-test-container.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/app-test-container.yml b/.github/workflows/app-test-container.yml index 83b2a47..e664329 100644 --- a/.github/workflows/app-test-container.yml +++ b/.github/workflows/app-test-container.yml @@ -31,4 +31,4 @@ jobs: env: TEST_FIREBASE_CLIENT_API_KEY: ${{ secrets.TEST_FIREBASE_CLIENT_API_KEY }} GOOGLE_APPLICATION_CREDENTIALS: ./.github/ci/firebase.gpg - run: docker-compose --file ./.github/ci/docker-compose-test.yml up --build --exit-code-from app + run: docker-compose --file ./.github/ci/docker-compose-test-ci.yml up --build --exit-code-from app From e6ceb5ab73b4bca739d1bbc490129f170ac2a65b Mon Sep 17 00:00:00 2001 From: JonathanLeeWH Date: Tue, 10 Nov 2020 14:41:33 +0800 Subject: [PATCH 05/11] Resolved context for docker-compose-test-ci.yml --- .github/ci/docker-compose-test-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ci/docker-compose-test-ci.yml b/.github/ci/docker-compose-test-ci.yml index da9993e..ad10832 100644 --- a/.github/ci/docker-compose-test-ci.yml +++ b/.github/ci/docker-compose-test-ci.yml @@ -14,7 +14,7 @@ services: depends_on: - mongo build: - context: './.github/ci' + context: '.github/ci' dockerfile: Dockerfile-ci environment: - TEST_FIREBASE_CLIENT_API_KEY=${TEST_FIREBASE_CLIENT_API_KEY} From 6cd75be91f99b6552d71b59582643a3d97926c23 Mon Sep 17 00:00:00 2001 From: JonathanLeeWH Date: Tue, 10 Nov 2020 14:44:05 +0800 Subject: [PATCH 06/11] Resolved docker-compose-test-ci.yml context bug not resolved in previous commit --- .github/ci/docker-compose-test-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ci/docker-compose-test-ci.yml b/.github/ci/docker-compose-test-ci.yml index ad10832..14a6faa 100644 --- a/.github/ci/docker-compose-test-ci.yml +++ b/.github/ci/docker-compose-test-ci.yml @@ -14,7 +14,7 @@ services: depends_on: - mongo build: - context: '.github/ci' + context: './' dockerfile: Dockerfile-ci environment: - TEST_FIREBASE_CLIENT_API_KEY=${TEST_FIREBASE_CLIENT_API_KEY} From 7c651b42a810522d432b12079a64a1f3976e790a Mon Sep 17 00:00:00 2001 From: JonathanLeeWH Date: Tue, 10 Nov 2020 14:47:57 +0800 Subject: [PATCH 07/11] Refactor dockerfile-ci to main project directory --- .github/ci/Dockerfile-ci => Dockerfile-ci | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/ci/Dockerfile-ci => Dockerfile-ci (100%) diff --git a/.github/ci/Dockerfile-ci b/Dockerfile-ci similarity index 100% rename from .github/ci/Dockerfile-ci rename to Dockerfile-ci From a436ec6cd67caa56ab7d16b14dee939508c07e8f Mon Sep 17 00:00:00 2001 From: JonathanLeeWH Date: Tue, 10 Nov 2020 14:54:11 +0800 Subject: [PATCH 08/11] Resolved docker-compose-test-ci.yml --- .github/ci/docker-compose-test-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ci/docker-compose-test-ci.yml b/.github/ci/docker-compose-test-ci.yml index 14a6faa..86e3344 100644 --- a/.github/ci/docker-compose-test-ci.yml +++ b/.github/ci/docker-compose-test-ci.yml @@ -14,7 +14,7 @@ services: depends_on: - mongo build: - context: './' + context: './${GITHUB_WORKSPACE}' dockerfile: Dockerfile-ci environment: - TEST_FIREBASE_CLIENT_API_KEY=${TEST_FIREBASE_CLIENT_API_KEY} From e8a54ea77e3a6858784eb1bbd0f4ad373bdc3a4c Mon Sep 17 00:00:00 2001 From: JonathanLeeWH Date: Tue, 10 Nov 2020 14:56:06 +0800 Subject: [PATCH 09/11] Resolved build context docker-compose-test-ci.yml --- .github/ci/docker-compose-test-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ci/docker-compose-test-ci.yml b/.github/ci/docker-compose-test-ci.yml index 86e3344..9dc4ef0 100644 --- a/.github/ci/docker-compose-test-ci.yml +++ b/.github/ci/docker-compose-test-ci.yml @@ -14,7 +14,7 @@ services: depends_on: - mongo build: - context: './${GITHUB_WORKSPACE}' + context: '${GITHUB_WORKSPACE}' dockerfile: Dockerfile-ci environment: - TEST_FIREBASE_CLIENT_API_KEY=${TEST_FIREBASE_CLIENT_API_KEY} From 055b85fae8c353b51b68ccce6b348c9ab488f771 Mon Sep 17 00:00:00 2001 From: JonathanLeeWH Date: Tue, 10 Nov 2020 16:54:55 +0800 Subject: [PATCH 10/11] Added custom .dockerignore file --- .github/scripts/decrypt_firebase_secret.sh | 2 +- .github/workflows/app-test-container.yml | 2 +- Dockerfile-ci | 2 +- Dockerfile-ci.dockerignore | 16 ++++++++++++++++ 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 Dockerfile-ci.dockerignore diff --git a/.github/scripts/decrypt_firebase_secret.sh b/.github/scripts/decrypt_firebase_secret.sh index 77a7a4a..9a12a4a 100755 --- a/.github/scripts/decrypt_firebase_secret.sh +++ b/.github/scripts/decrypt_firebase_secret.sh @@ -4,4 +4,4 @@ # Create the environment and decrypt the file mkdir $HOME/secrets -gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTION_PASSPHRASE" --output $HOME/secrets/firebase_secret.json ./.github/ci/firebase.gpg \ No newline at end of file +gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTION_PASSPHRASE" --output ./.github/ci/firebase_secret.json ./.github/ci/firebase.gpg \ No newline at end of file diff --git a/.github/workflows/app-test-container.yml b/.github/workflows/app-test-container.yml index e664329..223fd2d 100644 --- a/.github/workflows/app-test-container.yml +++ b/.github/workflows/app-test-container.yml @@ -30,5 +30,5 @@ jobs: shell: bash env: TEST_FIREBASE_CLIENT_API_KEY: ${{ secrets.TEST_FIREBASE_CLIENT_API_KEY }} - GOOGLE_APPLICATION_CREDENTIALS: ./.github/ci/firebase.gpg + GOOGLE_APPLICATION_CREDENTIALS: ./.github/ci/firebase_secret.json run: docker-compose --file ./.github/ci/docker-compose-test-ci.yml up --build --exit-code-from app diff --git a/Dockerfile-ci b/Dockerfile-ci index d4457db..4798ca7 100644 --- a/Dockerfile-ci +++ b/Dockerfile-ci @@ -16,7 +16,7 @@ RUN npm install # After the dependencies are installed, I copy over the source code of the web application to the current working directory. # Note: In this case, I add .dockerignore file (similar to .gitignore) and add node_modules to the .dockerignore file so that my local node_modules directory will not be copied to the container's working directory. -COPY [".", "${HOME}/secrets", "./"] +COPY . . # The container environmental variables. ENV PORT=8080 diff --git a/Dockerfile-ci.dockerignore b/Dockerfile-ci.dockerignore new file mode 100644 index 0000000..82d6402 --- /dev/null +++ b/Dockerfile-ci.dockerignore @@ -0,0 +1,16 @@ +node_modules/ +.git/ +requests/ +coverage/ +build/ + +.* +*.md +# tests* +docker-compose.yml + + +# To ignore .env +.env + +!.github/ci \ No newline at end of file From 0e5b1234b9673bd8c32ccc91dd97a593d9cf2c6b Mon Sep 17 00:00:00 2001 From: JonathanLeeWH Date: Tue, 10 Nov 2020 17:21:58 +0800 Subject: [PATCH 11/11] Attempt Bug fix for continuous integration --- .github/scripts/decrypt_firebase_secret.sh | 2 +- .github/workflows/app-test-container.yml | 2 +- Dockerfile-ci.dockerignore | 16 ---------------- 3 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 Dockerfile-ci.dockerignore diff --git a/.github/scripts/decrypt_firebase_secret.sh b/.github/scripts/decrypt_firebase_secret.sh index 9a12a4a..d53d3cf 100755 --- a/.github/scripts/decrypt_firebase_secret.sh +++ b/.github/scripts/decrypt_firebase_secret.sh @@ -4,4 +4,4 @@ # Create the environment and decrypt the file mkdir $HOME/secrets -gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTION_PASSPHRASE" --output ./.github/ci/firebase_secret.json ./.github/ci/firebase.gpg \ No newline at end of file +gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTION_PASSPHRASE" --output firebase_secret.json ./.github/ci/firebase.gpg \ No newline at end of file diff --git a/.github/workflows/app-test-container.yml b/.github/workflows/app-test-container.yml index 223fd2d..0ae98e2 100644 --- a/.github/workflows/app-test-container.yml +++ b/.github/workflows/app-test-container.yml @@ -30,5 +30,5 @@ jobs: shell: bash env: TEST_FIREBASE_CLIENT_API_KEY: ${{ secrets.TEST_FIREBASE_CLIENT_API_KEY }} - GOOGLE_APPLICATION_CREDENTIALS: ./.github/ci/firebase_secret.json + GOOGLE_APPLICATION_CREDENTIALS: ./firebase_secret.json run: docker-compose --file ./.github/ci/docker-compose-test-ci.yml up --build --exit-code-from app diff --git a/Dockerfile-ci.dockerignore b/Dockerfile-ci.dockerignore deleted file mode 100644 index 82d6402..0000000 --- a/Dockerfile-ci.dockerignore +++ /dev/null @@ -1,16 +0,0 @@ -node_modules/ -.git/ -requests/ -coverage/ -build/ - -.* -*.md -# tests* -docker-compose.yml - - -# To ignore .env -.env - -!.github/ci \ No newline at end of file