diff --git a/.github/ci/docker-compose-test-ci.yml b/.github/ci/docker-compose-test-ci.yml new file mode 100644 index 0000000..9dc4ef0 --- /dev/null +++ b/.github/ci/docker-compose-test-ci.yml @@ -0,0 +1,27 @@ +version: '3.8' + +services: + mongo: + image: mongo + restart: always + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: password + volumes: + - db-data:/data/db + + app: + depends_on: + - mongo + build: + context: '${GITHUB_WORKSPACE}' + 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 run test-ci' + +volumes: + db-data: diff --git a/.github/ci/firebase.gpg b/.github/ci/firebase.gpg new file mode 100644 index 0000000..7a2e0df Binary files /dev/null and b/.github/ci/firebase.gpg differ diff --git a/.github/scripts/decrypt_firebase_secret.sh b/.github/scripts/decrypt_firebase_secret.sh new file mode 100755 index 0000000..d53d3cf --- /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 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..0ae98e2 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: ./firebase_secret.json + run: docker-compose --file ./.github/ci/docker-compose-test-ci.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/Dockerfile-ci b/Dockerfile-ci new file mode 100644 index 0000000..4798ca7 --- /dev/null +++ b/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 . . + +# 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/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/docker-compose-test-ci.yml b/docker-compose-test-ci.yml deleted file mode 100644 index 50cd910..0000000 --- a/docker-compose-test-ci.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: "3.8" - -services: - mongo: - image: mongo - restart: always - environment: - MONGO_INITDB_ROOT_USERNAME: root - MONGO_INITDB_ROOT_PASSWORD: password - volumes: - - db-data:/data/db - - app: - depends_on: - - mongo - build: . - ports: - - 8080:8080 - command: "npm test" - -volumes: - db-data: 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 7962244..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", @@ -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", @@ -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", @@ -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,7 +8726,6 @@ "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": { @@ -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 7a766c8..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": { @@ -31,6 +32,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,6 +40,7 @@ "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.13", "mongoose-unique-validator": "^2.0.3", @@ -46,7 +49,7 @@ }, "devDependencies": { "eslint": "^7.13.0", - "eslint-config-airbnb-base": "^14.2.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 68% rename from tests/api/software/softwares.test.js rename to tests/api/software.test.js index 91c9822..c21ba8e 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/', () => { - 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(); + describe('POST request to /api/software/', () => { + 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 = { ...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(); + 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 = { ...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, };