From 3969cb0187dc6377a55a122857363b9719165d58 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Tue, 24 Aug 2021 23:08:24 +0000 Subject: [PATCH 01/65] feat: switch over to passport.js This adds username/password based authentication, using a cookie when accessing secure routes --- .env.example | 4 - README.md | 1 - db/migrations/20210221065805-create-group.js | 27 - .../20210225043202-create-shipment.js | 55 - .../20210302043126-add-group-type.js | 17 - db/migrations/20210302222654-update-groups.js | 39 - .../20210303082436-create-user-accounts.js | 34 - .../20210308070729-add-captain-to-group.js | 15 - db/migrations/20210310024724-create-offers.js | 49 - ...210314171740-index-groups-on-captain-id.js | 11 - ...10314172017-index-offers-on-shipment-id.js | 11 - .../20210314222712-create-pallets.js | 45 - .../20210315214429-create-line-items.js | 100 - ...0316064400-rename-line-item-box-columns.js | 21 - ...20210323060940-add-pricing-to-shipments.js | 13 - .../20210402074831-create-shipment-exports.js | 40 - db/seeders/20210302050835-demo-groups.js | 2 +- docs/auth0.md | 7 - .../src/components/UserProfileContext.tsx | 2 +- package.json | 61 +- schema.graphql | 1 + src/apolloServer.ts | 14 +- src/authenticateRequest.ts | 181 +- src/findOrCreateProfile.ts | 48 - src/getProfile.ts | 37 + src/login.ts | 35 + src/models/shipment_export.ts | 1 + src/models/user_account.ts | 15 +- src/registerUser.ts | 23 + src/sendShipmentExportCsv.ts | 4 +- src/server.ts | 18 +- src/testServer.ts | 8 +- src/tests/groups_api.test.ts | 28 +- src/tests/helpers/index.ts | 6 +- src/tests/line_items_api.test.ts | 4 +- src/tests/offers_api.test.ts | 4 +- src/tests/pallets_api.test.ts | 4 +- src/tests/shipment_exports_api.test.ts | 4 +- src/tests/user_account_api.test.ts | 114 ++ yarn.lock | 1635 +++++++++-------- 40 files changed, 1191 insertions(+), 1547 deletions(-) delete mode 100644 db/migrations/20210221065805-create-group.js delete mode 100644 db/migrations/20210225043202-create-shipment.js delete mode 100644 db/migrations/20210302043126-add-group-type.js delete mode 100644 db/migrations/20210302222654-update-groups.js delete mode 100644 db/migrations/20210303082436-create-user-accounts.js delete mode 100644 db/migrations/20210308070729-add-captain-to-group.js delete mode 100644 db/migrations/20210310024724-create-offers.js delete mode 100644 db/migrations/20210314171740-index-groups-on-captain-id.js delete mode 100644 db/migrations/20210314172017-index-offers-on-shipment-id.js delete mode 100644 db/migrations/20210314222712-create-pallets.js delete mode 100644 db/migrations/20210315214429-create-line-items.js delete mode 100644 db/migrations/20210316064400-rename-line-item-box-columns.js delete mode 100644 db/migrations/20210323060940-add-pricing-to-shipments.js delete mode 100644 db/migrations/20210402074831-create-shipment-exports.js delete mode 100644 docs/auth0.md delete mode 100644 src/findOrCreateProfile.ts create mode 100644 src/getProfile.ts create mode 100644 src/login.ts create mode 100644 src/registerUser.ts create mode 100644 src/tests/user_account_api.test.ts diff --git a/.env.example b/.env.example index cc8125e95..2babf4c24 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,2 @@ -# AUTH_MODE -# skips authenticating GraphQL requests with Auth0 when set to "SKIP" -# AUTH_MODE=SKIP - # The URL of the web app, used to restrict CORS requests CLIENT_URL="http://localhost:8080" \ No newline at end of file diff --git a/README.md b/README.md index 1594403e0..e256aa4eb 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,6 @@ See [the README in the `frontend` directory](/frontend/README.md) for instructio - [Database Migrations](./docs/migrations.md) - [Graphql Codegen](./docs/codegen.md) -- [Auth0 configuration](./docs/auth0.md) ### Type definitions diff --git a/db/migrations/20210221065805-create-group.js b/db/migrations/20210221065805-create-group.js deleted file mode 100644 index 16b0dcca4..000000000 --- a/db/migrations/20210221065805-create-group.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict' -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('Groups', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - name: { - type: Sequelize.STRING, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - }, - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Groups') - }, -} diff --git a/db/migrations/20210225043202-create-shipment.js b/db/migrations/20210225043202-create-shipment.js deleted file mode 100644 index c26fabd6f..000000000 --- a/db/migrations/20210225043202-create-shipment.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict' -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('Shipments', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - shippingRoute: { - allowNull: false, - type: Sequelize.STRING, - }, - labelYear: { - allowNull: false, - type: Sequelize.INTEGER, - }, - labelMonth: { - allowNull: false, - type: Sequelize.INTEGER, - }, - offerSubmissionDeadline: { - type: Sequelize.DATE, - }, - status: { - allowNull: false, - type: Sequelize.STRING, - }, - sendingHubs: { - type: Sequelize.INTEGER, - references: { model: 'Groups', key: 'id' }, - }, - receivingHubId: { - type: Sequelize.INTEGER, - references: { model: 'Groups', key: 'id' }, - }, - statusChangeTime: { - allowNull: false, - type: Sequelize.DATE, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - }, - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Shipments') - }, -} diff --git a/db/migrations/20210302043126-add-group-type.js b/db/migrations/20210302043126-add-group-type.js deleted file mode 100644 index 3f8fe328d..000000000 --- a/db/migrations/20210302043126-add-group-type.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - return await queryInterface.addColumn( - 'Groups', // Table name - 'groupType', // Field name - { - type: Sequelize.DataTypes.STRING, - }, - ) - }, - - down: async (queryInterface, Sequelize) => { - return await queryInterface.removeColumn('Groups', 'groupType') - }, -} diff --git a/db/migrations/20210302222654-update-groups.js b/db/migrations/20210302222654-update-groups.js deleted file mode 100644 index 034fb5e18..000000000 --- a/db/migrations/20210302222654-update-groups.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - return queryInterface.sequelize.transaction((t) => { - return Promise.all([ - queryInterface.addColumn('Groups', 'primaryLocation', { - allowNull: false, - type: Sequelize.JSONB, - }), - queryInterface.addColumn('Groups', 'website', { - type: Sequelize.STRING, - }), - queryInterface.addColumn('Groups', 'primaryContact', { - allowNull: false, - type: Sequelize.JSONB, - }), - queryInterface.changeColumn('Groups', 'name', { - allowNull: false, - type: Sequelize.STRING, - }), - ]) - }) - }, - - down: async (queryInterface, Sequelize) => { - return queryInterface.sequelize.transaction((t) => { - return Promise.all([ - queryInterface.removeColumn('Groups', 'primaryLocation'), - queryInterface.removeColumn('Groups', 'primaryContact'), - queryInterface.removeColumn('Groups', 'website'), - queryInterface.changeColumn('Groups', 'name', { - allowNull: true, - type: Sequelize.STRING, - }), - ]) - }) - }, -} diff --git a/db/migrations/20210303082436-create-user-accounts.js b/db/migrations/20210303082436-create-user-accounts.js deleted file mode 100644 index 5081cd818..000000000 --- a/db/migrations/20210303082436-create-user-accounts.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict' - -const { createInterface } = require('readline') - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('UserAccounts', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - auth0Id: { - allowNull: false, - type: Sequelize.STRING, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - - await queryInterface.addIndex('UserAccounts', ['auth0Id'], { unique: true }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('UserAccounts') - }, -} diff --git a/db/migrations/20210308070729-add-captain-to-group.js b/db/migrations/20210308070729-add-captain-to-group.js deleted file mode 100644 index d70e663cf..000000000 --- a/db/migrations/20210308070729-add-captain-to-group.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.addColumn('Groups', 'captainId', { - allowNull: false, - type: Sequelize.INTEGER, - references: { model: 'UserAccounts', key: 'id' }, - }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn('Groups', 'captainId') - }, -} diff --git a/db/migrations/20210310024724-create-offers.js b/db/migrations/20210310024724-create-offers.js deleted file mode 100644 index 8068e0dc6..000000000 --- a/db/migrations/20210310024724-create-offers.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('Offers', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - status: { - allowNull: false, - type: Sequelize.STRING, - }, - contact: { - type: Sequelize.JSONB, - }, - photoUris: { - allowNull: false, - type: Sequelize.JSONB, - }, - shipmentId: { - type: Sequelize.INTEGER, - references: { model: 'Shipments', key: 'id' }, - }, - sendingGroupId: { - type: Sequelize.INTEGER, - references: { model: 'Groups', key: 'id' }, - }, - statusChangeTime: { - allowNull: false, - type: Sequelize.DATE, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Offers') - }, -} diff --git a/db/migrations/20210314171740-index-groups-on-captain-id.js b/db/migrations/20210314171740-index-groups-on-captain-id.js deleted file mode 100644 index f95daddb4..000000000 --- a/db/migrations/20210314171740-index-groups-on-captain-id.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.addIndex('Groups', ['captainId'], { unique: false }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeIndex('Groups', 'captainId') - }, -} diff --git a/db/migrations/20210314172017-index-offers-on-shipment-id.js b/db/migrations/20210314172017-index-offers-on-shipment-id.js deleted file mode 100644 index c4856e3f8..000000000 --- a/db/migrations/20210314172017-index-offers-on-shipment-id.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.addIndex('Offers', ['shipmentId'], { unique: false }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeIndex('Offers', 'shipmentId') - }, -} diff --git a/db/migrations/20210314222712-create-pallets.js b/db/migrations/20210314222712-create-pallets.js deleted file mode 100644 index b2da94922..000000000 --- a/db/migrations/20210314222712-create-pallets.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('Pallets', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - offerId: { - allowNull: false, - type: Sequelize.INTEGER, - references: { model: 'Offers', key: 'id' }, - }, - palletType: { - allowNull: false, - type: Sequelize.STRING, - }, - paymentStatus: { - allowNull: false, - type: Sequelize.STRING, - }, - paymentStatusChangeTime: { - allowNull: false, - type: Sequelize.DATE, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - - await queryInterface.addIndex('Pallets', ['offerId'], { unique: false }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Pallets') - }, -} diff --git a/db/migrations/20210315214429-create-line-items.js b/db/migrations/20210315214429-create-line-items.js deleted file mode 100644 index b250fbcf2..000000000 --- a/db/migrations/20210315214429-create-line-items.js +++ /dev/null @@ -1,100 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('LineItems', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - offerPalletId: { - allowNull: false, - type: Sequelize.INTEGER, - references: { model: 'Pallets', key: 'id' }, - }, - proposedReceivingGroupId: { - type: Sequelize.INTEGER, - references: { model: 'Groups', key: 'id' }, - }, - acceptedReceivingGroupId: { - type: Sequelize.INTEGER, - references: { model: 'Groups', key: 'id' }, - }, - status: { - allowNull: false, - type: Sequelize.STRING, - }, - containerType: { - allowNull: false, - type: Sequelize.STRING, - }, - category: { - allowNull: false, - type: Sequelize.STRING, - }, - description: { - type: Sequelize.STRING, - }, - itemCount: { - allowNull: false, - type: Sequelize.INTEGER, - }, - boxCount: { - type: Sequelize.INTEGER, - }, - boxWeightGrams: { - type: Sequelize.INTEGER, - }, - containerLengthCm: { - type: Sequelize.INTEGER, - }, - containerWidthCm: { - type: Sequelize.INTEGER, - }, - containerHeightCm: { - type: Sequelize.INTEGER, - }, - affirmLiability: { - allowNull: false, - type: Sequelize.BOOLEAN, - }, - tosAccepted: { - allowNull: false, - type: Sequelize.BOOLEAN, - }, - dangerousGoods: { - allowNull: false, - type: Sequelize.JSONB, - }, - photoUris: { - allowNull: false, - type: Sequelize.JSONB, - }, - sendingHubDeliveryDate: { - type: Sequelize.DATE, - }, - statusChangeTime: { - allowNull: false, - type: Sequelize.DATE, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - - await queryInterface.addIndex('LineItems', ['offerPalletId'], { - unique: false, - }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('LineItems') - }, -} diff --git a/db/migrations/20210316064400-rename-line-item-box-columns.js b/db/migrations/20210316064400-rename-line-item-box-columns.js deleted file mode 100644 index 9e2eece21..000000000 --- a/db/migrations/20210316064400-rename-line-item-box-columns.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.renameColumn('LineItems', 'boxCount', 'containerCount') - await queryInterface.renameColumn( - 'LineItems', - 'boxWeightGrams', - 'containerWeightGrams', - ) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.renameColumn('LineItems', 'containerCount', 'boxCount') - await queryInterface.renameColumn( - 'LineItems', - 'containerWeightGrams', - 'boxWeightGrams', - ) - }, -} diff --git a/db/migrations/20210323060940-add-pricing-to-shipments.js b/db/migrations/20210323060940-add-pricing-to-shipments.js deleted file mode 100644 index 116896358..000000000 --- a/db/migrations/20210323060940-add-pricing-to-shipments.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.addColumn('Shipments', 'pricing', { - type: Sequelize.DataTypes.JSONB, - }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn('Shipments', 'pricing') - }, -} diff --git a/db/migrations/20210402074831-create-shipment-exports.js b/db/migrations/20210402074831-create-shipment-exports.js deleted file mode 100644 index dfc42e370..000000000 --- a/db/migrations/20210402074831-create-shipment-exports.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('ShipmentExports', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - contentsCsv: { - type: Sequelize.TEXT, - }, - shipmentId: { - allowNull: false, - type: Sequelize.INTEGER, - references: { model: 'Shipments', key: 'id' }, - }, - userAccountId: { - allowNull: false, - type: Sequelize.INTEGER, - references: { model: 'UserAccounts', key: 'id' }, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - - await queryInterface.addIndex('ShipmentExports', ['shipmentId'], { - unique: false, - }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeIndex('ShipmentExports', 'shipmentId') - await queryInterface.dropTable('ShipmentExports') - }, -} diff --git a/db/seeders/20210302050835-demo-groups.js b/db/seeders/20210302050835-demo-groups.js index 72579a5a8..1d98d385a 100644 --- a/db/seeders/20210302050835-demo-groups.js +++ b/db/seeders/20210302050835-demo-groups.js @@ -6,7 +6,7 @@ module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.bulkInsert('UserAccounts', [ { - auth0Id: 'seeded-account-id', + username: 'seeded-account-id', createdAt: new Date(), updatedAt: new Date(), }, diff --git a/docs/auth0.md b/docs/auth0.md deleted file mode 100644 index 365bb3ee2..000000000 --- a/docs/auth0.md +++ /dev/null @@ -1,7 +0,0 @@ -# Auth0 configuration - -We use Auth0 for authentication and authorization. - -Our Auth0 configuration is stored in GitHub, in the [https://github.com/distributeaid/auth0](distributeaid/auth0) repository. Whenever a change is merge in that repo, the Auth0 configuration is automatically updated. - -See https://github.com/distributeaid/auth0/blob/saga/clients/shipment-tracker.json. diff --git a/frontend/src/components/UserProfileContext.tsx b/frontend/src/components/UserProfileContext.tsx index 27554c7d3..75e5278c3 100644 --- a/frontend/src/components/UserProfileContext.tsx +++ b/frontend/src/components/UserProfileContext.tsx @@ -16,7 +16,7 @@ interface UserProfileData { } const fetchProfile = (token: string) => { - return fetch('/profile', { + return fetch('/me', { headers: { Authorization: `Bearer ${token}` }, }) } diff --git a/package.json b/package.json index 75b4bf290..25ecd80c8 100644 --- a/package.json +++ b/package.json @@ -27,30 +27,31 @@ "license": "(ISC OR GPL-3.0)", "dependencies": { "@graphql-tools/utils": "8.2.2", - "@sinclair/typebox": "^0.20.0", - "@types/graphql-depth-limit": "^1.1.2", - "@types/lodash": "^4.14.170", - "ajv": "^8.6.0", - "ajv-formats": "^2.1.0", - "apollo-server": "^3.0.0", - "apollo-server-express": "^3.0.0", - "compression": "^1.7.4", - "cors": "^2.8.5", - "dotenv": "^10.0.0", - "express": "^4.17.1", - "graphql": "^15.5.1", - "graphql-depth-limit": "^1.1.0", - "graphql-import-node": "^0.0.4", - "graphql-scalars": "^1.10.0", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.3", - "lodash": "^4.17.21", - "nodemon": "^2.0.7", - "pg": "^8.6.0", - "prettier-plugin-organize-imports": "^2.2.0", - "reflect-metadata": "^0.1.13", - "sequelize": "^6.6.2", - "sequelize-typescript": "^2.1.0" + "@sinclair/typebox": "0.20.0", + "ajv": "8.6.2", + "ajv-formats": "2.1.1", + "apollo-server": "3.0.0", + "apollo-server-express": "3.0.0", + "bcrypt": "5.0.1", + "body-parser": "1.19.0", + "compression": "1.7.4", + "cookie-parser": "1.4.5", + "cors": "2.8.5", + "dotenv": "10.0.0", + "express": "4.17.1", + "graphql": "15.5.1", + "graphql-depth-limit": "1.1.0", + "graphql-import-node": "0.0.4", + "graphql-scalars": "1.10.0", + "lodash": "4.17.21", + "nodemon": "2.0.7", + "passport": "0.4.1", + "passport-cookie": "1.0.9", + "pg": "8.6.0", + "reflect-metadata": "0.1.13", + "sequelize": "6.6.2", + "sequelize-typescript": "2.1.0", + "uuid": "8.3.2" }, "devDependencies": { "@distributeaid/shared-config": "distributeaid/shared-config", @@ -60,20 +61,30 @@ "@graphql-codegen/typescript-operations": "2.1.4", "@graphql-codegen/typescript-react-apollo": "3.1.4", "@graphql-codegen/typescript-resolvers": "2.2.1", + "@types/bcrypt": "5.0.0", + "@types/body-parser": "1.19.1", "@types/compression": "1.7.2", + "@types/cookie-parser": "1.4.2", "@types/express": "4.17.13", + "@types/graphql-depth-limit": "1.1.2", "@types/jest": "27.0.1", + "@types/lodash": "4.14.170", "@types/node": "14.17.16", + "@types/passport": "1.0.7", + "@types/supertest": "2.0.11", + "@types/uuid": "8.3.1", "@types/validator": "13.6.3", "@types/ws": "7.4.7", - "graphql-tag": "^2.12.5", + "graphql-tag": "2.12.5", "husky": "7.0.2", "jest": "27.2.0", "jest-extended": "0.11.5", "npm-run-all": "4.1.5", "prettier": "2.4.0", + "prettier-plugin-organize-imports": "2.2.0", "pretty-quick": "3.1.1", "sequelize-cli": "6.2.0", + "supertest": "6.1.6", "ts-jest": "27.0.5", "ts-node": "10.2.1", "ts-node-dev": "1.1.8", diff --git a/schema.graphql b/schema.graphql index d8b14b736..bfce79d44 100644 --- a/schema.graphql +++ b/schema.graphql @@ -198,6 +198,7 @@ type UserProfile { id: Int! isAdmin: Boolean! groupId: Int + username: String! } enum OfferStatus { diff --git a/src/apolloServer.ts b/src/apolloServer.ts index 349157466..ada12009d 100644 --- a/src/apolloServer.ts +++ b/src/apolloServer.ts @@ -4,7 +4,7 @@ import { AuthenticationError, } from 'apollo-server-express' import depthLimit from 'graphql-depth-limit' -import { AuthenticatedAuth, authenticateRequest } from './authenticateRequest' +import { AuthContext, authenticateWithToken } from './authenticateRequest' import generateCsv, { CsvRow } from './generateCsv' import resolvers from './resolvers' import typeDefs from './typeDefs' @@ -14,7 +14,7 @@ export type Services = { } export type AuthenticatedContext = { - auth: AuthenticatedAuth + auth: AuthContext services: Services } @@ -23,15 +23,13 @@ export const serverConfig: ApolloServerExpressConfig = { resolvers, validationRules: [depthLimit(7)], async context({ req }): Promise { - const auth = await authenticateRequest(req) + const auth = await authenticateWithToken(req.signedCookies.token) - if (auth.userAccount == null) { - throw new AuthenticationError( - `No user account found for profile ${auth.claims.sub}`, - ) + if (!('userAccount' in auth)) { + throw new AuthenticationError(auth.message) } - return { auth: auth as AuthenticatedAuth, services: { generateCsv } } + return { auth: auth as AuthContext, services: { generateCsv } } }, } diff --git a/src/authenticateRequest.ts b/src/authenticateRequest.ts index 13309a037..81f3538ca 100644 --- a/src/authenticateRequest.ts +++ b/src/authenticateRequest.ts @@ -1,169 +1,54 @@ -import { AuthenticationError, ForbiddenError } from 'apollo-server-express' -import { Request } from 'express' -import { Maybe } from 'graphql/jsutils/Maybe' -import jwt, { GetPublicKeyOrSecret } from 'jsonwebtoken' -import jwksClient from 'jwks-rsa' -import { omit } from 'lodash' +import { Strategy as CookieStrategy } from 'passport-cookie' import UserAccount from './models/user_account' -// These values are from the perspective of Auth0. -// audience is the Shipment Tracker API, while issuer is the -// distributeaid tenant in Auth0 that owns the API. -// jwksUri is where we can get the signing key from Auth0. -const audience: Maybe = - process.env.API_IDENTIFIER || 'https://da-shipping-tracker-dev' -const issuer = process.env.AUTH0_ISSUER || 'https://distributeaid.eu.auth0.com/' -const jwksUri = - process.env.AUTH0_JWKS_URI || - 'https://distributeaid.eu.auth0.com/.well-known/jwks.json' - -const client = jwksClient({ - jwksUri, +const fakeAccount = UserAccount.build({ + username: '', + token: '', + passwordHash: '', }) -type SigningKey = { - publicKey?: string - rsaPublicKey?: string -} - -const getAuth0SigningKey: GetPublicKeyOrSecret = (header, callback) => { - if (!header?.kid) { - return callback('Header is missing kid') - } - - client.getSigningKey(header.kid, (error, key: SigningKey) => { - if (error != null) callback(error) - - const signingKey: string | undefined = key.publicKey || key.rsaPublicKey - - callback(null, signingKey) - }) -} - -type AuthResult = { - decoded: object | null - error?: object -} - -const validateJwt = async (token: string): Promise => { - return new Promise((resolve, reject) => { - if (!token.startsWith('Bearer ')) { - return reject(new Error('Auth token is malformed')) - } - - const bearerToken = token.split(' ')[1] - - jwt.verify( - bearerToken, - getAuth0SigningKey, - { - audience, - issuer, - algorithms: ['RS256'], - }, - (error, decoded) => { - resolve({ decoded: decoded!, error: error! }) - }, - ) - }) -} - -const ADMIN_ROLE = 'admin' -const ROLES_KEY = 'http://id.distributeaid.org/role' - -type CommonAuthClaims = { - iss: string - sub: string - aud: string[] - iat: number - exp: number - azp: string - scope: string - permissions: string[] -} - -export type RawAuthClaims = CommonAuthClaims & { - [ROLES_KEY]: string[] -} - -export type AuthClaims = CommonAuthClaims & { - roles: string[] -} - -export type Auth = { - claims: AuthClaims - userAccount: UserAccount | null - isAdmin: boolean -} - -export type AuthenticatedAuth = { - claims: AuthClaims +export type AuthContext = { userAccount: UserAccount isAdmin: boolean } -const fakeAccount = UserAccount.build({ auth0Id: '' }) -const fakeClaims = { - roles: [], - iss: '', - sub: '', - aud: [], - iat: 0, - exp: 0, - azp: '', - scope: '', - permissions: [], +export type ErrorInfo = { + message: string } -export const fakeAdminAuth: Auth = { +export const fakeAdminAuth: AuthContext = { userAccount: fakeAccount, - claims: fakeClaims, isAdmin: true, } -export const fakeUserAuth: Auth = { +export const fakeUserAuth: AuthContext = { userAccount: fakeAccount, - claims: fakeClaims, isAdmin: false, } -export const authenticateRequest = async (req: Request): Promise => { - if (process.env.AUTH_MODE === 'SKIP') { - console.log("using fake admin credentials because AUTH_MODE='SKIP'") - return fakeAdminAuth - } - - const token = req.headers.authorization || req.query.authorization?.toString() - - if (token == null) { - throw new ForbiddenError('you must be logged in') - } - - let authResult: AuthResult +export const authenticateWithToken = async ( + token: string, +): Promise => { try { - authResult = await validateJwt(token) - } catch (e) { - throw new AuthenticationError((e as Error).message) - } - - if (authResult.error != null) { - throw new AuthenticationError(JSON.stringify(authResult.error)) - } - - if (authResult.decoded == null) { - throw new AuthenticationError( - 'Authentication completed but no payload was provided.', - ) + const userAccount = await UserAccount.findOne({ + where: { token }, + }) + if (userAccount === null) return { message: 'User not found for token.' } + return { userAccount, isAdmin: false } + } catch (err) { + return { message: (err as Error).message } } - - const rawClaims = authResult.decoded as RawAuthClaims - const userAccount = await UserAccount.findOne({ - where: { auth0Id: rawClaims.sub }, - }) - - const roles = rawClaims[ROLES_KEY] - const claims = { roles, ...omit(rawClaims, ROLES_KEY) } - const isAdmin = roles.includes(ADMIN_ROLE) - - return { userAccount, claims, isAdmin } } + +export const authTokenCookieName = 'token' +export const cookieAuthStrategy = new CookieStrategy( + { + cookieName: authTokenCookieName, + signed: true, + }, + async (token: string, done: any) => { + const res = await authenticateWithToken(token) + if ('userAccount' in res) return done(null, res) + return done(null, false, res) + }, +) diff --git a/src/findOrCreateProfile.ts b/src/findOrCreateProfile.ts deleted file mode 100644 index 34ace7ce3..000000000 --- a/src/findOrCreateProfile.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Maybe } from '@graphql-tools/utils' -import { Request, Response } from 'express' -import { authenticateRequest } from './authenticateRequest' -import Group from './models/group' -import UserAccount from './models/user_account' - -const sendProfile = ( - response: Response, - userAccount: UserAccount, - isAdmin = false, - groupId?: number, -) => response.json(userAccount.asProfile(groupId, isAdmin)).end() - -const findOrCreateProfile = async (request: Request, response: Response) => { - const auth = await authenticateRequest(request) - - if (auth.userAccount != null) { - console.info('found account for profile', auth.claims.sub) - - let groupForUser: Maybe - if (!auth.isAdmin) { - // Note: this assumes that there is only 1 captain per group, where in - // reality there are no restrictions on the number of groups with the same - // captain. For now, we've agreed that this is okay, but we probably need - // to solidify some restrictions later on. - // See https://github.com/distributeaid/shipment-tracker/issues/133 - groupForUser = await Group.findOne({ - where: { captainId: auth.userAccount.id }, - attributes: ['id'], - }) - } - - sendProfile(response, auth.userAccount, auth.isAdmin, groupForUser?.id) - return - } - - console.info('creating account for profile', auth.claims.sub) - - const userAccount = await UserAccount.create({ - auth0Id: auth.claims.sub, - }) - - console.info('created user account', userAccount.id) - - sendProfile(response, userAccount) -} - -export default findOrCreateProfile diff --git a/src/getProfile.ts b/src/getProfile.ts new file mode 100644 index 000000000..e99ea7760 --- /dev/null +++ b/src/getProfile.ts @@ -0,0 +1,37 @@ +import { Maybe } from '@graphql-tools/utils' +import { Request, Response } from 'express' +import { AuthContext } from './authenticateRequest' +import Group from './models/group' +import UserAccount from './models/user_account' + +const sendProfile = ( + response: Response, + userAccount: UserAccount, + isAdmin = false, + groupId?: number, +) => response.json(userAccount.asProfile(groupId, isAdmin)).end() + +const getProfile = async (request: Request, response: Response) => { + const authContext = request.user as AuthContext + let groupForUser: Maybe + if (!authContext.isAdmin) { + // Note: this assumes that there is only 1 captain per group, where in + // reality there are no restrictions on the number of groups with the same + // captain. For now, we've agreed that this is okay, but we probably need + // to solidify some restrictions later on. + // See https://github.com/distributeaid/shipment-tracker/issues/133 + groupForUser = await Group.findOne({ + where: { captainId: authContext.userAccount.id }, + attributes: ['id'], + }) + } + + sendProfile( + response, + authContext.userAccount, + authContext.isAdmin, + groupForUser?.id, + ) +} + +export default getProfile diff --git a/src/login.ts b/src/login.ts new file mode 100644 index 000000000..f5af5b364 --- /dev/null +++ b/src/login.ts @@ -0,0 +1,35 @@ +import bcrypt from 'bcrypt' +import { Request, Response } from 'express' +import { v4 } from 'uuid' +import { authTokenCookieName } from './authenticateRequest' +import UserAccount from './models/user_account' + +// FIXME: Add input validation +const login = + (penaltySeconds = 10) => + async (request: Request, response: Response) => { + const user = await UserAccount.findOne({ + where: { + username: request.body.username, + }, + }) + if (user === null) { + // Penalize + await new Promise((resolve) => setTimeout(resolve, penaltySeconds * 1000)) + return response.status(401).end() + } + if (!bcrypt.compareSync(request.body.password, user.passwordHash)) { + // Penalize + await new Promise((resolve) => setTimeout(resolve, penaltySeconds * 1000)) + return response.status(401).end() + } + // Generate new token + const token = v4() + await user.update({ token }) + response + .status(204) + .cookie(authTokenCookieName, token, { signed: true }) + .end() + } + +export default login diff --git a/src/models/shipment_export.ts b/src/models/shipment_export.ts index e440db9cd..cfc6cb1bf 100644 --- a/src/models/shipment_export.ts +++ b/src/models/shipment_export.ts @@ -56,6 +56,7 @@ export default class ShipmentExport extends Model { createdBy: { id: this.userAccountId, isAdmin: true, + username: this.userAccount.username, }, createdAt: this.createdAt, } diff --git a/src/models/user_account.ts b/src/models/user_account.ts index d61cf80ba..88feddfcb 100644 --- a/src/models/user_account.ts +++ b/src/models/user_account.ts @@ -3,6 +3,7 @@ import { CreatedAt, Model, Table, + Unique, UpdatedAt, } from 'sequelize-typescript' import { Optional } from 'sequelize/types' @@ -10,7 +11,9 @@ import { UserProfile } from '../server-internal-types' export interface UserAccountAttributes { id: number - auth0Id: string + username: string + passwordHash: string + token: string } export interface UserAccountCreationAttributes @@ -25,8 +28,15 @@ export default class UserAccount extends Model< > { public id!: number + @Unique @Column - public auth0Id!: string + public username!: string + + @Column + public passwordHash!: string + + @Column + public token!: string @CreatedAt @Column @@ -39,6 +49,7 @@ export default class UserAccount extends Model< public asProfile(groupId?: number, isAdmin = false): UserProfile { return { id: this.id, + username: this.username, isAdmin, groupId, } diff --git a/src/registerUser.ts b/src/registerUser.ts new file mode 100644 index 000000000..a4ad72f6c --- /dev/null +++ b/src/registerUser.ts @@ -0,0 +1,23 @@ +import bcrypt from 'bcrypt' +import { Request, Response } from 'express' +import { v4 } from 'uuid' +import { authTokenCookieName } from './authenticateRequest' +import UserAccount from './models/user_account' + +// FIXME: Add input validation +const registerUser = + (saltRounds = 10) => + async (request: Request, response: Response) => { + const token = v4() + await UserAccount.create({ + passwordHash: bcrypt.hashSync(request.body.password, saltRounds), + token, + username: request.body.username, + }) + response + .status(202) + .cookie(authTokenCookieName, token, { signed: true }) + .end() + } + +export default registerUser diff --git a/src/sendShipmentExportCsv.ts b/src/sendShipmentExportCsv.ts index 942fab9f7..bfaa35cdf 100644 --- a/src/sendShipmentExportCsv.ts +++ b/src/sendShipmentExportCsv.ts @@ -1,10 +1,10 @@ import { ForbiddenError } from 'apollo-server' import { Request, Response } from 'express' -import { authenticateRequest } from './authenticateRequest' +import { AuthContext } from './authenticateRequest' import ShipmentExport from './models/shipment_export' const sendShipmentExportCsv = async (req: Request, res: Response) => { - const auth = await authenticateRequest(req) + const auth = req.user as AuthContext if (!auth.isAdmin) { throw new ForbiddenError('Only admins are allowed to export shipments') diff --git a/src/server.ts b/src/server.ts index 19835339c..012d464d6 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4,6 +4,9 @@ import cors from 'cors' import express from 'express' import { createServer } from 'http' import path from 'path' +import passport from 'passport' +import cookieParser from 'cookie-parser' +import { json } from 'body-parser' // Note: the order of these imports matters! import './loadEnv' @@ -12,14 +15,23 @@ import './loadEnv' import './sequelize' import apolloServer from './apolloServer' -import findOrCreateProfile from './findOrCreateProfile' +import getProfile from './getProfile' import getAllFilesSync from './getAllFilesSync' import sendShipmentExportCsv from './sendShipmentExportCsv' +import { cookieAuthStrategy } from './authenticateRequest' +import registerUser from './registerUser' +import login from './login' const app = express() +app.use(cookieParser(process.env.COOKIE_SECRET ?? 'cookie-secret')) +app.use(json()) +const cookieAuth = passport.authenticate('cookie', { session: false }) +passport.use(cookieAuthStrategy) -app.get('/profile', findOrCreateProfile) -app.get('/shipment-exports/:id', sendShipmentExportCsv) +app.get('/me', cookieAuth, getProfile) +app.get('/login', login()) +app.get('/user', registerUser) +app.get('/shipment-exports/:id', cookieAuth, sendShipmentExportCsv) app.use( cors({ diff --git a/src/testServer.ts b/src/testServer.ts index 9eeb5962b..be582f8d7 100644 --- a/src/testServer.ts +++ b/src/testServer.ts @@ -30,7 +30,9 @@ export const makeTestServer = async ( ): Promise => { if (overrides.context == null) { const userAccount = await UserAccount.create({ - auth0Id: 'user-auth0-id', + username: 'user-id', + passwordHash: '', + token: '', }) overrides.context = () => ({ @@ -52,7 +54,9 @@ export const makeAdminTestServerWithServices = async ( overrides: Partial = {}, ) => { const userAccount = await UserAccount.create({ - auth0Id: 'admin-auth0-id', + username: 'admin-auth0-id', + passwordHash: '', + token: '', }) const fakeGenrateCsv = makeFakeGenerateCsvFn() diff --git a/src/tests/groups_api.test.ts b/src/tests/groups_api.test.ts index 75a59f36f..656fc0ee8 100644 --- a/src/tests/groups_api.test.ts +++ b/src/tests/groups_api.test.ts @@ -41,10 +41,14 @@ describe('Groups API', () => { // Create test servers captain = await UserAccount.create({ - auth0Id: 'captain-id', + username: 'captain-id', + passwordHash: '', + token: '', }) newCaptain = await UserAccount.create({ - auth0Id: 'new-captain-id', + username: 'new-captain-id', + passwordHash: '', + token: '', }) testServer = await makeTestServer({ context: () => ({ auth: { ...fakeUserAuth, userAccount: captain } }), @@ -274,11 +278,19 @@ describe('Groups API', () => { */ beforeAll(async () => { captain1 = await UserAccount.create({ - auth0Id: captain1Name, + username: captain1Name, + passwordHash: '', + token: '', + }) + captain2 = await UserAccount.create({ + username: captain2Name, + passwordHash: '', + token: '', }) - captain2 = await UserAccount.create({ auth0Id: captain2Name }) daCaptain = await UserAccount.create({ - auth0Id: daCaptainName, + username: daCaptainName, + passwordHash: '', + token: '', }) sendingGroup1 = await Group.create({ name: sendingGroup1Name, @@ -481,7 +493,7 @@ describe('Groups API', () => { variables: { captainId: ( await UserAccount.findOne({ - where: { auth0Id: captainName }, + where: { username: captainName }, }) )?.id, }, @@ -521,7 +533,7 @@ describe('Groups API', () => { variables: { captainId: ( await UserAccount.findOne({ - where: { auth0Id: captainName }, + where: { username: captainName }, }) )?.id, groupType: groupTypes, @@ -535,7 +547,7 @@ describe('Groups API', () => { }) ) .filter(({ name }) => expectedGroupNames.includes(name)) - .filter(({ captain }) => captain.auth0Id === captainName) + .filter(({ captain }) => captain.username === captainName) .map(toGroupData), ) }, diff --git a/src/tests/helpers/index.ts b/src/tests/helpers/index.ts index 744e385ad..13c8d13d8 100644 --- a/src/tests/helpers/index.ts +++ b/src/tests/helpers/index.ts @@ -10,7 +10,7 @@ import { ShipmentCreateInput, } from '../../server-internal-types' -let fakeAuth0Id = 1 +let fakeusername = 1 async function createGroup( input: GroupCreateInput, @@ -18,7 +18,9 @@ async function createGroup( ): Promise { if (!captainId) { const groupCaptain = await UserAccount.create({ - auth0Id: `fake-auth-id-${fakeAuth0Id++}`, + username: `fake-auth-id-${fakeusername++}`, + passwordHash: '', + token: '', }) captainId = groupCaptain.id } diff --git a/src/tests/line_items_api.test.ts b/src/tests/line_items_api.test.ts index ae287f70a..2e1040f31 100644 --- a/src/tests/line_items_api.test.ts +++ b/src/tests/line_items_api.test.ts @@ -37,7 +37,9 @@ describe('LineItems API', () => { await sequelize.sync({ force: true }) captain = await UserAccount.create({ - auth0Id: 'captain-id', + username: 'captain-id', + passwordHash: '', + token: '', }) captainTestServer = await makeTestServer({ diff --git a/src/tests/offers_api.test.ts b/src/tests/offers_api.test.ts index 2682e60a2..461c25d40 100644 --- a/src/tests/offers_api.test.ts +++ b/src/tests/offers_api.test.ts @@ -34,7 +34,9 @@ describe('Offers API', () => { await Shipment.truncate({ cascade: true, force: true }) captain = await UserAccount.create({ - auth0Id: 'captain-id', + username: 'captain-id', + passwordHash: '', + token: '', }) captainTestServer = await makeTestServer({ diff --git a/src/tests/pallets_api.test.ts b/src/tests/pallets_api.test.ts index 258e5036f..e09054d27 100644 --- a/src/tests/pallets_api.test.ts +++ b/src/tests/pallets_api.test.ts @@ -40,7 +40,9 @@ describe('Pallets API', () => { await Pallet.truncate({ cascade: true, force: true }) captain = await UserAccount.create({ - auth0Id: 'captain-id', + username: 'captain-id', + passwordHash: '', + token: '', }) captainTestServer = await makeTestServer({ diff --git a/src/tests/shipment_exports_api.test.ts b/src/tests/shipment_exports_api.test.ts index dc469ab54..78730329d 100644 --- a/src/tests/shipment_exports_api.test.ts +++ b/src/tests/shipment_exports_api.test.ts @@ -38,7 +38,9 @@ describe('ShipmentExports API', () => { await sequelize.sync({ force: true }) captain = await UserAccount.create({ - auth0Id: 'captain-id', + username: 'captain-id', + passwordHash: '', + token: '', }) const serverWithContext = await makeAdminTestServerWithServices() diff --git a/src/tests/user_account_api.test.ts b/src/tests/user_account_api.test.ts new file mode 100644 index 000000000..84f0db810 --- /dev/null +++ b/src/tests/user_account_api.test.ts @@ -0,0 +1,114 @@ +// tslint:disable:ordered-imports +import { randomWords } from '@nordicsemiconductor/random-words' +import express, { Express } from 'express' +import { createServer, Server } from 'http' +import passport from 'passport' +import request, { SuperTest, Test } from 'supertest' +import '../sequelize' +import { authTokenCookieName, cookieAuthStrategy } from '../authenticateRequest' +import getProfile from '../getProfile' +import cookieParser from 'cookie-parser' +import { json } from 'body-parser' +import registerUser from '../registerUser' +import login from '../login' + +jest.setTimeout(60 * 1000) + +const cookieAuth = passport.authenticate('cookie', { session: false }) +passport.use(cookieAuthStrategy) + +const tokenCookieRx = new RegExp(`${authTokenCookieName}=([^;]+); Path=/`) + +const generateUsername = async () => + (await randomWords({ numWords: 3 })).join('-') + +describe('User account API', () => { + let app: Express + let httpServer: Server + let r: SuperTest + let username: string + let password: string + let token: string + beforeAll(async () => { + username = await generateUsername() + password = 'y{uugBmw"9,?=L_' + app = express() + app.use(cookieParser(process.env.COOKIE_SECRET ?? 'cookie-secret')) + app.use(json()) + app.get('/me', cookieAuth, getProfile) + app.post('/user', registerUser(1)) + app.post('/login', login(0)) + httpServer = createServer(app) + await new Promise((resolve) => + httpServer.listen(8888, '127.0.0.1', undefined, resolve), + ) + r = request('http://127.0.0.1:8888') + }) + afterAll(async () => { + httpServer.close() + }) + describe('/register', () => { + it('should register a new user account', async () => { + const res = await r + .post('/user') + .set('Accept', 'application/json') + .set('Content-type', 'application/json; charset=utf-8') + .send({ + username, + password, + }) + .expect(202) + .expect('set-cookie', tokenCookieRx) + + token = tokenCookieRx.exec(res.header['set-cookie'])?.[1] as string + }) + }) + describe('/me', () => { + it('should return the user account of the current user', async () => { + const res = await r + .get('/me') + .set('Cookie', [`${authTokenCookieName}=${token}`]) + .set('Accept', 'application/json') + .send() + .expect(200) + expect(res.body).toMatchObject({ + id: /[0-9]+/, + username, + isAdmin: false, + }) + }) + it('should deny request for unknown token', async () => + r + .get('/me') + .set('Cookie', [`${authTokenCookieName}=foo`]) + .send() + .expect(401)) + }) + describe('/login', () => { + it('should return a token on login', () => + r + .post('/login') + .send({ + username, + password, + }) + .expect(204) + .expect('set-cookie', tokenCookieRx)) + it('should fail with invalid password', () => + r + .post('/login') + .send({ + username, + password: 'foo', + }) + .expect(401)) + it('should fail with user not found', () => + r + .post('/login') + .send({ + username: 'foo', + password: 'foo', + }) + .expect(401)) + }) +}) diff --git a/yarn.lock b/yarn.lock index 3a32dd82c..af9af3df0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33,13 +33,6 @@ dependencies: xss "^1.0.8" -"@ardatan/aggregate-error@0.0.6": - version "0.0.6" - resolved "https://registry.yarnpkg.com/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz#fe6924771ea40fc98dc7a7045c2e872dc8527609" - integrity sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ== - dependencies: - tslib "~2.0.1" - "@ardatan/fetch-event-source@2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@ardatan/fetch-event-source/-/fetch-event-source-2.0.2.tgz#734aa3eaa0da456453d24d8dc7c14d5e366a8d21" @@ -52,25 +45,25 @@ dependencies: "@babel/highlight" "^7.14.5" -"@babel/compat-data@^7.14.5", "@babel/compat-data@^7.14.7": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.7.tgz#7b047d7a3a89a67d2258dc61f604f098f1bc7e08" - integrity sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw== +"@babel/compat-data@^7.14.7", "@babel/compat-data@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176" + integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA== "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.7.2", "@babel/core@^7.7.5": - version "7.14.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.6.tgz#e0814ec1a950032ff16c13a2721de39a8416fcab" - integrity sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA== + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.0.tgz#749e57c68778b73ad8082775561f67f5196aafa8" + integrity sha512-tXtmTminrze5HEUPn/a0JtOzzfp0nk+UEXQ/tqIJo3WDGypl/2OFQEMll/zSFU8f/lfmfLXvTaORHF3cfXIQMw== dependencies: "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.14.5" - "@babel/helper-compilation-targets" "^7.14.5" - "@babel/helper-module-transforms" "^7.14.5" - "@babel/helpers" "^7.14.6" - "@babel/parser" "^7.14.6" + "@babel/generator" "^7.15.0" + "@babel/helper-compilation-targets" "^7.15.0" + "@babel/helper-module-transforms" "^7.15.0" + "@babel/helpers" "^7.14.8" + "@babel/parser" "^7.15.0" "@babel/template" "^7.14.5" - "@babel/traverse" "^7.14.5" - "@babel/types" "^7.14.5" + "@babel/traverse" "^7.15.0" + "@babel/types" "^7.15.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -78,16 +71,7 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/generator@^7.14.5", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.5.tgz#848d7b9f031caca9d0cd0af01b063f226f52d785" - integrity sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA== - dependencies: - "@babel/types" "^7.14.5" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/generator@^7.15.0": +"@babel/generator@^7.15.0", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": version "7.15.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.0.tgz#a7d0c172e0d814974bad5aa77ace543b97917f15" integrity sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ== @@ -103,26 +87,26 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-compilation-targets@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz#7a99c5d0967911e972fe2c3411f7d5b498498ecf" - integrity sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw== +"@babel/helper-compilation-targets@^7.14.5", "@babel/helper-compilation-targets@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz#973df8cbd025515f3ff25db0c05efc704fa79818" + integrity sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A== dependencies: - "@babel/compat-data" "^7.14.5" + "@babel/compat-data" "^7.15.0" "@babel/helper-validator-option" "^7.14.5" browserslist "^4.16.6" semver "^6.3.0" "@babel/helper-create-class-features-plugin@^7.14.5": - version "7.14.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.6.tgz#f114469b6c06f8b5c59c6c4e74621f5085362542" - integrity sha512-Z6gsfGofTxH/+LQXqYEK45kxmcensbzmk/oi8DmaQytlQCgqNZt9XQF8iqlI/SeXWVjaMNxvYvzaYw+kh42mDg== + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.15.0.tgz#c9a137a4d137b2d0e2c649acf536d7ba1a76c0f7" + integrity sha512-MdmDXgvTIi4heDVX/e9EFfeGpugqm9fobBVg/iioE8kueXrOHdRDe36FAY7SnE9xXLVeYCoJR/gdrBEIHRC83Q== dependencies: "@babel/helper-annotate-as-pure" "^7.14.5" "@babel/helper-function-name" "^7.14.5" - "@babel/helper-member-expression-to-functions" "^7.14.5" + "@babel/helper-member-expression-to-functions" "^7.15.0" "@babel/helper-optimise-call-expression" "^7.14.5" - "@babel/helper-replace-supers" "^7.14.5" + "@babel/helper-replace-supers" "^7.15.0" "@babel/helper-split-export-declaration" "^7.14.5" "@babel/helper-function-name@^7.14.5": @@ -148,12 +132,12 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-member-expression-to-functions@^7.14.5": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz#97e56244beb94211fe277bd818e3a329c66f7970" - integrity sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA== +"@babel/helper-member-expression-to-functions@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz#0ddaf5299c8179f27f37327936553e9bba60990b" + integrity sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg== dependencies: - "@babel/types" "^7.14.5" + "@babel/types" "^7.15.0" "@babel/helper-module-imports@^7.14.5": version "7.14.5" @@ -162,19 +146,19 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-module-transforms@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz#7de42f10d789b423eb902ebd24031ca77cb1e10e" - integrity sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA== +"@babel/helper-module-transforms@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz#679275581ea056373eddbe360e1419ef23783b08" + integrity sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg== dependencies: "@babel/helper-module-imports" "^7.14.5" - "@babel/helper-replace-supers" "^7.14.5" - "@babel/helper-simple-access" "^7.14.5" + "@babel/helper-replace-supers" "^7.15.0" + "@babel/helper-simple-access" "^7.14.8" "@babel/helper-split-export-declaration" "^7.14.5" - "@babel/helper-validator-identifier" "^7.14.5" + "@babel/helper-validator-identifier" "^7.14.9" "@babel/template" "^7.14.5" - "@babel/traverse" "^7.14.5" - "@babel/types" "^7.14.5" + "@babel/traverse" "^7.15.0" + "@babel/types" "^7.15.0" "@babel/helper-optimise-call-expression@^7.14.5": version "7.14.5" @@ -188,22 +172,22 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== -"@babel/helper-replace-supers@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz#0ecc0b03c41cd567b4024ea016134c28414abb94" - integrity sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow== +"@babel/helper-replace-supers@^7.14.5", "@babel/helper-replace-supers@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz#ace07708f5bf746bf2e6ba99572cce79b5d4e7f4" + integrity sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA== dependencies: - "@babel/helper-member-expression-to-functions" "^7.14.5" + "@babel/helper-member-expression-to-functions" "^7.15.0" "@babel/helper-optimise-call-expression" "^7.14.5" - "@babel/traverse" "^7.14.5" - "@babel/types" "^7.14.5" + "@babel/traverse" "^7.15.0" + "@babel/types" "^7.15.0" -"@babel/helper-simple-access@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz#66ea85cf53ba0b4e588ba77fc813f53abcaa41c4" - integrity sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw== +"@babel/helper-simple-access@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz#82e1fec0644a7e775c74d305f212c39f8fe73924" + integrity sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg== dependencies: - "@babel/types" "^7.14.5" + "@babel/types" "^7.14.8" "@babel/helper-skip-transparent-expression-wrappers@^7.14.5": version "7.14.5" @@ -219,12 +203,7 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-validator-identifier@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8" - integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg== - -"@babel/helper-validator-identifier@^7.14.9": +"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9": version "7.14.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== @@ -234,14 +213,14 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== -"@babel/helpers@^7.14.6": - version "7.14.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.6.tgz#5b58306b95f1b47e2a0199434fa8658fa6c21635" - integrity sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA== +"@babel/helpers@^7.14.8": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.15.3.tgz#c96838b752b95dcd525b4e741ed40bb1dc2a1357" + integrity sha512-HwJiz52XaS96lX+28Tnbu31VeFSQJGOeKHJeaEPQlTl7PnlhFElWPj8tUXtqFIzeN86XxXoBr+WFAyK2PPVz6g== dependencies: "@babel/template" "^7.14.5" - "@babel/traverse" "^7.14.5" - "@babel/types" "^7.14.5" + "@babel/traverse" "^7.15.0" + "@babel/types" "^7.15.0" "@babel/highlight@^7.14.5": version "7.14.5" @@ -252,16 +231,11 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@7.15.3", "@babel/parser@^7.15.0": +"@babel/parser@7.15.3", "@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.14.5", "@babel/parser@^7.15.0", "@babel/parser@^7.7.2": version "7.15.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.3.tgz#3416d9bea748052cfcb63dbcc27368105b1ed862" integrity sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA== -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.14.5", "@babel/parser@^7.14.6", "@babel/parser@^7.14.7", "@babel/parser@^7.7.2": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.7.tgz#6099720c8839ca865a2637e6c85852ead0bdb595" - integrity sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA== - "@babel/plugin-proposal-class-properties@^7.0.0": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz#40d1ee140c5b1e31a350f4f5eed945096559b42e" @@ -401,16 +375,16 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-block-scoping@^7.0.0": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.5.tgz#8cc63e61e50f42e078e6f09be775a75f23ef9939" - integrity sha512-LBYm4ZocNgoCqyxMLoOnwpsmQ18HWTQvql64t3GvMUzLQrNoV1BDG0lNftC8QKYERkZgCCT/7J5xWGObGAyHDw== + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz#94c81a6e2fc230bcce6ef537ac96a1e4d2b3afaf" + integrity sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-classes@^7.0.0": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.5.tgz#0e98e82097b38550b03b483f9b51a78de0acb2cf" - integrity sha512-J4VxKAMykM06K/64z9rwiL6xnBHgB1+FVspqvlgCdwD1KUbQNfszeKVVOMh59w3sztHYIZDgnhOC4WbdEfHFDA== + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.9.tgz#2a391ffb1e5292710b00f2e2c210e1435e7d449f" + integrity sha512-NfZpTcxU3foGWbl4wxmZ35mTsYJy8oQocbeIMoDAGGFarAmSQlL+LWMkDx/tj6pNotpbX3rltIA4dprgAPOq5A== dependencies: "@babel/helper-annotate-as-pure" "^7.14.5" "@babel/helper-function-name" "^7.14.5" @@ -472,13 +446,13 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-modules-commonjs@^7.0.0": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.5.tgz#7aaee0ea98283de94da98b28f8c35701429dad97" - integrity sha512-en8GfBtgnydoao2PS+87mKyw62k02k7kJ9ltbKe0fXTHrQmG6QZZflYuGI1VVG7sVpx4E1n7KBpNlPb8m78J+A== + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.15.0.tgz#3305896e5835f953b5cdb363acd9e8c2219a5281" + integrity sha512-3H/R9s8cXcOGE8kgMlmjYYC9nqr5ELiPkJn4q0mypBrjhYQoc+5/Maq69vV4xRPWnkzZuwJPf5rArxpB/35Cig== dependencies: - "@babel/helper-module-transforms" "^7.14.5" + "@babel/helper-module-transforms" "^7.15.0" "@babel/helper-plugin-utils" "^7.14.5" - "@babel/helper-simple-access" "^7.14.5" + "@babel/helper-simple-access" "^7.14.8" babel-plugin-dynamic-import-node "^2.3.3" "@babel/plugin-transform-object-super@^7.0.0": @@ -504,22 +478,22 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-react-display-name@^7.0.0": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.14.5.tgz#baa92d15c4570411301a85a74c13534873885b65" - integrity sha512-07aqY1ChoPgIxsuDviptRpVkWCSbXWmzQqcgy65C6YSFOfPFvb/DX3bBRHh7pCd/PMEEYHYWUTSVkCbkVainYQ== + version "7.15.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.15.1.tgz#6aaac6099f1fcf6589d35ae6be1b6e10c8c602b9" + integrity sha512-yQZ/i/pUCJAHI/LbtZr413S3VT26qNrEm0M5RRxQJA947/YNYwbZbBaXGDrq6CG5QsZycI1VIP6d7pQaBfP+8Q== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-react-jsx@^7.0.0": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.5.tgz#39749f0ee1efd8a1bd729152cf5f78f1d247a44a" - integrity sha512-7RylxNeDnxc1OleDm0F5Q/BSL+whYRbOAR+bwgCxIr0L32v7UFh/pz1DLMZideAUxKT6eMoS2zQH6fyODLEi8Q== + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.9.tgz#3314b2163033abac5200a869c4de242cd50a914c" + integrity sha512-30PeETvS+AeD1f58i1OVyoDlVYQhap/K20ZrMjLmmzmC2AYR/G43D4sdJAaDAqCD3MYpSWbmrz3kES158QSLjw== dependencies: "@babel/helper-annotate-as-pure" "^7.14.5" "@babel/helper-module-imports" "^7.14.5" "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-jsx" "^7.14.5" - "@babel/types" "^7.14.5" + "@babel/types" "^7.14.9" "@babel/plugin-transform-shorthand-properties@^7.0.0": version "7.14.5" @@ -544,9 +518,9 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/runtime@^7.0.0": - version "7.14.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d" - integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg== + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b" + integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA== dependencies: regenerator-runtime "^0.13.4" @@ -559,7 +533,7 @@ "@babel/parser" "^7.14.5" "@babel/types" "^7.14.5" -"@babel/traverse@7.15.0": +"@babel/traverse@7.15.0", "@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.15.0", "@babel/traverse@^7.7.2": version "7.15.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.0.tgz#4cca838fd1b2a03283c1f38e141f639d60b3fc98" integrity sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw== @@ -574,22 +548,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.14.5", "@babel/traverse@^7.7.2": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.7.tgz#64007c9774cfdc3abd23b0780bc18a3ce3631753" - integrity sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.14.5" - "@babel/helper-function-name" "^7.14.5" - "@babel/helper-hoist-variables" "^7.14.5" - "@babel/helper-split-export-declaration" "^7.14.5" - "@babel/parser" "^7.14.7" - "@babel/types" "^7.14.5" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@7.15.0", "@babel/types@^7.15.0": +"@babel/types@7.15.0", "@babel/types@^7.0.0", "@babel/types@^7.14.5", "@babel/types@^7.14.8", "@babel/types@^7.14.9", "@babel/types@^7.15.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": version "7.15.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.0.tgz#61af11f2286c4e9c69ca8deb5f4375a73c72dcbd" integrity sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ== @@ -597,14 +556,6 @@ "@babel/helper-validator-identifier" "^7.14.9" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.14.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.5.tgz#3bb997ba829a2104cedb20689c4a5b8121d383ff" - integrity sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg== - dependencies: - "@babel/helper-validator-identifier" "^7.14.5" - to-fast-properties "^2.0.0" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -624,7 +575,7 @@ "@distributeaid/shared-config@distributeaid/shared-config": version "1.0.0" - resolved "https://codeload.github.com/distributeaid/shared-config/tar.gz/dc30907aaffde9c86e185c18a5dd18c05957aae7" + resolved "https://codeload.github.com/distributeaid/shared-config/tar.gz/05ab602f963c25bf1d028ae65c1ce0172fa42f4f" "@endemolshinegroup/cosmiconfig-typescript-loader@3.0.2": version "3.0.2" @@ -700,19 +651,7 @@ "@graphql-tools/utils" "^8.1.1" tslib "~2.3.0" -"@graphql-codegen/plugin-helpers@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.1.0.tgz#f7a22bd00a36c27b3d2b1a01dd331d3e36a3dd1c" - integrity sha512-4yX+mlkwc6786yd+vgzx563Lfm3lp4pdYTQp8zEav8ZGVysI6t981WmD5TcfloTsqIG5ZrM7iSFnw2/2DQS9tg== - dependencies: - "@graphql-tools/utils" "^8.1.1" - change-case-all "1.0.14" - common-tags "1.8.0" - import-from "4.0.0" - lodash "~4.17.0" - tslib "~2.3.0" - -"@graphql-codegen/plugin-helpers@^2.1.1": +"@graphql-codegen/plugin-helpers@^2.1.0", "@graphql-codegen/plugin-helpers@^2.1.1": version "2.1.1" resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.1.1.tgz#fc13e735763574ef308045bbb95c3e7201ec0027" integrity sha512-7jjN9fekMQkpd7cRTbaBxgqt/hkR3CXeOUSsEyHFDDHKtvCrnev3iyc75IeWXpO9tOwDE8mVPTzEZnu4QukrNA== @@ -794,16 +733,6 @@ sync-fetch "0.3.0" tslib "~2.3.0" -"@graphql-tools/batch-execute@^8.0.1": - version "8.0.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/batch-execute/-/batch-execute-8.0.1.tgz#5e99e63c5fcfd383201900b2b4d1dc3a365ad957" - integrity sha512-39SpVo2BgcuFLp3ZNvnyPbyFBCCAQMsR/0BvSC8yQaq5AOMLU76fJCKY5RcmNY+9n6529eem8kzdN20qm2rq+g== - dependencies: - "@graphql-tools/utils" "8.0.1" - dataloader "2.0.0" - tslib "~2.3.0" - value-or-promise "1.0.10" - "@graphql-tools/batch-execute@^8.0.5": version "8.0.5" resolved "https://registry.yarnpkg.com/@graphql-tools/batch-execute/-/batch-execute-8.0.5.tgz#a0f8a9ff2c61209974c626faf3dd922a5c68d2b0" @@ -815,47 +744,35 @@ value-or-promise "1.0.10" "@graphql-tools/code-file-loader@^7.0.6": - version "7.0.6" - resolved "https://registry.yarnpkg.com/@graphql-tools/code-file-loader/-/code-file-loader-7.0.6.tgz#f6ca4554c11a9554fc305cf2d587148626d879e9" - integrity sha512-+xddAWyvrcW5Em3Yx+RJ7vwFMyOoS8pwqgeVr0CTZF9YbrQSPz7wQJRAVFZNJQaJOalKlPWxsmKkSyMQRG+t4A== + version "7.0.7" + resolved "https://registry.yarnpkg.com/@graphql-tools/code-file-loader/-/code-file-loader-7.0.7.tgz#ae5d4560da65eed89461f7dc0d56a5c938b0d7a7" + integrity sha512-GCiABoEmYEXo9TSgkLLQS/AxaIfN6efOOWNHGPOvXQ2DkMgg+5uX3hGd1h0nvPIzL4gC61Rr9llwMjovkoJlfw== dependencies: "@graphql-tools/graphql-tag-pluck" "^7.0.5" - "@graphql-tools/utils" "^8.1.1" + "@graphql-tools/utils" "^8.1.2" globby "^11.0.3" tslib "~2.3.0" unixify "^1.0.0" -"@graphql-tools/delegate@8.0.3": - version "8.0.3" - resolved "https://registry.yarnpkg.com/@graphql-tools/delegate/-/delegate-8.0.3.tgz#b9e7622ee8021bcf4499506b367828cb8da18916" - integrity sha512-u6TTTqslVDna/0v9kJSqIYeqa+TdZDQMqDlwrsLi7VsATnzgv0OAYxj55XxSBJQWh0oSM+i4EoGqbwj+wU2tvg== - dependencies: - "@graphql-tools/batch-execute" "^8.0.1" - "@graphql-tools/schema" "^8.0.1" - "@graphql-tools/utils" "8.0.1" - dataloader "2.0.0" - tslib "~2.3.0" - value-or-promise "1.0.10" - -"@graphql-tools/delegate@^8.1.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@graphql-tools/delegate/-/delegate-8.1.0.tgz#8e811bdb0079a87607d2f98685d1ff329fef4638" - integrity sha512-jJmty2rr4k2k6pG3H0qoCH9LDezjyVe0wLNUbVQcSfWBj1VgeHX6BF4qgbT5HsDD3oOP8ruiHZgbn6EbrOwDfA== +"@graphql-tools/delegate@^8.1.0", "@graphql-tools/delegate@^8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/delegate/-/delegate-8.1.1.tgz#d20e6d81a2900b1c8a69c2c0a3a8a0df2f9030c2" + integrity sha512-Vttd0nfYTqRnRMKLvk8s4cIi9U+OMXGc9CMZAlKkHrBJ6dGXjdSM+4n3p9rfWZc/FtbVk1FnNS4IFyMeKwFuxA== dependencies: "@graphql-tools/batch-execute" "^8.0.5" "@graphql-tools/schema" "^8.1.2" - "@graphql-tools/utils" "^8.1.1" + "@graphql-tools/utils" "^8.1.2" dataloader "2.0.0" tslib "~2.3.0" value-or-promise "1.0.10" "@graphql-tools/git-loader@^7.0.5": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@graphql-tools/git-loader/-/git-loader-7.0.5.tgz#b1000206eb48bc3d63d32f5e6cc3428d0ac0b691" - integrity sha512-f0O/5jXHClc8x6hIw3d4DUjoxj/dB9mimx5Q1YtLOSC67DRUbO3S0Pv54usX80EK4qcnVW9tK+6HqKFeFQB0Vw== + version "7.0.6" + resolved "https://registry.yarnpkg.com/@graphql-tools/git-loader/-/git-loader-7.0.6.tgz#f66221efbc8b93b2c2889fdf485615aaa0a45f66" + integrity sha512-6YoLqKdf1AiVQ7mJXWUQZI3un0r23JDFhp2ChIcR5uDKCHZkikSMUph6JvjajCLnPryPBGwr10659sRnESujmA== dependencies: "@graphql-tools/graphql-tag-pluck" "^7.0.5" - "@graphql-tools/utils" "^8.1.1" + "@graphql-tools/utils" "^8.1.2" is-glob "4.0.1" micromatch "^4.0.4" tslib "~2.3.0" @@ -871,25 +788,13 @@ cross-fetch "3.1.4" tslib "~2.3.0" -"@graphql-tools/graphql-file-loader@^7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-file-loader/-/graphql-file-loader-7.0.1.tgz#fc14da0c4c775c81b834cb7fcd50733fe1e10838" - integrity sha512-gSYh+W86GcR/TP8bLCPuNdAUeV1/y3+0czM32r6VxqZNiJjiSF6k68rb4F7M6jJ/1dA/SAEZpXLd94Dokc2s/g== - dependencies: - "@graphql-tools/import" "^6.2.6" - "@graphql-tools/utils" "8.0.1" - globby "^11.0.3" - is-glob "^4.0.1" - tslib "~2.3.0" - unixify "^1.0.0" - -"@graphql-tools/graphql-file-loader@^7.0.5": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-file-loader/-/graphql-file-loader-7.0.5.tgz#38d538fd1cf746627b38e948c8fa90d2148fed47" - integrity sha512-hYdz/zvA2z2M6zqgTCkEiqWPIKLsFirg1ZawaKzCLN0sUi+hRrLVTc1eIlz/vfR8ojvswEq81OMtk5d+lbHiHQ== +"@graphql-tools/graphql-file-loader@^7.0.1", "@graphql-tools/graphql-file-loader@^7.0.5": + version "7.0.6" + resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-file-loader/-/graphql-file-loader-7.0.6.tgz#602a2013dc926e17542c979b8f9af45187329bcb" + integrity sha512-jndtcNwPUQxEiY/3FKNbAb4dpNXO8tXoDZzrnvv+z/tf27ViiZW0KUBkO4Mw4b0sqaDq+fS4d6BXzVFQQUPolA== dependencies: "@graphql-tools/import" "^6.2.6" - "@graphql-tools/utils" "^8.1.1" + "@graphql-tools/utils" "^8.1.2" globby "^11.0.3" tslib "~2.3.0" unixify "^1.0.0" @@ -913,32 +818,23 @@ resolve-from "5.0.0" tslib "~2.2.0" -"@graphql-tools/json-file-loader@^7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/json-file-loader/-/json-file-loader-7.0.1.tgz#cdac2e967eb190154e55325535399ed651b0f1e6" - integrity sha512-UfZ3vA37d0OG28p8GijyIo5zRMXozz9f1TBcb++k0cQKmElILrnBHD4ZiNkWTFz5VBSKSjk4gpJD89D8BKKDyg== +"@graphql-tools/json-file-loader@^7.0.1", "@graphql-tools/json-file-loader@^7.1.2": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@graphql-tools/json-file-loader/-/json-file-loader-7.1.3.tgz#fef87d40cdad2afd61e0303155f2021587a7bcc0" + integrity sha512-Z6B1mwgmmit4BUc44NNSelF67Q7laJ98+QPNaIHBBpEHdCD2ToLPaIo2P+xrzf9BTS+hypXvupbEtxtKTUe9uQ== dependencies: - "@graphql-tools/utils" "8.0.1" - tslib "~2.3.0" - -"@graphql-tools/json-file-loader@^7.1.2": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@graphql-tools/json-file-loader/-/json-file-loader-7.1.2.tgz#8cff19b6fb179aaea806506d7d5699246b857ae2" - integrity sha512-/CrnNBwi4WDvt4yc2PcRmQxqGIOtK84s4iI5vsHwlcK7D02WX1cza4YEblFlMQUkxR9pmwGIYXverUOiaysqRA== - dependencies: - "@graphql-tools/utils" "^8.1.1" + "@graphql-tools/utils" "^8.1.2" globby "^11.0.3" tslib "~2.3.0" unixify "^1.0.0" "@graphql-tools/load@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@graphql-tools/load/-/load-7.1.0.tgz#dc561684faff70a7d4e98257f3886e6817a4589e" - integrity sha512-THh07BQ6Fmv9wCUIONWXFgkBe/3H9jB67aV7FJ+t+snACYvPhB7kDX8D6VC6RjCfcoaI1MNyHIgNR5JZ5GJ1ng== + version "7.1.9" + resolved "https://registry.yarnpkg.com/@graphql-tools/load/-/load-7.1.9.tgz#6bcff0a2866051ecdf514e6a8f82c2b4c0f5401d" + integrity sha512-4R0JLXRynPgHRWkGvL778XNH3IYX1sXjmEqGy3LPpJ4KR6woCO0us8oXNMAJMjjU4ZcOX6I8Jdj16G7qcHYTLA== dependencies: - "@graphql-tools/merge" "^6.2.16" - "@graphql-tools/utils" "8.0.1" - import-from "4.0.0" + "@graphql-tools/schema" "8.1.2" + "@graphql-tools/utils" "^8.1.2" p-limit "3.1.0" tslib "~2.3.0" @@ -952,21 +848,13 @@ p-limit "3.1.0" tslib "~2.3.0" -"@graphql-tools/merge@6.2.16", "@graphql-tools/merge@^6.2.16": - version "6.2.16" - resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-6.2.16.tgz#5dd5f582075c21ef4d74ef84dfcc4e8d0c2db186" - integrity sha512-KjZ1pppzKcr2Uspgb53p8uw5yhWVuGIL+sEroar7vLsClSsuiGib8OKVICAGWjC9wrCxGaL9SjJGavfXpJMQIg== - dependencies: - "@graphql-tools/schema" "^8.0.1" - "@graphql-tools/utils" "8.0.1" - tslib "~2.3.0" - -"@graphql-tools/merge@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.0.2.tgz#510d6a8da6a761853e18d36710a23791edbc2405" - integrity sha512-li/bl6RpcZCPA0LrSxMYMcyYk+brer8QYY25jCKLS7gvhJkgzEFpCDaX43V1+X13djEoAbgay2mCr3dtfJQQRQ== +"@graphql-tools/merge@^6.2.16": + version "6.2.17" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-6.2.17.tgz#4dedf87d8435a5e1091d7cc8d4f371ed1e029f1f" + integrity sha512-G5YrOew39fZf16VIrc49q3c8dBqQDD0ax5LYPiNja00xsXDi0T9zsEWVt06ApjtSdSF6HDddlu5S12QjeN8Tow== dependencies: - "@graphql-tools/utils" "^8.1.1" + "@graphql-tools/schema" "^8.0.2" + "@graphql-tools/utils" "8.0.2" tslib "~2.3.0" "@graphql-tools/merge@^8.1.0": @@ -978,15 +866,14 @@ tslib "~2.3.0" "@graphql-tools/mock@^8.1.2": - version "8.1.3" - resolved "https://registry.yarnpkg.com/@graphql-tools/mock/-/mock-8.1.3.tgz#bad4aa7a6ce3e37739e2df9a86ac3925bfde15b2" - integrity sha512-xtY3amuEdPLeoSALNN4cEaOmietbVaxFAVfkn08v0AHr7zfXyy+sCLn98y8BXxTaow8/nTMBCTdCZ5Qe9gtbQQ== + version "8.2.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/mock/-/mock-8.2.2.tgz#ff6b520a7234f23bff302406286d2e68b8177792" + integrity sha512-3cUJi14UHW1/8mebbXlAqfZl78IxeKzF2QlcJV5PSRQe27Dp/UnkHyid1UH/iwBdA98J7l0uw8NU1MRRVjhjIA== dependencies: - "@graphql-tools/schema" "^7.0.0" - "@graphql-tools/utils" "^7.0.0" + "@graphql-tools/schema" "^8.1.2" + "@graphql-tools/utils" "^8.1.1" fast-json-stable-stringify "^2.1.0" - ts-is-defined "^1.0.0" - tslib "~2.2.0" + tslib "~2.3.0" "@graphql-tools/optimize@^1.0.1": version "1.0.1" @@ -1030,36 +917,7 @@ relay-compiler "11.0.2" tslib "~2.3.0" -"@graphql-tools/schema@8.2.0": - version "8.2.0" - resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.2.0.tgz#ae75cbb2df6cee9ed6d89fce56be467ab23758dc" - integrity sha512-ufmI5mJQa8NJczzfkh0pUttKvspqDcT5LLakA3jUmOrrE4d4NVj6onZlazdTzF5sAepSNqanFnwhrxZpCAJMKg== - dependencies: - "@graphql-tools/merge" "^8.1.0" - "@graphql-tools/utils" "^8.2.0" - tslib "~2.3.0" - value-or-promise "1.0.10" - -"@graphql-tools/schema@^7.0.0", "@graphql-tools/schema@^7.1.5": - version "7.1.5" - resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-7.1.5.tgz#07b24e52b182e736a6b77c829fc48b84d89aa711" - integrity sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA== - dependencies: - "@graphql-tools/utils" "^7.1.2" - tslib "~2.2.0" - value-or-promise "1.0.6" - -"@graphql-tools/schema@^8.0.1": - version "8.0.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.0.1.tgz#178b734f3784e632a76bdd144ffa8a32169a6f4a" - integrity sha512-QG2HGLJjmsNc1wcj+rwZTEArgfMp7rsrb8iVq4P8ce1mDYAt6kRV6bLyPVb9q/j8Ik2zBc/B/Y1jPsnAVUHwdA== - dependencies: - "@graphql-tools/merge" "6.2.16" - "@graphql-tools/utils" "8.0.1" - tslib "~2.3.0" - value-or-promise "1.0.10" - -"@graphql-tools/schema@^8.1.2": +"@graphql-tools/schema@8.1.2", "@graphql-tools/schema@^8.0.0", "@graphql-tools/schema@^8.0.2", "@graphql-tools/schema@^8.1.2": version "8.1.2" resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.1.2.tgz#913879da1a7889a9488e9b7dc189e7c83eff74be" integrity sha512-rX2pg42a0w7JLVYT+f/yeEKpnoZL5PpLq68TxC3iZ8slnNBNjfVfvzzOn8Q8Q6Xw3t17KP9QespmJEDfuQe4Rg== @@ -1069,44 +927,28 @@ tslib "~2.3.0" value-or-promise "1.0.10" -"@graphql-tools/url-loader@^7.0.11": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@graphql-tools/url-loader/-/url-loader-7.0.11.tgz#4826c15efd4ac382e5e9aebdf0bba7d5c6da990c" - integrity sha512-4u+h5RtjJXaQjhfBdClreNWavBZqa0U92Cx3Z+zFvIsIYRrEboy7x+cH4QD9OEca/LuUcskJ4yyWllztz/ktow== +"@graphql-tools/schema@8.2.0": + version "8.2.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.2.0.tgz#ae75cbb2df6cee9ed6d89fce56be467ab23758dc" + integrity sha512-ufmI5mJQa8NJczzfkh0pUttKvspqDcT5LLakA3jUmOrrE4d4NVj6onZlazdTzF5sAepSNqanFnwhrxZpCAJMKg== dependencies: - "@ardatan/fetch-event-source" "2.0.2" - "@graphql-tools/delegate" "^8.1.0" - "@graphql-tools/utils" "^8.1.1" - "@graphql-tools/wrap" "^8.0.13" - "@n1ru4l/graphql-live-query" "0.7.1" - "@types/websocket" "1.0.4" - abort-controller "3.0.0" - cross-fetch "3.1.4" - extract-files "11.0.0" - form-data "4.0.0" - graphql-ws "^5.0.0" - is-promise "4.0.0" - isomorphic-ws "4.0.1" - lodash "4.17.21" - meros "1.1.4" - subscriptions-transport-ws "^0.10.0" - sync-fetch "0.3.0" + "@graphql-tools/merge" "^8.1.0" + "@graphql-tools/utils" "^8.2.0" tslib "~2.3.0" - valid-url "1.0.9" value-or-promise "1.0.10" - ws "8.2.0" -"@graphql-tools/url-loader@^7.0.3": - version "7.0.3" - resolved "https://registry.yarnpkg.com/@graphql-tools/url-loader/-/url-loader-7.0.3.tgz#3f1ea7a0987af8cf84e6a2c0e4670380f5690fa9" - integrity sha512-9QhYaA6nCAleFSw5WvNgwy/ixkcJTrMfFAP3Ofsgk9Cau0iUesrgRYEYNJldf0NevP7wHzaVuWqRP/xHLqW2iw== +"@graphql-tools/url-loader@^7.0.11", "@graphql-tools/url-loader@^7.0.3": + version "7.0.12" + resolved "https://registry.yarnpkg.com/@graphql-tools/url-loader/-/url-loader-7.0.12.tgz#846e358d7e8b6a24b501778f42422007d175b134" + integrity sha512-CSGHi0Z2ow6QZp0yx+zVHGj/FpZ+e+HH4AWNI++w9nF8wEHDSd5ghPXpXz58KmqXTE5+Trf5zEjtIm/2mcDYBA== dependencies: "@ardatan/fetch-event-source" "2.0.2" - "@graphql-tools/delegate" "8.0.3" - "@graphql-tools/utils" "8.0.1" - "@graphql-tools/wrap" "^8.0.3" + "@graphql-tools/delegate" "^8.1.1" + "@graphql-tools/utils" "^8.1.2" + "@graphql-tools/wrap" "^8.0.13" "@n1ru4l/graphql-live-query" "0.7.1" "@types/websocket" "1.0.4" + "@types/ws" "^7.4.7" abort-controller "3.0.0" cross-fetch "3.1.4" extract-files "11.0.0" @@ -1121,12 +963,12 @@ tslib "~2.3.0" valid-url "1.0.9" value-or-promise "1.0.10" - ws "8.0.0" + ws "8.2.0" -"@graphql-tools/utils@8.0.1", "@graphql-tools/utils@^8.0.1": - version "8.0.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.0.1.tgz#bf09e8d6d17c4a0830f0fbf2c69dd725de19058c" - integrity sha512-gjQk6sht4b0/hcG+QEVxfMyO8bn5tuU1nIOVhQ4whgFaUmrnb3hx2mwzz1EJzfIOAuHKE8tY0lu6jt3bGTD4yg== +"@graphql-tools/utils@8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.0.2.tgz#795a8383cdfdc89855707d62491c576f439f3c51" + integrity sha512-gzkavMOgbhnwkHJYg32Adv6f+LxjbQmmbdD5Hty0+CWxvaiuJq+nU6tzb/7VSU4cwhbNLx/lGu2jbCPEW1McZQ== dependencies: tslib "~2.3.0" @@ -1137,19 +979,10 @@ dependencies: tslib "~2.3.0" -"@graphql-tools/utils@^7.0.0", "@graphql-tools/utils@^7.1.2", "@graphql-tools/utils@^7.9.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-7.10.0.tgz#07a4cb5d1bec1ff1dc1d47a935919ee6abd38699" - integrity sha512-d334r6bo9mxdSqZW6zWboEnnOOFRrAPVQJ7LkU8/6grglrbcu6WhwCLzHb90E94JI3TD3ricC3YGbUqIi9Xg0w== - dependencies: - "@ardatan/aggregate-error" "0.0.6" - camel-case "4.1.2" - tslib "~2.2.0" - -"@graphql-tools/utils@^8.1.1": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.1.1.tgz#2ef056a1d6e1e909085e1115d3bb48f890c2a2b6" - integrity sha512-QbFNoBmBiZ+ej4y6mOv8Ba4lNhcrTEKXAhZ0f74AhdEXi7b9xbGUH/slO5JaSyp85sGQYIPmxjRPpXBjLklbmw== +"@graphql-tools/utils@^8.0.0", "@graphql-tools/utils@^8.0.1", "@graphql-tools/utils@^8.1.1", "@graphql-tools/utils@^8.1.2": + version "8.1.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.1.2.tgz#a376259fafbca7532fda657e3abeec23b545e5d3" + integrity sha512-3G+NIBR5mHjPm78jAD0l07JRE0XH+lr9m7yL/wl69jAzK0Jr/H+/Ok4ljEolI70iglz+ZhIShVPAwyesF6rnFg== dependencies: tslib "~2.3.0" @@ -1164,17 +997,6 @@ tslib "~2.3.0" value-or-promise "1.0.10" -"@graphql-tools/wrap@^8.0.3": - version "8.0.3" - resolved "https://registry.yarnpkg.com/@graphql-tools/wrap/-/wrap-8.0.3.tgz#8282650ea543a4ec4c2b5544a1e3623df162547b" - integrity sha512-32tZiT5pEdyAzhU3jUW2ff/PS+z00jVGDvoy9m+LBG/NXMPb4JGFh3mDB91ZYnqrxvUHd2UNckxf+rg8Ej8tLg== - dependencies: - "@graphql-tools/delegate" "8.0.3" - "@graphql-tools/schema" "^8.0.1" - "@graphql-tools/utils" "8.0.1" - tslib "~2.3.0" - value-or-promise "1.0.10" - "@iarna/toml@^2.2.5": version "2.2.5" resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" @@ -1390,28 +1212,6 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@jest/types@^27.0.2": - version "27.0.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.0.2.tgz#e153d6c46bda0f2589f0702b071f9898c7bbd37e" - integrity sha512-XpjCtJ/99HB4PmyJ2vgmN7vT+JLP7RW1FBT9RgnMFS4Dt7cvIyBee8O3/j98aUZ34ZpenPZFqmaaObWSeL65dg== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" - -"@jest/types@^27.0.6": - version "27.0.6" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.0.6.tgz#9a992bc517e0c49f035938b8549719c2de40706b" - integrity sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" - "@jest/types@^27.1.1": version "27.1.1" resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.1.1.tgz#77a3fc014f906c65752d12123a0134359707c0ad" @@ -1428,6 +1228,21 @@ resolved "https://registry.yarnpkg.com/@josephg/resolvable/-/resolvable-1.0.1.tgz#69bc4db754d79e1a2f17a650d3466e038d94a5eb" integrity sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg== +"@mapbox/node-pre-gyp@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz#2a0b32fcb416fb3f2250fd24cb2a81421a4f5950" + integrity sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA== + dependencies: + detect-libc "^1.0.3" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.1" + nopt "^5.0.0" + npmlog "^4.1.2" + rimraf "^3.0.2" + semver "^7.3.4" + tar "^6.1.0" + "@n1ru4l/graphql-live-query@0.7.1": version "0.7.1" resolved "https://registry.yarnpkg.com/@n1ru4l/graphql-live-query/-/graphql-live-query-0.7.1.tgz#c020d017c3ed6bcfdde49a7106ba035e4d0774f5" @@ -1447,18 +1262,13 @@ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz#94c23db18ee4653e129abd26fb06f870ac9e1ee2" - integrity sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA== + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@panva/asn1.js@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" - integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== - "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -1519,10 +1329,10 @@ dependencies: any-observable "^0.3.0" -"@sinclair/typebox@^0.20.0": - version "0.20.4" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.20.4.tgz#d1c68a9aa320f2e1ae77d1bed595c03b3853176b" - integrity sha512-cWmUFRMdGanzrrDzE1uHyWG0FeDGf8EWtzxbGQ0zD+Dad8Y4f0xSBe1kK+27TWQKQKcC9/GuOTysmOqaQfK3Aw== +"@sinclair/typebox@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.20.0.tgz#f0e620ea3a4f82b2fbbd16d89a92fbe5c869ba90" + integrity sha512-qFKbghRt4sutWEQYchceiErGVhunbCoCssCcOjLXN6vtMMyaycCQGGEgd1kGyueucONnnO+nNYUXLXATU35j7w== "@sindresorhus/is@^0.14.0": version "0.14.0" @@ -1583,9 +1393,9 @@ "@types/node" "*" "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": - version "7.1.14" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.14.tgz#faaeefc4185ec71c389f4501ee5ec84b170cc402" - integrity sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g== + version "7.1.15" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.15.tgz#2ccfb1ad55a02c83f8e0ad327cbc332f55eb1024" + integrity sha512-bxlMKPDbY8x5h6HBwVzEOk2C8fb6SLfYQ5Jw3uBYuYF1lfWk/kbLd81la82vrIkBb0l+JdmrZaDikPrNxpS/Ew== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" @@ -1594,36 +1404,35 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz#f3d71178e187858f7c45e30380f8f1b7415a12d8" - integrity sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ== + version "7.6.3" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.3.tgz#f456b4b2ce79137f768aa130d2423d2f0ccfaba5" + integrity sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.0.tgz#0c888dd70b3ee9eebb6e4f200e809da0076262be" - integrity sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A== + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.11.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.11.1.tgz#654f6c4f67568e24c23b367e947098c6206fa639" - integrity sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw== + version "7.14.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" + integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== dependencies: "@babel/types" "^7.3.0" -"@types/body-parser@*": - version "1.19.0" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" - integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== +"@types/bcrypt@5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/bcrypt/-/bcrypt-5.0.0.tgz#a835afa2882d165aff5690893db314eaa98b9f20" + integrity sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw== dependencies: - "@types/connect" "*" "@types/node" "*" -"@types/body-parser@1.19.1": +"@types/body-parser@*", "@types/body-parser@1.19.1": version "1.19.1" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.1.tgz#0c0174c42a7d017b818303d4b5d969cb0b75929c" integrity sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg== @@ -1639,61 +1448,53 @@ "@types/express" "*" "@types/connect@*": - version "3.4.34" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" - integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ== + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== dependencies: "@types/node" "*" +"@types/cookie-parser@1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.2.tgz#e4d5c5ffda82b80672a88a4281aaceefb1bd9df5" + integrity sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg== + dependencies: + "@types/express" "*" + +"@types/cookiejar@*": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8" + integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog== + +"@types/cors@2.8.11": + version "2.8.11" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.11.tgz#0bbd036cc6c8c63e0e5d64115fa9692eabb7eaa3" + integrity sha512-64aQQZXPSo1fdLEE/utClOFVUqDUjyh5j3JorcCTlYQm4r5wsfggx6yhSY6hNudJLkbmIt+pO6xWyCnM0EQgPw== + "@types/cors@2.8.12": version "2.8.12" resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== -"@types/express-jwt@0.0.42": - version "0.0.42" - resolved "https://registry.yarnpkg.com/@types/express-jwt/-/express-jwt-0.0.42.tgz#4f04e1fadf9d18725950dc041808a4a4adf7f5ae" - integrity sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag== - dependencies: - "@types/express" "*" - "@types/express-unless" "*" - -"@types/express-serve-static-core@4.17.24": - version "4.17.24" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz#ea41f93bf7e0d59cd5a76665068ed6aab6815c07" - integrity sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA== +"@types/express-serve-static-core@4.17.23": + version "4.17.23" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.23.tgz#721c371fc53fe7b3ea40d8977b209b90cb275f58" + integrity sha512-WYqTtTPTJn9kXMdnAH5HPPb7ctXvBpP4PfuOb8MV4OHPQWHhDZixGlhgR159lJPpKm23WOdoCkt2//cCEaOJkw== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" -"@types/express-serve-static-core@^4.17.18": - version "4.17.21" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz#a427278e106bca77b83ad85221eae709a3414d42" - integrity sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA== +"@types/express-serve-static-core@4.17.24", "@types/express-serve-static-core@^4.17.18": + version "4.17.24" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz#ea41f93bf7e0d59cd5a76665068ed6aab6815c07" + integrity sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" -"@types/express-unless@*": - version "0.5.1" - resolved "https://registry.yarnpkg.com/@types/express-unless/-/express-unless-0.5.1.tgz#4f440b905e42bbf53382b8207bc337dc5ff9fd1f" - integrity sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw== - dependencies: - "@types/express" "*" - -"@types/express@*": - version "4.17.12" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.12.tgz#4bc1bf3cd0cfe6d3f6f2853648b40db7d54de350" - integrity sha512-pTYas6FrP15B1Oa0bkN5tQMNqOcVXa9j4FTFtO8DWI9kppKib+6NJtfTOOLcwxuuYvcX2+dVG6et1SxW/Kc17Q== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/express@4.17.13": +"@types/express@*", "@types/express@4.17.13": version "4.17.13" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== @@ -1710,7 +1511,7 @@ dependencies: "@types/node" "*" -"@types/graphql-depth-limit@^1.1.2": +"@types/graphql-depth-limit@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@types/graphql-depth-limit/-/graphql-depth-limit-1.1.2.tgz#8e8a7b68548d703a3c3bd7f0531f314b3051f556" integrity sha512-CJoghYUfE5/IKrqexgSsTECP0RcP2Ii+ulv/BjjFniABNAMgfwCTKFnCkkRskg9Sr3WZeJrSLjwBhNFetP5Tyw== @@ -1753,23 +1554,23 @@ pretty-format "^27.0.0" "@types/js-yaml@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.1.tgz#5544730b65a480b18ace6b6ce914e519cec2d43b" - integrity sha512-xdOvNmXmrZqqPy3kuCQ+fz6wA0xU5pji9cd1nDrflWaAWtYLLGk5ykW0H6yg5TVyehHP1pfmuuSaZkhP+kspVA== + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.3.tgz#9f33cd6fbf0d5ec575dc8c8fc69c7fec1b4eb200" + integrity sha512-5t9BhoORasuF5uCPr+d5/hdB++zRFUTMIZOzbNkr+jZh3yQht4HYbRDyj9fY8n2TZT30iW9huzav73x4NikqWg== "@types/json-stable-stringify@^1.0.32": - version "1.0.32" - resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz#121f6917c4389db3923640b2e68de5fa64dda88e" - integrity sha512-q9Q6+eUEGwQkv4Sbst3J4PNgDOvpuVuKj79Hl/qnmBMEIPzB5QoFRUtjcgcg2xNUZyYUGXBk5wYIBKHt0A+Mxw== + version "1.0.33" + resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.33.tgz#099b0712d824d15e2660c20e1c16e6a8381f308c" + integrity sha512-qEWiQff6q2tA5gcJGWwzplQcXdJtm+0oy6IHGHzlOf3eFAkGE/FIPXZK9ofWgNSHVp8AFFI33PJJshS0ei3Gvw== "@types/jsonwebtoken@^8.5.0": - version "8.5.2" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.2.tgz#eb71c717b3b8681bb85fbd2950c9c4c5d4506748" - integrity sha512-X8BOCkp+WJVNYCYIBugREtVZa4Y09Or9HDx6xqRZem5F8jJV8FuJgNessXyMuv9+U8pjnvdezASwU28uw+1scw== + version "8.5.5" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.5.tgz#da5f2f4baee88f052ef3e4db4c1a0afb46cff22c" + integrity sha512-OGqtHQ7N5/Ap/TUwO6IgHDuLiAoTmHhGpNvgkCm/F4N6pKzx/RBSfr2OXZSwC6vkfnsEdb6+7DNZVtiXiwdwFw== dependencies: "@types/node" "*" -"@types/lodash@^4.14.170": +"@types/lodash@4.14.170": version "4.14.170" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6" integrity sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q== @@ -1785,14 +1586,14 @@ integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== "@types/minimatch@^3.0.3": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" - integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== "@types/node@*": - version "14.14.31" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz" - integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g== + version "16.7.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.6.tgz#8666478db8095aa66e25b7e469f3e7b53ea2855e" + integrity sha512-VESVNFoa/ahYA62xnLBjo5ur6gPsgEE5cNRy8SrdnkZ2nwJSW0kJ4ufbFr2zuU9ALtHM8juY53VcRoTA7htXSg== "@types/node@14.17.16": version "14.17.16" @@ -1809,25 +1610,32 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/passport@1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.7.tgz#85892f14932168158c86aecafd06b12f5439467a" + integrity sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw== + dependencies: + "@types/express" "*" + "@types/prettier@^2.1.5": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.0.tgz#2e8332cc7363f887d32ec5496b207d26ba8052bb" - integrity sha512-hkc1DATxFLQo4VxPDpMH1gCkPpBbpOoJ/4nhuXw4n63/0R6bCpQECj4+K226UJ4JO/eJQz+1mC2I7JsWanAdQw== + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3" + integrity sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog== "@types/qs@*": - version "6.9.6" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" - integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA== + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== "@types/range-parser@*": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" - integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== "@types/serve-static@*": - version "1.13.9" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e" - integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA== + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== dependencies: "@types/mime" "^1" "@types/node" "*" @@ -1838,9 +1646,9 @@ integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== "@types/stack-utils@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" - integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== "@types/strip-bom@^3.0.0": version "3.0.0" @@ -1852,6 +1660,26 @@ resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== +"@types/superagent@*": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-4.1.12.tgz#fad68c6712936892ad24cf94f2f7a07cc749fd0f" + integrity sha512-1GQvD6sySQPD6p9EopDFI3f5OogdICl1sU/2ij3Esobz/RtL9fWZZDPmsuv7eiy5ya+XNiPAxUcI3HIUTJa+3A== + dependencies: + "@types/cookiejar" "*" + "@types/node" "*" + +"@types/supertest@2.0.11": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.11.tgz#2e70f69f220bc77b4f660d72c2e1a4231f44a77d" + integrity sha512-uci4Esokrw9qGb9bvhhSVEjd6rkny/dk5PK/Qz4yxKiyppEI+dOPlNrZBahE3i+PoKFYyDxChVXZ/ysS/nrm1Q== + dependencies: + "@types/superagent" "*" + +"@types/uuid@8.3.1": + version "8.3.1" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" + integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== + "@types/validator@13.6.3": version "13.6.3" resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.6.3.tgz#31ca2e997bf13a0fffca30a25747d5b9f7dbb7de" @@ -1864,7 +1692,7 @@ dependencies: "@types/node" "*" -"@types/ws@7.4.7": +"@types/ws@7.4.7", "@types/ws@^7.4.7": version "7.4.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== @@ -1872,21 +1700,21 @@ "@types/node" "*" "@types/yargs-parser@*": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" - integrity sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA== + version "20.2.1" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" + integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== "@types/yargs@^13.0.0": - version "13.0.11" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.11.tgz#def2f0c93e4bdf2c61d7e34899b17e34be28d3b1" - integrity sha512-NRqD6T4gktUrDi1o1wLH3EKC1o2caCr7/wR87ODcbVITQF106OM3sFN92ysZ++wqelOd1CTzatnOBRDYYG6wGQ== + version "13.0.12" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.12.tgz#d895a88c703b78af0465a9de88aa92c61430b092" + integrity sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ== dependencies: "@types/yargs-parser" "*" "@types/yargs@^16.0.0": - version "16.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.3.tgz#4b6d35bb8e680510a7dc2308518a80ee1ef27e01" - integrity sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ== + version "16.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== dependencies: "@types/yargs-parser" "*" @@ -1950,17 +1778,17 @@ agent-base@6: dependencies: debug "4" -ajv-formats@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.0.tgz#96eaf83e38d32108b66d82a9cb0cfa24886cdfeb" - integrity sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q== +ajv-formats@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== dependencies: ajv "^8.0.0" -ajv@^8.0.0, ajv@^8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.0.tgz#60cc45d9c46a477d80d92c48076d972c342e5720" - integrity sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ== +ajv@8.6.2, ajv@^8.0.0: + version "8.6.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" + integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -2048,13 +1876,13 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -apollo-datasource@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-3.0.1.tgz#e4061b284cdda2b6e2009a1ab26129e92a8e401c" - integrity sha512-WSl3ABhhw4d9vqlspb3AQwx062XM3Osig8ZhuMK5nyJiQsCmTmE86x3gMsl96qyfX2qQ/ooX2hgJpPgxWdvIdA== +apollo-datasource@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-3.1.0.tgz#44153cb99c7602f4524397ebc8f13e486a010c09" + integrity sha512-ywcVjuWNo84eMB9uBOYygQI+00+Ne4ShyPIxJzT//sn1j1Fu3J+KStMNd6s1jyERWgjGZzxkiLn6nLmwsGymBg== dependencies: - apollo-server-caching "^3.0.1" - apollo-server-env "^4.0.1" + apollo-server-caching "^3.1.0" + apollo-server-env "^4.0.3" apollo-graphql@^0.9.0: version "0.9.3" @@ -2072,32 +1900,32 @@ apollo-reporting-protobuf@^3.0.0: dependencies: "@apollo/protobufjs" "1.2.2" -apollo-server-caching@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-3.0.1.tgz#bb6e51442266cb3ff05611c24c4e2f2fe8d6949e" - integrity sha512-Cd0imFQlU6IKrkm+RNY0MQvKTMBTME+518EuwCaw3TKNUYDpir1vOuIdc4bALXDANilOR73k/UQs/oPxayXfrg== +apollo-server-caching@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-3.1.0.tgz#c68f2159ad8a25a0bdbb18ad6bdbbde59cd4647d" + integrity sha512-bZ4bo0kSAsax9LbMQPlpuMTkQ657idF2ehOYe4Iw+8vj7vfAYa39Ii9IlaVAFMC1FxCYzLNFz+leZBm/Stn/NA== dependencies: lru-cache "^6.0.0" -apollo-server-core@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-3.0.1.tgz#2207c533cda87a5af0d2d5426c09677b6b6a4bd4" - integrity sha512-Aza5bNM0rctdmh1thY1c/OyI5WsQ20DcZpoDVm/tQ8gP8fcWYdjDW187wv+8DDKFvex3L46AqqxeXI1JJ6TFRA== +apollo-server-core@^3.0.0, apollo-server-core@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-3.3.0.tgz#f973c6f755884f8e17452cb9022672ae6f0ed9e7" + integrity sha512-KmkzKVG3yjybouDyUX6Melv39u1EOFipvAKP17IlPis/TjVbubJmb6hkE0am/g2RipyhRvlpxAjHqPaCTXR1dQ== dependencies: "@apollographql/apollo-tools" "^0.5.1" "@apollographql/graphql-playground-html" "1.6.29" "@graphql-tools/mock" "^8.1.2" - "@graphql-tools/schema" "^7.1.5" - "@graphql-tools/utils" "^7.9.0" + "@graphql-tools/schema" "^8.0.0" + "@graphql-tools/utils" "^8.0.0" "@josephg/resolvable" "^1.0.0" - apollo-datasource "^3.0.1" + apollo-datasource "^3.1.0" apollo-graphql "^0.9.0" apollo-reporting-protobuf "^3.0.0" - apollo-server-caching "^3.0.1" - apollo-server-env "^4.0.1" - apollo-server-errors "^3.0.1" - apollo-server-plugin-base "^3.0.1" - apollo-server-types "^3.0.1" + apollo-server-caching "^3.1.0" + apollo-server-env "^4.0.3" + apollo-server-errors "^3.1.0" + apollo-server-plugin-base "^3.2.0" + apollo-server-types "^3.2.0" async-retry "^1.2.1" fast-json-stable-stringify "^2.1.0" graphql-tag "^2.11.0" @@ -2106,23 +1934,39 @@ apollo-server-core@^3.0.1: sha.js "^2.4.11" uuid "^8.0.0" -apollo-server-env@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-4.0.1.tgz#da61b24c8422d2989b4a6d129f4667c93d64fda3" - integrity sha512-Pxalh/TyFkbWvhMoi/xVW1BbSBj9yUEX54SLfNPNiV5e+FRzz/bPxLQmdg8piWbi44/7PX497OH162FKklvWHA== +apollo-server-env@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-4.0.3.tgz#082a5c1dd4dfb3b34de5e1fa7dc170dd15a5062f" + integrity sha512-B32+RUOM4GUJAwnQqQE1mT1BG7+VfW3a0A87Bp3gv/q8iNnhY2BIWe74Qn03pX8n27g3EGVCt0kcBuHhjG5ltA== dependencies: node-fetch "^2.6.1" - util.promisify "^1.0.1" -apollo-server-errors@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-3.0.1.tgz#0dfcc1fe8b10eab311fc7e4f6da93189ea6bcdae" - integrity sha512-PSp64IFeN1YK5EYZ3V/8iDRESMMyE00h1vE5aCr83wHL3T0mN7VRiMKoOIZ+2rUtnn7CpK73o6QLmouhxPtXsQ== +apollo-server-errors@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-3.1.0.tgz#0b890dc7ae36a1f0ca4841d353e8d1c3c6524ee2" + integrity sha512-bUmobPEvtcBFt+OVHYqD390gacX/Cm5s5OI5gNZho8mYKAA6OjgnRlkm/Lti6NzniXVxEQyD5vjkC6Ox30mGFg== -apollo-server-express@^3.0.0, apollo-server-express@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-3.0.1.tgz#e450cdfe4d2ada5492b46868ad9fc72d3fd7c9f0" - integrity sha512-M6NZkU7ertQsMjNljeKi7l1ciIRaGaru7Putus2Qbu85b8/rUnXntlasF3AKYFFd4vl4cUIYRS7YjLbb42S1Eg== +apollo-server-express@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-3.0.0.tgz#5692cb74f5ab32ea378453842e82a368ba7ad20b" + integrity sha512-5xLr7B1Ky+OEk1aicBv3cU/ynVSsAij92sZsJD2MjokR0+VHM40aoi9++ZFAaXgUh2/amsWBjPC5dFN+1VMdJQ== + dependencies: + "@types/accepts" "^1.3.5" + "@types/body-parser" "1.19.1" + "@types/cors" "2.8.11" + "@types/express" "4.17.13" + "@types/express-serve-static-core" "4.17.23" + accepts "^1.3.5" + apollo-server-core "^3.0.0" + apollo-server-types "^3.0.0" + body-parser "^1.19.0" + cors "^2.8.5" + parseurl "^1.3.3" + +apollo-server-express@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-3.3.0.tgz#23ec8b102a4758548c1416fb4770334e814ffb12" + integrity sha512-qJedh77IxbfT+HpYsDraC2CGdy08wiWTwoKYXjRK4S/DHbe94A4957/1blw4boYO4n44xRKQd1k6zxiixCp+XQ== dependencies: "@types/accepts" "^1.3.5" "@types/body-parser" "1.19.1" @@ -2130,38 +1974,51 @@ apollo-server-express@^3.0.0, apollo-server-express@^3.0.1: "@types/express" "4.17.13" "@types/express-serve-static-core" "4.17.24" accepts "^1.3.5" - apollo-server-core "^3.0.1" - apollo-server-types "^3.0.1" + apollo-server-core "^3.3.0" + apollo-server-types "^3.2.0" body-parser "^1.19.0" cors "^2.8.5" parseurl "^1.3.3" -apollo-server-plugin-base@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-3.0.1.tgz#675fa6d98934d3606896509d9de49444516d5b29" - integrity sha512-Ws6fiIavOCYWWNDgRjNKBgrsU4UDCoY2ejdoZQqJ2b/d2fF5EKtnhmwKrzVAeo+LTV5yJkUf1qv9y2X/fNZU+w== +apollo-server-plugin-base@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-3.2.0.tgz#415337a0b1b88fc1d5f5620130a51e2935dd8dbf" + integrity sha512-anjyiw79wxU4Cj2bYZFWQqZPjuaZ4mVJvxCoyvkFrNvjPua9dovCOfpng43C5NwdsqJpz78Vqs236eFM2QoeaA== dependencies: - apollo-server-types "^3.0.1" + apollo-server-types "^3.2.0" -apollo-server-types@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-3.0.1.tgz#ec2e7688e67b1ab8173ac013dd4ed1e9391d3932" - integrity sha512-OpPCBngyBNQp0QKIAWVHaOn68Iwi+b0BBNu8USkAN+1vD5gOD7KO62A3GRpdxKKE/SJXbOtTR6uioVY8WnOFoQ== +apollo-server-types@^3.0.0, apollo-server-types@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-3.2.0.tgz#6243b34d35fbb09ded2cc84bf7e5f59968ccfa21" + integrity sha512-Fh7QP84ufDZHbLzoLyyxyzznlW8cpgEZYYkGsS1i36zY4VaAt5OUOp1f+FxWdLGehq0Arwb6D1W7y712IoZ/JQ== dependencies: apollo-reporting-protobuf "^3.0.0" - apollo-server-caching "^3.0.1" - apollo-server-env "^4.0.1" + apollo-server-caching "^3.1.0" + apollo-server-env "^4.0.3" -apollo-server@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-3.0.1.tgz#98e0d500a283666ed1680824fd2131918dbcb92a" - integrity sha512-xUcASfunTyqBnTIs++XmVXbjUsgdTy/FuP1wQxh1MbPSePBPeT5yuJg1Umak72yigyrjOCkbv/R6AGsQxyZt/g== +apollo-server@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-3.0.0.tgz#0485f3fae5ca3d8fbcd4f1235462472a7b9d7d90" + integrity sha512-ugNy3kYs7sAAX4nhcWBeCH9V9lQAKefj8S/sNYCVBSdqagpj7fwHT5wpnF1BLAiY1Z1O1qulnK3WBEdttSnKUA== dependencies: - apollo-server-core "^3.0.1" - apollo-server-express "^3.0.1" + apollo-server-core "^3.0.0" + apollo-server-express "^3.0.0" express "^4.17.1" stoppable "^1.1.0" +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -2235,11 +2092,11 @@ assign-symbols@^1.0.0: integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= async-retry@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.1.tgz#139f31f8ddce50c0870b0ba558a6079684aaed55" - integrity sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA== + version "1.3.3" + resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" + integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== dependencies: - retry "0.12.0" + retry "0.13.1" asynckit@^0.4.0: version "0.4.0" @@ -2390,6 +2247,14 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +bcrypt@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.1.tgz#f1a2c20f208e2ccdceea4433df0c8b2c54ecdf71" + integrity sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.0" + node-addon-api "^3.1.0" + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" @@ -2467,15 +2332,15 @@ browser-process-hrtime@^1.0.0: integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== browserslist@^4.16.6: - version "4.16.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" - integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== + version "4.16.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.8.tgz#cb868b0b554f137ba6e33de0ecff2eda403c4fb0" + integrity sha512-sc2m9ohR/49sWEbPj14ZSSZqp+kbi16aLao42Hmn3Z8FpjuMaq2xCA2l4zl9ITfyzvnvyE0hcg62YkIGKxgaNQ== dependencies: - caniuse-lite "^1.0.30001219" - colorette "^1.2.2" - electron-to-chromium "^1.3.723" + caniuse-lite "^1.0.30001251" + colorette "^1.3.0" + electron-to-chromium "^1.3.811" escalade "^3.1.1" - node-releases "^1.1.71" + node-releases "^1.1.75" bs-logger@0.x: version "0.2.6" @@ -2497,9 +2362,9 @@ buffer-equal-constant-time@1.0.1: integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer-writer@2.0.0: version "2.0.0" @@ -2565,7 +2430,7 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camel-case@4.1.2, camel-case@^4.1.2: +camel-case@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== @@ -2583,10 +2448,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-lite@^1.0.30001219: - version "1.0.30001240" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001240.tgz#ec15d125b590602c8731545c5351ff054ad2d52f" - integrity sha512-nb8mDzfMdxBDN7ZKx8chWafAdBp5DAAlpWvNyUGe5tcDWd838zpzDN3Rah9cjCqhfOKkrvx40G2SDtP0qiWX/w== +caniuse-lite@^1.0.30001251: + version "1.0.30001252" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001252.tgz#cb16e4e3dafe948fc4a9bb3307aea054b912019a" + integrity sha512-I56jhWDGMtdILQORdusxBOH+Nl/KgQSdDmpJezYddnAkVOmnoU8zwjTV9xAjMIYxr0iPreEAVylCGcmHCjfaOw== capital-case@^1.0.4: version "1.0.4" @@ -2626,9 +2491,9 @@ chalk@^3.0.0: supports-color "^7.1.0" chalk@^4.0.0, chalk@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" @@ -2692,6 +2557,11 @@ chokidar@^3.2.2, chokidar@^3.5.1, chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -2703,9 +2573,9 @@ ci-info@^3.1.1: integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== cjs-module-lexer@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.1.tgz#2fd46d9906a126965aa541345c499aaa18e8cd73" - integrity sha512-jVamGdJPDeuQilKhvVn1h3knuMOZzr8QDnpk+M9aMlCaMkTDd6fBWPhiDqFvFZ07pL0liqabAiuy8SY4jGHeaw== + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== class-utils@^0.3.5: version "0.3.6" @@ -2842,10 +2712,10 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" - integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== +colorette@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.3.0.tgz#ff45d2f0edb244069d3b772adeb04fed38d0a0af" + integrity sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w== combined-stream@^1.0.8: version "1.0.8" @@ -2864,7 +2734,7 @@ common-tags@1.8.0, common-tags@^1.8.0: resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== -component-emitter@^1.2.1: +component-emitter@^1.2.1, component-emitter@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== @@ -2876,7 +2746,7 @@ compressible@~2.0.16: dependencies: mime-db ">= 1.43.0 < 2" -compression@^1.7.4: +compression@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== @@ -2914,6 +2784,11 @@ configstore@^5.0.1: write-file-atomic "^3.0.0" xdg-basedir "^4.0.0" +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + constant-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1" @@ -2942,6 +2817,14 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +cookie-parser@1.4.5: + version "1.4.5" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.5.tgz#3e572d4b7c0c80f9c61daf604e4336831b5d1d49" + integrity sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw== + dependencies: + cookie "0.4.0" + cookie-signature "1.0.6" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -2952,17 +2835,27 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookiejar@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" + integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== + copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js-pure@^3.10.2: - version "3.15.1" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.15.1.tgz#8356f6576fa2aa8e54ca6fe02620968ff010eed7" - integrity sha512-OZuWHDlYcIda8sJLY4Ec6nWq2hRjlyCqCZ+jCflyleMkVt3tPedDVErvHslyS2nbO+SlBFMSBJYvtLMwxnrzjA== + version "3.16.4" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.16.4.tgz#8b23122628d88c560f209812b9b2d9ebbce5e29c" + integrity sha512-bY1K3/1Jy9D8Jd12eoeVahNXHLfHFb4TXWI8SQ4y8bImR9qDPmGITBAfmcffTkgUvbJn87r8dILOTWW5kZzkgA== + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cors@^2.8.5: +cors@2.8.5, cors@^2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== @@ -2977,7 +2870,7 @@ cosmiconfig-toml-loader@1.0.0: dependencies: "@iarna/toml" "^2.2.5" -cosmiconfig@7.0.0, cosmiconfig@^7.0.0: +cosmiconfig@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== @@ -2988,6 +2881,17 @@ cosmiconfig@7.0.0, cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +cosmiconfig@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -3087,9 +2991,9 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3: ms "2.0.0" debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: ms "2.1.2" @@ -3181,6 +3085,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -3201,6 +3110,11 @@ detect-indent@^6.0.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -3250,7 +3164,7 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" -dotenv@^10.0.0: +dotenv@10.0.0, dotenv@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== @@ -3294,10 +3208,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.723: - version "1.3.759" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.759.tgz#b0d652d376831470a4c230ba721da2427bfb996a" - integrity sha512-nM76xH0t2FBH5iMEZDVc3S/qbdKjGH7TThezxC8k1Q7w7WHvIAyJh8lAe2UamGfdRqBTjHfPDn82LJ0ksCiB9g== +electron-to-chromium@^1.3.811: + version "1.3.822" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.822.tgz#7036edc7f669b0aa79e9801dc5f56866c6ddc0b2" + integrity sha512-k7jG5oYYHxF4jx6PcqwHX3JVME/OjzolqOZiIogi9xtsfsmTjTdie4x88OakYFPEa8euciTgCCzvVNwvmjHb1Q== elegant-spinner@^1.0.1: version "1.0.1" @@ -3339,9 +3253,9 @@ error-ex@^1.3.1: is-arrayish "^0.2.1" es-abstract@^1.18.0-next.2: - version "1.18.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.3.tgz#25c4c3380a27aa203c44b2b685bba94da31b63e0" - integrity sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw== + version "1.18.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19" + integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" @@ -3349,11 +3263,12 @@ es-abstract@^1.18.0-next.2: get-intrinsic "^1.1.1" has "^1.0.3" has-symbols "^1.0.2" + internal-slot "^1.0.3" is-callable "^1.2.3" is-negative-zero "^2.0.1" is-regex "^1.1.3" is-string "^1.0.6" - object-inspect "^1.10.3" + object-inspect "^1.11.0" object-keys "^1.1.1" object.assign "^4.1.2" string.prototype.trimend "^1.0.4" @@ -3552,7 +3467,7 @@ expect@^27.2.0: jest-message-util "^27.2.0" jest-regex-util "^27.0.6" -express@^4.17.1: +express@4.17.1, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== @@ -3589,11 +3504,11 @@ express@^4.17.1: vary "~1.1.2" ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + version "1.5.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.5.0.tgz#e93b97ae0cb23f8370380f6107d2d2b7887687ad" + integrity sha512-+ONcYoWj/SoQwUofMr94aGu05Ou4FepKi7N7b+O8T4jVfyIsZQV1/xeS8jpaBzF0csAk0KLXoHCxU7cKYZjo1Q== dependencies: - type "^2.0.0" + type "^2.5.0" extend-shallow@^2.0.1: version "2.0.1" @@ -3649,16 +3564,15 @@ fast-deep-equal@^3.1.1: integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.1.1: - version "3.2.5" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" + glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" + micromatch "^4.0.4" fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" @@ -3670,10 +3584,15 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-safe-stringify@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz#dc2af48c46cf712b683e849b2bbd446b32de936f" + integrity sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag== + fastq@^1.6.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" - integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== + version "1.12.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.12.0.tgz#ed7b6ab5d62393fb2cc591c853652a5c318bf794" + integrity sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg== dependencies: reusify "^1.0.4" @@ -3769,13 +3688,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -3799,6 +3711,11 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formidable@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" + integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q== + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -3825,6 +3742,13 @@ fs-extra@^7.0.1: jsonfile "^4.0.0" universalify "^0.1.0" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -3840,6 +3764,20 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -3850,7 +3788,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== @@ -3888,7 +3826,7 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= -glob-parent@^5.1.0, glob-parent@~5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -3961,9 +3899,9 @@ got@^9.6.0: url-parse-lax "^3.0.0" graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: - version "4.2.6" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" - integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== graphql-config@^4.0.1: version "4.0.1" @@ -3982,35 +3920,35 @@ graphql-config@^4.0.1: minimatch "3.0.4" string-env-interpolation "1.0.1" -graphql-depth-limit@^1.1.0: +graphql-depth-limit@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/graphql-depth-limit/-/graphql-depth-limit-1.1.0.tgz#59fe6b2acea0ab30ee7344f4c75df39cc18244e8" integrity sha512-+3B2BaG8qQ8E18kzk9yiSdAa75i/hnnOwgSeAxVJctGQPvmeiLtqKOYF6HETCyRjiF7Xfsyal0HbLlxCQkgkrw== dependencies: arrify "^1.0.1" -graphql-import-node@^0.0.4: +graphql-import-node@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/graphql-import-node/-/graphql-import-node-0.0.4.tgz#0522f058978c7e1b99d1e6be1b851ee17007b111" integrity sha512-okpdABQIgIM0qdx9Mfgdu6fTsFEazZmHZdEU34cijkUj9D1db1SyPRGHPxbXmbacamhEF41ckxpCAgHiGliryQ== graphql-request@^3.3.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.4.0.tgz#3a400cd5511eb3c064b1873afb059196bbea9c2b" - integrity sha512-acrTzidSlwAj8wBNO7Q/UQHS8T+z5qRGquCQRv9J1InwR01BBWV9ObnoE+JS5nCCEj8wSGS0yrDXVDoRiKZuOg== + version "3.5.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.5.0.tgz#7e69574e15875fb3f660a4b4be3996ecd0bbc8b7" + integrity sha512-Io89QpfU4rqiMbqM/KwMBzKaDLOppi8FU8sEccCE4JqCgz95W9Q8bvxQ4NfPALLSMvg9nafgg8AkYRmgKSlukA== dependencies: cross-fetch "^3.0.6" extract-files "^9.0.0" form-data "^3.0.0" -graphql-scalars@^1.10.0: +graphql-scalars@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/graphql-scalars/-/graphql-scalars-1.10.0.tgz#9daf9252b16e6fae553a06976163a23f41b65dfd" integrity sha512-LONlj8FfhA2iGpkZJWf5e4PVAHXxnZEHSOEvowLYvNXl/TNnhIck8VmE+lren/aa6GKrG+lZufo5lgnyjxcF6g== dependencies: tslib "~2.2.0" -graphql-tag@^2.11.0, graphql-tag@^2.12.5: +graphql-tag@2.12.5, graphql-tag@^2.11.0: version "2.12.5" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.5.tgz#5cff974a67b417747d05c8d9f5f3cb4495d0db8f" integrity sha512-5xNhP4063d16Pz3HBtKprutsPrmHZi5IdUGOWRxA2B6VF7BIRGOHZ5WQvDmJXZuPcBg7rYwaFxvQYjqkSdR3TQ== @@ -4018,9 +3956,14 @@ graphql-tag@^2.11.0, graphql-tag@^2.12.5: tslib "^2.1.0" graphql-ws@^5.0.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.3.0.tgz#345f73686b639735f1f4ef0b9ea28e17c7f6a745" - integrity sha512-53MbSTOmgx5i6hf3DHVD5PrXix1drDmt2ja8MW7NG+aTpKGzkXVLyNcyNpxme4SK8jVtIV6ZIHkiwirqN0efpw== + version "5.4.1" + resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.4.1.tgz#76fb4c39dfa44a961546995b6bb5320726ff5f71" + integrity sha512-maBduPneZOFYb3otLgc9/U+fU3f9MVXLKg6lVVLqA9BDQQ6YR7YhQO21Rf9+44IRBi4l8m35lynFMFQTzN3eAQ== + +graphql@15.5.1: + version "15.5.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.1.tgz#f2f84415d8985e7b84731e7f3536f8bb9d383aad" + integrity sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw== graphql@^14.5.3: version "14.7.0" @@ -4029,11 +3972,6 @@ graphql@^14.5.3: dependencies: iterall "^1.2.2" -graphql@^15.5.1: - version "15.5.1" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.1.tgz#f2f84415d8985e7b84731e7f3536f8bb9d383aad" - integrity sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw== - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -4061,6 +3999,18 @@ has-symbols@^1.0.1, has-symbols@^1.0.2: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -4264,7 +4214,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4303,6 +4253,15 @@ inquirer@^7.3.3: strip-ansi "^6.0.0" through "^2.3.6" +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -4343,9 +4302,11 @@ is-arrayish@^0.2.1: integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= is-bigint@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" - integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" is-binary-path@~2.1.0: version "2.1.0" @@ -4355,21 +4316,22 @@ is-binary-path@~2.1.0: binary-extensions "^2.0.0" is-boolean-object@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" - integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== dependencies: call-bind "^1.0.2" + has-tostringtag "^1.0.0" is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" - integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== +is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== is-ci@^2.0.0: version "2.0.0" @@ -4386,9 +4348,9 @@ is-ci@^3.0.0: ci-info "^3.1.1" is-core-module@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" - integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== + version "2.6.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19" + integrity sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ== dependencies: has "^1.0.3" @@ -4407,9 +4369,11 @@ is-data-descriptor@^1.0.0: kind-of "^6.0.0" is-date-object@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5" - integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A== + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" is-descriptor@^0.1.0: version "0.1.6" @@ -4501,9 +4465,11 @@ is-npm@^4.0.0: integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== is-number-object@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" - integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" is-number@^3.0.0: version "3.0.0" @@ -4557,12 +4523,12 @@ is-promise@^2.1.0, is-promise@^2.2.2: integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== is-regex@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" - integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: call-bind "^1.0.2" - has-symbols "^1.0.2" + has-tostringtag "^1.0.0" is-relative@^1.0.0: version "1.0.0" @@ -4577,14 +4543,16 @@ is-stream@^1.1.0: integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== is-string@^1.0.5, is-string@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" - integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" @@ -4627,7 +4595,7 @@ is-yarn-global@^0.3.0: resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== -isarray@1.0.0: +isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -4798,14 +4766,14 @@ jest-diff@^24.9.0: pretty-format "^24.9.0" jest-diff@^27.0.0: - version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.0.6.tgz#4a7a19ee6f04ad70e0e3388f35829394a44c7b5e" - integrity sha512-Z1mqgkTCSYaFgwTlP/NUiRzdqgxmmhzHY1Tq17zL94morOHfHu3K4bgSgl+CR4GLhpV8VxkuOYuIWnQ9LnFqmg== + version "27.1.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.1.0.tgz#c7033f25add95e2218f3c7f4c3d7b634ab6b3cd2" + integrity sha512-rjfopEYl58g/SZTsQFmspBODvMSytL16I+cirnScWTLkQVXYVZfxm78DFfdIIXc05RCYuGjxJqrdyG4PIFzcJg== dependencies: chalk "^4.0.0" diff-sequences "^27.0.6" jest-get-type "^27.0.6" - pretty-format "^27.0.6" + pretty-format "^27.1.0" jest-diff@^27.2.0: version "27.2.0" @@ -5142,11 +5110,11 @@ jest-snapshot@^27.2.0: semver "^7.3.2" jest-util@^27.0.0: - version "27.0.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.0.2.tgz#fc2c7ace3c75ae561cf1e5fdb643bf685a5be7c7" - integrity sha512-1d9uH3a00OFGGWSibpNYr+jojZ6AckOMCXV2Z4K3YXDnzpkAaXQyIpY14FOJPiUmil7CD+A6Qs+lnnh6ctRbIA== + version "27.1.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.1.0.tgz#06a53777a8cb7e4940ca8e20bf9c67dd65d9bd68" + integrity sha512-edSLD2OneYDKC6gZM1yc+wY/877s/fuJNoM1k3sOEpzFyeptSmke3SLnk1dDHk9CgTA+58mnfx3ew3J11Kes/w== dependencies: - "@jest/types" "^27.0.2" + "@jest/types" "^27.1.0" "@types/node" "*" chalk "^4.0.0" graceful-fs "^4.2.4" @@ -5208,13 +5176,6 @@ jest@27.2.0: import-local "^3.0.2" jest-cli "^27.2.0" -jose@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/jose/-/jose-2.0.5.tgz#29746a18d9fff7dcf9d5d2a6f62cb0c7cd27abd3" - integrity sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA== - dependencies: - "@panva/asn1.js" "^1.0.0" - js-beautify@^1.8.8: version "1.14.0" resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.0.tgz#2ce790c555d53ce1e3d7363227acf5dc69024c2d" @@ -5246,9 +5207,9 @@ js-yaml@^4.0.0: argparse "^2.0.1" jsdom@^16.6.0: - version "16.6.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.6.0.tgz#f79b3786682065492a3da6a60a4695da983805ac" - integrity sha512-Ty1vmF4NHJkolaEmdjtxTfSfkdb8Ywarwf63f+F8/mDD1uLSSWDxDuMiZxiPhwunLrn9LOSVItWj4bLYsLN3Dg== + version "16.7.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== dependencies: abab "^2.0.5" acorn "^8.2.4" @@ -5275,7 +5236,7 @@ jsdom@^16.6.0: whatwg-encoding "^1.0.5" whatwg-mimetype "^2.3.0" whatwg-url "^8.5.0" - ws "^7.4.5" + ws "^7.4.6" xml-name-validator "^3.0.0" jsesc@^2.5.1: @@ -5362,17 +5323,6 @@ jwa@^1.4.1: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" -jwks-rsa@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/jwks-rsa/-/jwks-rsa-2.0.3.tgz#4059f25e27f1d9cb5681dd12a98e46f8aa39fcbd" - integrity sha512-/rkjXRWAp0cS00tunsHResw68P5iTQru8+jHufLNv3JHc4nObFEndfEUSuPugh09N+V9XYxKUqi7QrkmCHSSSg== - dependencies: - "@types/express-jwt" "0.0.42" - debug "^4.1.0" - jose "^2.0.5" - limiter "^1.1.5" - lru-memoizer "^2.1.2" - jws@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" @@ -5437,11 +5387,6 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -limiter@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" - integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== - lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -5516,11 +5461,6 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= - lodash.get@^4: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -5566,7 +5506,7 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= -lodash@4.17.21, lodash@4.x, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0, lodash@~4.17.0: +lodash@4.17.21, lodash@4.x, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@^4.7.0, lodash@~4.17.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5651,22 +5591,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@~4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e" - integrity sha1-HRdnnAac2l0ECZGgnbwsDbN35V4= - dependencies: - pseudomap "^1.0.1" - yallist "^2.0.0" - -lru-memoizer@^2.1.2: - version "2.1.4" - resolved "https://registry.yarnpkg.com/lru-memoizer/-/lru-memoizer-2.1.4.tgz#b864d92b557f00b1eeb322156a0409cb06dafac6" - integrity sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ== - dependencies: - lodash.clonedeep "^4.5.0" - lru-cache "~4.0.0" - lru-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" @@ -5674,7 +5598,7 @@ lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -5749,7 +5673,7 @@ meros@1.1.4: resolved "https://registry.yarnpkg.com/meros/-/meros-1.1.4.tgz#c17994d3133db8b23807f62bec7f0cb276cfd948" integrity sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ== -methods@~1.1.2: +methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -5773,7 +5697,7 @@ micromatch@^3.1.10: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== @@ -5781,23 +5705,28 @@ micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" -mime-db@1.48.0, "mime-db@>= 1.43.0 < 2": - version "1.48.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" - integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== +mime-db@1.49.0, "mime-db@>= 1.43.0 < 2": + version "1.49.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" + integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== mime-types@^2.1.12, mime-types@~2.1.24: - version "2.1.31" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" - integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== + version "2.1.32" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" + integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== dependencies: - mime-db "1.48.0" + mime-db "1.49.0" mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^2.4.6: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -5825,6 +5754,21 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minipass@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -5833,7 +5777,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^1.0.4: +mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -5941,6 +5885,11 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +node-addon-api@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" + integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== + node-fetch@2.6.1, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" @@ -5956,12 +5905,12 @@ node-modules-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-releases@^1.1.71: - version "1.1.73" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" - integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg== +node-releases@^1.1.75: + version "1.1.75" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.75.tgz#6dd8c876b9897a1b8e5a02de26afa79bb54ebbfe" + integrity sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw== -nodemon@^2.0.7: +nodemon@2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.7.tgz#6f030a0a0ebe3ea1ba2a38f71bf9bab4841ced32" integrity sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA== @@ -6040,6 +5989,16 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npmlog@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + nullthrows@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" @@ -6069,10 +6028,10 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.10.3: - version "1.10.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" - integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" @@ -6096,15 +6055,6 @@ object.assign@^4.1.0, object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" -object.getownpropertydescriptors@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz#1bd63aeacf0d5d2d2f31b5e393b03a7c601a23f7" - integrity sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" @@ -6290,6 +6240,26 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= +passport-cookie@1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/passport-cookie/-/passport-cookie-1.0.9.tgz#a21dca7ce6cc95fb929083a171a9f894024dcecb" + integrity sha512-8a6foX2bbGoJzup0RAiNcC2tTqzYS46RQEK3Z4u8p86wesPUjgDaji3C7+5j4TGyCq4ZoOV+3YLw1Hy6cV6kyw== + dependencies: + passport-strategy "^1.0.0" + +passport-strategy@1.x.x, passport-strategy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= + +passport@0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" + integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + path-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f" @@ -6357,6 +6327,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10= + pg-connection-string@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" @@ -6368,9 +6343,9 @@ pg-int8@1.0.1: integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== pg-pool@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.3.0.tgz#12d5c7f65ea18a6e99ca9811bd18129071e562fc" - integrity sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg== + version "3.4.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.4.1.tgz#0e71ce2c67b442a5e862a9c182172c37eda71e9c" + integrity sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ== pg-protocol@^1.5.0: version "1.5.0" @@ -6388,7 +6363,7 @@ pg-types@^2.1.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" -pg@^8.6.0: +pg@8.6.0: version "8.6.0" resolved "https://registry.yarnpkg.com/pg/-/pg-8.6.0.tgz#e222296b0b079b280cce106ea991703335487db2" integrity sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ== @@ -6474,7 +6449,7 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier-plugin-organize-imports@^2.2.0: +prettier-plugin-organize-imports@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.2.0.tgz#dcdd5222416a90bd819734f131fea4a22b1c613a" integrity sha512-2WM3moc/cAPCCsSneYhaL4+mMws0Bypbxz+98wuRyaA7GMokhOECVkQCG7l2hcH+9/4d5NsgZs9yktfwDy4r7A== @@ -6502,12 +6477,12 @@ pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" -pretty-format@^27.0.0, pretty-format@^27.0.6: - version "27.0.6" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.0.6.tgz#ab770c47b2c6f893a21aefc57b75da63ef49a11f" - integrity sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ== +pretty-format@^27.0.0: + version "27.1.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.1.0.tgz#022f3fdb19121e0a2612f3cff8d724431461b9ca" + integrity sha512-4aGaud3w3rxAO6OXmK3fwBFQ0bctIOG3/if+jYEFGNGIs0EvuidQm3bZ9mlP2/t9epLNC/12czabfy7TZNSwVA== dependencies: - "@jest/types" "^27.0.6" + "@jest/types" "^27.1.0" ansi-regex "^5.0.0" ansi-styles "^5.0.0" react-is "^17.0.1" @@ -6534,6 +6509,11 @@ pretty-quick@3.1.1: mri "^1.1.5" multimatch "^4.0.0" +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -6562,7 +6542,7 @@ proxy-addr@~2.0.5: forwarded "0.2.0" ipaddr.js "1.9.1" -pseudomap@^1.0.1, pseudomap@^1.0.2: +pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= @@ -6602,6 +6582,13 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@^6.9.4: + version "6.10.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" + integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== + dependencies: + side-channel "^1.0.4" + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -6651,7 +6638,20 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -readable-stream@^3.0.0: +readable-stream@^2.0.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -6667,15 +6667,15 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -reflect-metadata@^0.1.13: +reflect-metadata@0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== regenerator-runtime@^0.13.4: - version "0.13.7" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" - integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" @@ -6841,10 +6841,10 @@ retry-as-promised@^3.2.0: dependencies: any-promise "^1.3.0" -retry@0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= +retry@0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== reusify@^1.0.4: version "1.0.4" @@ -6858,7 +6858,7 @@ rimraf@^2.6.1: dependencies: glob "^7.1.3" -rimraf@^3.0.0: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -6884,7 +6884,7 @@ rxjs@^6.3.3, rxjs@^6.6.0: dependencies: tslib "^1.9.0" -safe-buffer@5.1.2, safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -6930,7 +6930,7 @@ semver-diff@^3.1.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.x, semver@^7.3.2: +semver@7.x, semver@^7.3.2, semver@^7.3.4: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -6988,14 +6988,14 @@ sequelize-pool@^6.0.0: resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-6.1.0.tgz#caaa0c1e324d3c2c3a399fed2c7998970925d668" integrity sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg== -sequelize-typescript@^2.1.0: +sequelize-typescript@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/sequelize-typescript/-/sequelize-typescript-2.1.0.tgz#7d42dac368f32829a736acc4f0c9f3b79fc089bb" integrity sha512-wwPxydBQ/wIZ92pFxDQEAhW8uRHqwFZGm6JkPmpsCjrODWrH8TANZiOCjwGouygFMgBwCNK91RNwLe5TYoy5pg== dependencies: glob "7.1.6" -sequelize@^6.6.2: +sequelize@6.6.2: version "6.6.2" resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-6.6.2.tgz#3681b0a4aeb106e31079d3a537d88542051dab2e" integrity sha512-H/zrzmTK+tis9PJaSigkuXI57nKBvNCtPQol0yxCvau1iWLzSOuq8t3tMOVeQ+Ep8QH2HoD9/+FCCIAqzUr/BQ== @@ -7024,7 +7024,7 @@ serve-static@1.14.1: parseurl "~1.3.3" send "0.17.1" -set-blocking@^2.0.0: +set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -7086,12 +7086,21 @@ shell-quote@^1.6.1: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + sigmund@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= -signal-exit@^3.0.2, signal-exit@^3.0.3: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -7220,9 +7229,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz#8a595135def9592bda69709474f1cbeea7c2467f" - integrity sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ== + version "3.0.10" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b" + integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -7304,7 +7313,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -7362,6 +7371,13 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -7421,6 +7437,31 @@ subscriptions-transport-ws@^0.10.0: symbol-observable "^1.0.4" ws "^5.2.0 || ^6.0.0 || ^7.0.0" +superagent@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-6.1.0.tgz#09f08807bc41108ef164cfb4be293cebd480f4a6" + integrity sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg== + dependencies: + component-emitter "^1.3.0" + cookiejar "^2.1.2" + debug "^4.1.1" + fast-safe-stringify "^2.0.7" + form-data "^3.0.0" + formidable "^1.2.2" + methods "^1.1.2" + mime "^2.4.6" + qs "^6.9.4" + readable-stream "^3.6.0" + semver "^7.3.2" + +supertest@6.1.6: + version "6.1.6" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-6.1.6.tgz#6151c518f4c5ced2ac2aadb9f96f1bf8198174c8" + integrity sha512-0hACYGNJ8OHRg8CRITeZOdbjur7NLuNs0mBjVhdpxi7hP6t3QIbOzLON5RTUmZcy2I9riuII3+Pr2C7yztrIIg== + dependencies: + methods "^1.1.2" + superagent "^6.1.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -7480,6 +7521,18 @@ sync-fetch@0.3.0: buffer "^5.7.0" node-fetch "^2.6.1" +tar@^6.1.0: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + term-size@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" @@ -7619,13 +7672,6 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -ts-is-defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/ts-is-defined/-/ts-is-defined-1.0.0.tgz#8f106af8c43ba6200beaac0a8013dc3745451ae7" - integrity sha512-HmzqN8xWETXnfpXyUqMf5nvcZszn9aTNjxVIJ6R2aNNg14oLo3PCi9IRhsv+vg2C7TI90M7PyjBOrg4f6/nupA== - dependencies: - ts-tiny-invariant "0.0.3" - ts-jest@27.0.5: version "27.0.5" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.0.5.tgz#0b0604e2271167ec43c12a69770f0bb65ad1b750" @@ -7691,11 +7737,6 @@ ts-node@^9, ts-node@^9.0.0: source-map-support "^0.5.17" yn "3.1.1" -ts-tiny-invariant@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/ts-tiny-invariant/-/ts-tiny-invariant-0.0.3.tgz#6f51c153c285a7839bb59b1e5a2721fcb25a5e39" - integrity sha512-EiaBUsUta7PPzVKpvZurcSDgaSkymxwiUc2rhX6Wu30bws2maipT6ihbEY072dU9lz6/FoFWEc6psXdlo0xqtg== - tsconfig@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" @@ -7712,9 +7753,9 @@ tslib@^1.9.0: integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2, tslib@^2.0.3, tslib@^2.1.0, tslib@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" - integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== tslib@~2.0.1: version "2.0.3" @@ -7761,7 +7802,7 @@ type@^1.0.1: resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== -type@^2.0.0: +type@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== @@ -7911,28 +7952,17 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -util-deprecate@^1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.1.1.tgz#77832f57ced2c9478174149cae9b96e9918cd54b" - integrity sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - for-each "^0.3.3" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.1" - utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^8.0.0, uuid@^8.1.0: +uuid@8.3.2, uuid@^8.0.0, uuid@^8.1.0: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -7969,11 +7999,6 @@ value-or-promise@1.0.10: resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.10.tgz#5bf041f1e9a8e7043911875547636768a836e446" integrity sha512-1OwTzvcfXkAfabk60UVr5NdjtjJ0Fg0T5+B1bhxtrOEwSH2fe8y4DnLgoksfCyd8yZCOQQHB0qLMQnwgCjbXLQ== -value-or-promise@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.6.tgz#218aa4794aa2ee24dcf48a29aba4413ed584747f" - integrity sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg== - vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -8066,6 +8091,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + widest-line@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" @@ -8135,20 +8167,15 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.0.0.tgz#550605d13dfc1437c9ec1396975709c6d7ffc57d" - integrity sha512-6AcSIXpBlS0QvCVKk+3cWnWElLsA6SzC0lkQ43ciEglgXJXiCWK3/CGFEJ+Ybgp006CMibamAsqOlxE9s4AvYA== - ws@8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.0.tgz#0b738cd484bfc9303421914b11bb4011e07615bb" integrity sha512-uYhVJ/m9oXwEI04iIVmgLmugh2qrZihkywG9y5FfZV2ATeLIzHf93qs+tUNqlttbQK957/VX3mtwAS+UfIwA4g== -"ws@^5.2.0 || ^6.0.0 || ^7.0.0", ws@^7.4.5: - version "7.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.0.tgz#0033bafea031fb9df041b2026fc72a571ca44691" - integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw== +"ws@^5.2.0 || ^6.0.0 || ^7.0.0", ws@^7.4.6: + version "7.5.4" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.4.tgz#56bfa20b167427e138a7795de68d134fe92e21f9" + integrity sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg== xdg-basedir@^4.0.0: version "4.0.0" @@ -8188,7 +8215,7 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^2.0.0, yallist@^2.1.2: +yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= @@ -8276,9 +8303,9 @@ yargs@^16.0.3: yargs-parser "^20.2.2" yargs@^17.0.0: - version "17.0.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.0.1.tgz#6a1ced4ed5ee0b388010ba9fd67af83b9362e0bb" - integrity sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ== + version "17.1.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.1.1.tgz#c2a8091564bdb196f7c0a67c1d12e5b85b8067ba" + integrity sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ== dependencies: cliui "^7.0.2" escalade "^3.1.1" From d1f8aec42b5a55f058b35219d8124be366f8200e Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 26 Aug 2021 00:37:56 +0200 Subject: [PATCH 02/65] feat: update auth context and implement cookie renewal --- db/migrations/.gitkeep | 0 docs/authentication.md | 21 ++++ schema.graphql | 4 +- src/apolloServer.ts | 19 ++-- src/authenticateRequest.ts | 103 +++++++++++------- src/getProfile.ts | 37 ------- .../input-validation/Contact.ts | 0 .../input-validation/Location.ts | 0 .../input-validation/country-codes.ts | 0 .../input-validation/currency-codes.ts | 0 .../errorsToProblemDetails.ts | 0 .../input-validation/idInputSchema.ts | 0 src/input-validation/trimAll.ts | 8 ++ src/{resolvers => }/input-validation/types.ts | 0 .../validateWithJSONSchema.ts | 4 +- src/login.ts | 35 ------ src/models/shipment_export.ts | 4 +- src/models/user_account.ts | 31 +++++- src/registerUser.ts | 23 ---- src/resolvers/group.ts | 33 +++--- src/resolvers/line_items.ts | 18 +-- src/resolvers/offer.ts | 16 +-- src/resolvers/offer_authorization.ts | 4 +- src/resolvers/pallet.ts | 6 +- src/resolvers/shipment.ts | 20 ++-- src/resolvers/shipment_exports.ts | 17 ++- src/routes/login.ts | 54 +++++++++ src/routes/me.ts | 17 +++ src/routes/me/cookie.ts | 28 +++++ src/routes/register.ts | 56 ++++++++++ src/server.ts | 16 ++- src/testServer.ts | 26 +++-- ...unt_api.test.ts => authentication.test.ts} | 64 ++++++++--- src/tests/groups_api.test.ts | 20 ++-- src/tests/helpers/index.ts | 2 +- .../errorsToProblemDetails.test.ts | 9 +- src/tests/input-validation/types.test.ts | 4 +- .../validateWithJSONSchema.test.ts | 2 +- src/tests/line_items_api.test.ts | 8 +- src/tests/offers_api.test.ts | 8 +- src/tests/pallets_api.test.ts | 8 +- src/tests/shipment_exports_api.test.ts | 4 +- src/tests/trimAll.test.ts | 18 +++ 43 files changed, 474 insertions(+), 273 deletions(-) create mode 100644 db/migrations/.gitkeep create mode 100644 docs/authentication.md delete mode 100644 src/getProfile.ts rename src/{resolvers => }/input-validation/Contact.ts (100%) rename src/{resolvers => }/input-validation/Location.ts (100%) rename src/{resolvers => }/input-validation/country-codes.ts (100%) rename src/{resolvers => }/input-validation/currency-codes.ts (100%) rename src/{resolvers => }/input-validation/errorsToProblemDetails.ts (100%) rename src/{resolvers => }/input-validation/idInputSchema.ts (100%) create mode 100644 src/input-validation/trimAll.ts rename src/{resolvers => }/input-validation/types.ts (100%) rename src/{resolvers => }/input-validation/validateWithJSONSchema.ts (95%) delete mode 100644 src/login.ts delete mode 100644 src/registerUser.ts create mode 100644 src/routes/login.ts create mode 100644 src/routes/me.ts create mode 100644 src/routes/me/cookie.ts create mode 100644 src/routes/register.ts rename src/tests/{user_account_api.test.ts => authentication.test.ts} (58%) create mode 100644 src/tests/trimAll.test.ts diff --git a/db/migrations/.gitkeep b/db/migrations/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/authentication.md b/docs/authentication.md new file mode 100644 index 000000000..ea55b315f --- /dev/null +++ b/docs/authentication.md @@ -0,0 +1,21 @@ +# Authentication + +The backend authenticates requests using signed cookies so they can contain user information so that it does not have to be fetched for every request. + +The cookie contains the user's id. + +Cookies are sent [`secure` and `HttpOnly`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) when users register their account, or when they login using username and password. + +Cookies expire after 30 minutes and the client is responsible to renew cookies by calling the `GET /me/cookie` endpoint before they expire. + +When renewing cookies the server will re-check if the user still exists and if they haven't changed their password. For this a hash of the user's password hash, email, username, and id will be generated and included in the cookie. If any of these properties changes, the cookie cannot be renewed and the user has to log-in again. + +## Admin permissions + +Admin permission are granted via the `isAdmin` flag on the `UserAccount`. + +## Configuration + +These environment variables control the authentication: + +- `COOKIE_SECRET`: sets the secret used to sign cookies, default value is a random string diff --git a/schema.graphql b/schema.graphql index bfce79d44..581a0f233 100644 --- a/schema.graphql +++ b/schema.graphql @@ -197,8 +197,8 @@ enum GroupType { type UserProfile { id: Int! isAdmin: Boolean! - groupId: Int - username: String! + name: String! + group: Group } enum OfferStatus { diff --git a/src/apolloServer.ts b/src/apolloServer.ts index ada12009d..cd819f077 100644 --- a/src/apolloServer.ts +++ b/src/apolloServer.ts @@ -4,7 +4,11 @@ import { AuthenticationError, } from 'apollo-server-express' import depthLimit from 'graphql-depth-limit' -import { AuthContext, authenticateWithToken } from './authenticateRequest' +import { + AuthContext, + authCookieName, + decodeAuthCookie, +} from './authenticateRequest' import generateCsv, { CsvRow } from './generateCsv' import resolvers from './resolvers' import typeDefs from './typeDefs' @@ -23,13 +27,14 @@ export const serverConfig: ApolloServerExpressConfig = { resolvers, validationRules: [depthLimit(7)], async context({ req }): Promise { - const auth = await authenticateWithToken(req.signedCookies.token) - - if (!('userAccount' in auth)) { - throw new AuthenticationError(auth.message) + try { + return { + auth: decodeAuthCookie(req.signedCookies[authCookieName]), + services: { generateCsv }, + } + } catch (err) { + throw new AuthenticationError((err as Error).message) } - - return { auth: auth as AuthContext, services: { generateCsv } } }, } diff --git a/src/authenticateRequest.ts b/src/authenticateRequest.ts index 81f3538ca..1f8670915 100644 --- a/src/authenticateRequest.ts +++ b/src/authenticateRequest.ts @@ -1,54 +1,79 @@ +import * as crypto from 'crypto' +import { CookieOptions } from 'express' import { Strategy as CookieStrategy } from 'passport-cookie' import UserAccount from './models/user_account' -const fakeAccount = UserAccount.build({ - username: '', - token: '', - passwordHash: '', -}) +type AuthCookiePayload = { + /** user ID */ + i: number + /** user is admin */ + a: boolean + /** user hash */ + c: string +} export type AuthContext = { - userAccount: UserAccount + userId: number isAdmin: boolean + userHash: string } -export type ErrorInfo = { - message: string -} - -export const fakeAdminAuth: AuthContext = { - userAccount: fakeAccount, - isAdmin: true, -} - -export const fakeUserAuth: AuthContext = { - userAccount: fakeAccount, - isAdmin: false, -} - -export const authenticateWithToken = async ( - token: string, -): Promise => { - try { - const userAccount = await UserAccount.findOne({ - where: { token }, - }) - if (userAccount === null) return { message: 'User not found for token.' } - return { userAccount, isAdmin: false } - } catch (err) { - return { message: (err as Error).message } - } -} +export const userHash = (user: UserAccount): string => + crypto + .createHash('sha1') + .update(`${user.id}:${user.username}:${user.passwordHash}`) + .digest('hex') -export const authTokenCookieName = 'token' +export const authCookieName = 'auth' export const cookieAuthStrategy = new CookieStrategy( { - cookieName: authTokenCookieName, + cookieName: authCookieName, signed: true, }, - async (token: string, done: any) => { - const res = await authenticateWithToken(token) - if ('userAccount' in res) return done(null, res) - return done(null, false, res) + async (value: string, done: any) => { + try { + return done(null, decodeAuthCookie(value)) + } catch (error) { + return done( + null, + false, + new Error( + `Failed to decode cookie payload: ${(error as Error).message}!`, + ), + ) + } }, ) + +export const authCookie = ( + user: UserAccount, + lifetimeInMinutes: number = 30, +): [string, string, CookieOptions] => [ + authCookieName, + JSON.stringify({ + i: user.id, + a: false, + c: userHash(user), + }), + { + signed: true, + secure: true, + httpOnly: true, + expires: new Date(Date.now() + lifetimeInMinutes * 60 * 1000), + }, +] + +export const userToAuthContext = (user: UserAccount): AuthContext => ({ + isAdmin: user.isAdmin, + userId: user.id, + userHash: userHash(user), +}) + +export const decodeAuthCookie = (value: string): AuthContext => { + const { + i: userId, + a: isAdmin, + c: userHash, + } = JSON.parse(value) as AuthCookiePayload + return { userId, isAdmin, userHash } +} diff --git a/src/getProfile.ts b/src/getProfile.ts deleted file mode 100644 index e99ea7760..000000000 --- a/src/getProfile.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Maybe } from '@graphql-tools/utils' -import { Request, Response } from 'express' -import { AuthContext } from './authenticateRequest' -import Group from './models/group' -import UserAccount from './models/user_account' - -const sendProfile = ( - response: Response, - userAccount: UserAccount, - isAdmin = false, - groupId?: number, -) => response.json(userAccount.asProfile(groupId, isAdmin)).end() - -const getProfile = async (request: Request, response: Response) => { - const authContext = request.user as AuthContext - let groupForUser: Maybe - if (!authContext.isAdmin) { - // Note: this assumes that there is only 1 captain per group, where in - // reality there are no restrictions on the number of groups with the same - // captain. For now, we've agreed that this is okay, but we probably need - // to solidify some restrictions later on. - // See https://github.com/distributeaid/shipment-tracker/issues/133 - groupForUser = await Group.findOne({ - where: { captainId: authContext.userAccount.id }, - attributes: ['id'], - }) - } - - sendProfile( - response, - authContext.userAccount, - authContext.isAdmin, - groupForUser?.id, - ) -} - -export default getProfile diff --git a/src/resolvers/input-validation/Contact.ts b/src/input-validation/Contact.ts similarity index 100% rename from src/resolvers/input-validation/Contact.ts rename to src/input-validation/Contact.ts diff --git a/src/resolvers/input-validation/Location.ts b/src/input-validation/Location.ts similarity index 100% rename from src/resolvers/input-validation/Location.ts rename to src/input-validation/Location.ts diff --git a/src/resolvers/input-validation/country-codes.ts b/src/input-validation/country-codes.ts similarity index 100% rename from src/resolvers/input-validation/country-codes.ts rename to src/input-validation/country-codes.ts diff --git a/src/resolvers/input-validation/currency-codes.ts b/src/input-validation/currency-codes.ts similarity index 100% rename from src/resolvers/input-validation/currency-codes.ts rename to src/input-validation/currency-codes.ts diff --git a/src/resolvers/input-validation/errorsToProblemDetails.ts b/src/input-validation/errorsToProblemDetails.ts similarity index 100% rename from src/resolvers/input-validation/errorsToProblemDetails.ts rename to src/input-validation/errorsToProblemDetails.ts diff --git a/src/resolvers/input-validation/idInputSchema.ts b/src/input-validation/idInputSchema.ts similarity index 100% rename from src/resolvers/input-validation/idInputSchema.ts rename to src/input-validation/idInputSchema.ts diff --git a/src/input-validation/trimAll.ts b/src/input-validation/trimAll.ts new file mode 100644 index 000000000..7912901c0 --- /dev/null +++ b/src/input-validation/trimAll.ts @@ -0,0 +1,8 @@ +export const trimAll = (o: Record): Record => + Object.entries(o).reduce( + (r, [k, v]) => ({ + ...r, + [k]: v.trim(), + }), + {}, + ) diff --git a/src/resolvers/input-validation/types.ts b/src/input-validation/types.ts similarity index 100% rename from src/resolvers/input-validation/types.ts rename to src/input-validation/types.ts diff --git a/src/resolvers/input-validation/validateWithJSONSchema.ts b/src/input-validation/validateWithJSONSchema.ts similarity index 95% rename from src/resolvers/input-validation/validateWithJSONSchema.ts rename to src/input-validation/validateWithJSONSchema.ts index edb1a6d04..4e8d091c9 100644 --- a/src/resolvers/input-validation/validateWithJSONSchema.ts +++ b/src/input-validation/validateWithJSONSchema.ts @@ -16,9 +16,7 @@ ajv.addKeyword('modifier') export const validateWithJSONSchema = >( schema: T, -): (( - value: Record, -) => +): ((value: Record) => | { errors: ErrorObject[] } diff --git a/src/login.ts b/src/login.ts deleted file mode 100644 index f5af5b364..000000000 --- a/src/login.ts +++ /dev/null @@ -1,35 +0,0 @@ -import bcrypt from 'bcrypt' -import { Request, Response } from 'express' -import { v4 } from 'uuid' -import { authTokenCookieName } from './authenticateRequest' -import UserAccount from './models/user_account' - -// FIXME: Add input validation -const login = - (penaltySeconds = 10) => - async (request: Request, response: Response) => { - const user = await UserAccount.findOne({ - where: { - username: request.body.username, - }, - }) - if (user === null) { - // Penalize - await new Promise((resolve) => setTimeout(resolve, penaltySeconds * 1000)) - return response.status(401).end() - } - if (!bcrypt.compareSync(request.body.password, user.passwordHash)) { - // Penalize - await new Promise((resolve) => setTimeout(resolve, penaltySeconds * 1000)) - return response.status(401).end() - } - // Generate new token - const token = v4() - await user.update({ token }) - response - .status(204) - .cookie(authTokenCookieName, token, { signed: true }) - .end() - } - -export default login diff --git a/src/models/shipment_export.ts b/src/models/shipment_export.ts index cfc6cb1bf..5f7c86954 100644 --- a/src/models/shipment_export.ts +++ b/src/models/shipment_export.ts @@ -55,8 +55,8 @@ export default class ShipmentExport extends Model { downloadPath: `/shipment-exports/${this.id}`, createdBy: { id: this.userAccountId, - isAdmin: true, - username: this.userAccount.username, + isAdmin: this.userAccount.isAdmin, + name: this.userAccount.name, }, createdAt: this.createdAt, } diff --git a/src/models/user_account.ts b/src/models/user_account.ts index 88feddfcb..c597583c0 100644 --- a/src/models/user_account.ts +++ b/src/models/user_account.ts @@ -1,6 +1,8 @@ +import { Maybe } from 'graphql/jsutils/Maybe' import { Column, CreatedAt, + Default, Model, Table, Unique, @@ -8,12 +10,14 @@ import { } from 'sequelize-typescript' import { Optional } from 'sequelize/types' import { UserProfile } from '../server-internal-types' +import Group from './group' export interface UserAccountAttributes { id: number username: string passwordHash: string - token: string + isAdmin?: boolean + name: string } export interface UserAccountCreationAttributes @@ -36,7 +40,11 @@ export default class UserAccount extends Model< public passwordHash!: string @Column - public token!: string + public name!: string + + @Default(false) + @Column + public isAdmin!: boolean @CreatedAt @Column @@ -46,12 +54,23 @@ export default class UserAccount extends Model< @Column public readonly updatedAt!: Date - public asProfile(groupId?: number, isAdmin = false): UserProfile { + public async asPublicProfile(): Promise { + let groupForUser: Maybe + if (!this.isAdmin) { + // Note: this assumes that there is only 1 captain per group, where in + // reality there are no restrictions on the number of groups with the same + // captain. For now, we've agreed that this is okay, but we probably need + // to solidify some restrictions later on. + // See https://github.com/distributeaid/shipment-tracker/issues/133 + groupForUser = await Group.findOne({ + where: { captainId: this.id }, + }) + } return { id: this.id, - username: this.username, - isAdmin, - groupId, + isAdmin: this.isAdmin, + name: this.name, + group: groupForUser, } } } diff --git a/src/registerUser.ts b/src/registerUser.ts deleted file mode 100644 index a4ad72f6c..000000000 --- a/src/registerUser.ts +++ /dev/null @@ -1,23 +0,0 @@ -import bcrypt from 'bcrypt' -import { Request, Response } from 'express' -import { v4 } from 'uuid' -import { authTokenCookieName } from './authenticateRequest' -import UserAccount from './models/user_account' - -// FIXME: Add input validation -const registerUser = - (saltRounds = 10) => - async (request: Request, response: Response) => { - const token = v4() - await UserAccount.create({ - passwordHash: bcrypt.hashSync(request.body.password, saltRounds), - token, - username: request.body.username, - }) - response - .status(202) - .cookie(authTokenCookieName, token, { signed: true }) - .end() - } - -export default registerUser diff --git a/src/resolvers/group.ts b/src/resolvers/group.ts index b0ccb7f61..c4f7b7362 100644 --- a/src/resolvers/group.ts +++ b/src/resolvers/group.ts @@ -2,6 +2,16 @@ import { Type } from '@sinclair/typebox' import { ApolloError, ForbiddenError, UserInputError } from 'apollo-server' import { strict as assert } from 'assert' import { FindOptions, Op } from 'sequelize' +import { Contact } from '../input-validation/Contact' +import { validateIdInput } from '../input-validation/idInputSchema' +import { Location } from '../input-validation/Location' +import { + ID, + NonEmptyShortString, + OptionalValueOrUnset, + URI, +} from '../input-validation/types' +import { validateWithJSONSchema } from '../input-validation/validateWithJSONSchema' import Group, { GroupAttributes } from '../models/group' import UserAccount from '../models/user_account' import { @@ -9,16 +19,6 @@ import { MutationResolvers, QueryResolvers, } from '../server-internal-types' -import { Contact } from './input-validation/Contact' -import { validateIdInput } from './input-validation/idInputSchema' -import { Location } from './input-validation/Location' -import { - ID, - NonEmptyShortString, - OptionalValueOrUnset, - URI, -} from './input-validation/types' -import { validateWithJSONSchema } from './input-validation/validateWithJSONSchema' // Group query resolvers @@ -95,7 +95,7 @@ const addGroup: MutationResolvers['addGroup'] = async ( context, ) => { assert.ok( - typeof context.auth.userAccount.id === 'number', + typeof context.auth.userId === 'number', 'Current user id should be set', ) const valid = validateAddGroupInput(input) @@ -114,7 +114,7 @@ const addGroup: MutationResolvers['addGroup'] = async ( // Non-admins are only allowed to create a single group if (!context.auth.isAdmin) { const numGroupsForUser = await Group.count({ - where: { captainId: context.auth.userAccount.id }, + where: { captainId: context.auth.userId }, }) if (numGroupsForUser > 0) { @@ -124,7 +124,7 @@ const addGroup: MutationResolvers['addGroup'] = async ( return Group.create({ ...valid.value, - captainId: context.auth.userAccount.id, + captainId: context.auth.userId, }) } @@ -150,7 +150,7 @@ const updateGroup: MutationResolvers['updateGroup'] = async ( context, ) => { assert.ok( - typeof context.auth.userAccount.id === 'number', + typeof context.auth.userId === 'number', 'Current user id should be set', ) const valid = validateUpdateGroupInput(input) @@ -164,10 +164,7 @@ const updateGroup: MutationResolvers['updateGroup'] = async ( throw new UserInputError(`Group ${id} does not exist`) } - if ( - group.captainId !== context.auth.userAccount.id && - !context.auth.isAdmin - ) { + if (group.captainId !== context.auth.userId && !context.auth.isAdmin) { throw new ForbiddenError('Not permitted to update group') } diff --git a/src/resolvers/line_items.ts b/src/resolvers/line_items.ts index 527c91da6..23e5bb927 100644 --- a/src/resolvers/line_items.ts +++ b/src/resolvers/line_items.ts @@ -2,6 +2,15 @@ import { Static, Type } from '@sinclair/typebox' import { ApolloError, UserInputError } from 'apollo-server' import { isEqual } from 'lodash' import { AuthenticatedContext } from '../apolloServer' +import { validateIdInput } from '../input-validation/idInputSchema' +import { + DateTime, + ID, + NonEmptyShortString, + PositiveInteger, + URI, +} from '../input-validation/types' +import { validateWithJSONSchema } from '../input-validation/validateWithJSONSchema' import Group from '../models/group' import LineItem, { LineItemAttributes } from '../models/line_item' import Offer from '../models/offer' @@ -17,15 +26,6 @@ import { QueryResolvers, } from '../server-internal-types' import getPalletWithParentAssociations from './getPalletWithParentAssociations' -import { validateIdInput } from './input-validation/idInputSchema' -import { - DateTime, - ID, - NonEmptyShortString, - PositiveInteger, - URI, -} from './input-validation/types' -import { validateWithJSONSchema } from './input-validation/validateWithJSONSchema' import { authorizeOfferMutation, authorizeOfferQuery, diff --git a/src/resolvers/offer.ts b/src/resolvers/offer.ts index 2db584a34..e525343e0 100644 --- a/src/resolvers/offer.ts +++ b/src/resolvers/offer.ts @@ -1,6 +1,10 @@ import { Type } from '@sinclair/typebox' import { ForbiddenError, UserInputError } from 'apollo-server' import { strict as assert } from 'assert' +import { Contact } from '../input-validation/Contact' +import { validateIdInput } from '../input-validation/idInputSchema' +import { ID, URI } from '../input-validation/types' +import { validateWithJSONSchema } from '../input-validation/validateWithJSONSchema' import Group from '../models/group' import Offer, { OfferAttributes } from '../models/offer' import Pallet from '../models/pallet' @@ -12,10 +16,6 @@ import { QueryResolvers, ShipmentStatus, } from '../server-internal-types' -import { Contact } from './input-validation/Contact' -import { validateIdInput } from './input-validation/idInputSchema' -import { ID, URI } from './input-validation/types' -import { validateWithJSONSchema } from './input-validation/validateWithJSONSchema' import { authorizeOfferMutation, authorizeOfferQuery, @@ -43,7 +43,7 @@ const addOffer: MutationResolvers['addOffer'] = async ( context, ) => { assert.ok( - typeof context.auth.userAccount.id === 'number', + typeof context.auth.userId === 'number', 'Current user id should be set', ) const valid = validateAddOfferInput(input) @@ -62,11 +62,11 @@ const addOffer: MutationResolvers['addOffer'] = async ( const sendingGroup = await sendingGroupPromise if ( - sendingGroup?.captainId !== context.auth.userAccount.id && + sendingGroup?.captainId !== context.auth.userId && !context.auth.isAdmin ) { throw new ForbiddenError( - `User ${context.auth.userAccount.id} not permitted to create offer for group ${valid.value.sendingGroupId}`, + `User ${context.auth.userId} not permitted to create offer for group ${valid.value.sendingGroupId}`, ) } @@ -188,7 +188,7 @@ const listOffers: QueryResolvers['listOffers'] = async ( } const groupsPromise = Group.findAll({ - where: { captainId: context.auth.userAccount.id }, + where: { captainId: context.auth.userId }, }) const shipment = await Shipment.findByPk(shipmentId) diff --git a/src/resolvers/offer_authorization.ts b/src/resolvers/offer_authorization.ts index a56385745..2bd2fecbe 100644 --- a/src/resolvers/offer_authorization.ts +++ b/src/resolvers/offer_authorization.ts @@ -33,11 +33,11 @@ const assertAccountIsCaptainOrAdmin = ( context: AuthenticatedContext, ): void => { assert.ok( - typeof context.auth.userAccount.id === 'number', + typeof context.auth.userId === 'number', 'Current user id should be set', ) if ( - offer.sendingGroup.captainId !== context.auth.userAccount.id && + offer.sendingGroup.captainId !== context.auth.userId && !context.auth.isAdmin ) { throw new ForbiddenError('Forbidden to access this offer') diff --git a/src/resolvers/pallet.ts b/src/resolvers/pallet.ts index bce46a20f..cebbc77b2 100644 --- a/src/resolvers/pallet.ts +++ b/src/resolvers/pallet.ts @@ -1,5 +1,8 @@ import { Type } from '@sinclair/typebox' import { ApolloError, UserInputError } from 'apollo-server' +import { validateIdInput } from '../input-validation/idInputSchema' +import { ID } from '../input-validation/types' +import { validateWithJSONSchema } from '../input-validation/validateWithJSONSchema' import LineItem from '../models/line_item' import Offer from '../models/offer' import Pallet, { PalletAttributes } from '../models/pallet' @@ -11,9 +14,6 @@ import { QueryResolvers, } from '../server-internal-types' import getPalletWithParentAssociations from './getPalletWithParentAssociations' -import { validateIdInput } from './input-validation/idInputSchema' -import { ID } from './input-validation/types' -import { validateWithJSONSchema } from './input-validation/validateWithJSONSchema' import { authorizeOfferMutation, authorizeOfferQuery, diff --git a/src/resolvers/shipment.ts b/src/resolvers/shipment.ts index 320bf660a..c81f8a106 100644 --- a/src/resolvers/shipment.ts +++ b/src/resolvers/shipment.ts @@ -1,11 +1,20 @@ import { Type } from '@sinclair/typebox' import { ApolloError, ForbiddenError, UserInputError } from 'apollo-server' import { isEqual, xor } from 'lodash' +import { validateIdInput } from '../input-validation/idInputSchema' +import { + CurrentYearOrGreater, + ID, + MonthIndexStartingAt1, + Pricing, +} from '../input-validation/types' +import { validateWithJSONSchema } from '../input-validation/validateWithJSONSchema' import Group from '../models/group' import Shipment, { ShipmentAttributes } from '../models/shipment' import ShipmentExport from '../models/shipment_export' import ShipmentReceivingHub from '../models/shipment_receiving_hub' import ShipmentSendingHub from '../models/shipment_sending_hub' +import UserAccount from '../models/user_account' import { MutationResolvers, QueryResolvers, @@ -13,14 +22,6 @@ import { ShipmentStatus, ShippingRoute, } from '../server-internal-types' -import { validateIdInput } from './input-validation/idInputSchema' -import { - CurrentYearOrGreater, - ID, - MonthIndexStartingAt1, - Pricing, -} from './input-validation/types' -import { validateWithJSONSchema } from './input-validation/validateWithJSONSchema' const arraysOverlap = (a: unknown[], b: unknown[]): boolean => xor(a, b).length === 0 @@ -482,7 +483,7 @@ const receivingHubs: ShipmentResolvers['receivingHubs'] = async (parent) => { const shipmentExports: ShipmentResolvers['exports'] = async ( parent, _, - { auth: isAdmin }, + { auth: { isAdmin } }, ) => { if (!isAdmin) { throw new ForbiddenError('Must be admin to query shipment exports') @@ -490,6 +491,7 @@ const shipmentExports: ShipmentResolvers['exports'] = async ( return ShipmentExport.findAll({ where: { shipmentId: parent.id }, + include: [UserAccount], }).then((shipmentExports) => shipmentExports.map((shipmentExport) => shipmentExport.toWireFormat()), ) diff --git a/src/resolvers/shipment_exports.ts b/src/resolvers/shipment_exports.ts index 49b872dab..f6d8c5c7b 100644 --- a/src/resolvers/shipment_exports.ts +++ b/src/resolvers/shipment_exports.ts @@ -5,8 +5,11 @@ import Offer from '../models/offer' import Pallet from '../models/pallet' import Shipment from '../models/shipment' import ShipmentExport from '../models/shipment_export' +import UserAccount from '../models/user_account' import { MutationResolvers, QueryResolvers } from '../server-internal-types' +const include = [UserAccount] + const exportShipment: MutationResolvers['exportShipment'] = async ( _, { shipmentId }, @@ -59,10 +62,14 @@ const exportShipment: MutationResolvers['exportShipment'] = async ( const exportRecord = await ShipmentExport.create({ contentsCsv: csv, shipmentId, - userAccountId: auth.userAccount.id, + userAccountId: auth.userId, }) - return exportRecord.toWireFormat() + return ( + (await ShipmentExport.findByPk(exportRecord.id, { + include, + })) as ShipmentExport + ).toWireFormat() } export const HEADER_ROW = [ @@ -119,9 +126,9 @@ const listShipmentExports: QueryResolvers['listShipmentExports'] = async ( throw new ForbiddenError('Must be admin') } - return ( - await ShipmentExport.findAll({ where: { shipmentId } }) - ).map((shipmentExport: ShipmentExport) => shipmentExport.toWireFormat()) + return (await ShipmentExport.findAll({ where: { shipmentId }, include })).map( + (shipmentExport: ShipmentExport) => shipmentExport.toWireFormat(), + ) } export { exportShipment, listShipmentExports } diff --git a/src/routes/login.ts b/src/routes/login.ts new file mode 100644 index 000000000..176fda0f8 --- /dev/null +++ b/src/routes/login.ts @@ -0,0 +1,54 @@ +import { Type } from '@sinclair/typebox' +import { UserInputError } from 'apollo-server-express' +import bcrypt from 'bcrypt' +import { Request, Response } from 'express' +import { authCookie } from '../authenticateRequest' +import { trimAll } from '../input-validation/trimAll' +import { validateWithJSONSchema } from '../input-validation/validateWithJSONSchema' +import UserAccount from '../models/user_account' +import { passwordInput, usernameInput } from './register' + +const loginInput = Type.Object( + { + username: usernameInput, + password: passwordInput, + }, + { additionalProperties: false }, +) + +const validateLoginInput = validateWithJSONSchema(loginInput) + +const login = + (penaltySeconds = 10) => + async (request: Request, response: Response) => { + const valid = validateLoginInput(trimAll(request.body)) + if ('errors' in valid) { + return response + .status(400) + .json(new UserInputError('Login input invalid', valid.errors)) + .end() + } + + const user = await UserAccount.findOne({ + where: { + username: valid.value.username, + }, + }) + if (user === null) { + // Penalize + await new Promise((resolve) => setTimeout(resolve, penaltySeconds * 1000)) + return response.status(401).end() + } + if (!bcrypt.compareSync(valid.value.password, user.passwordHash)) { + // Penalize + await new Promise((resolve) => setTimeout(resolve, penaltySeconds * 1000)) + return response.status(401).end() + } + // Generate new token + response + .status(204) + .cookie(...authCookie(user)) + .end() + } + +export default login diff --git a/src/routes/me.ts b/src/routes/me.ts new file mode 100644 index 000000000..d18d6f970 --- /dev/null +++ b/src/routes/me.ts @@ -0,0 +1,17 @@ +import { Request, Response } from 'express' +import { AuthContext } from '../authenticateRequest' +import UserAccount from '../models/user_account' + +const getProfile = async (request: Request, response: Response) => { + const authContext = request.user as AuthContext + const user = await UserAccount.findByPk(authContext.userId) + if (user === null) return response.send(404).end() + return response + .json({ + username: user.username, + ...(await user.asPublicProfile()), + }) + .end() +} + +export default getProfile diff --git a/src/routes/me/cookie.ts b/src/routes/me/cookie.ts new file mode 100644 index 000000000..ffa92a0e6 --- /dev/null +++ b/src/routes/me/cookie.ts @@ -0,0 +1,28 @@ +import { Request, Response } from 'express' +import { AuthContext, authCookie, userHash } from '../../authenticateRequest' +import UserAccount from '../../models/user_account' + +const renewCookie = + (penaltySeconds = 10) => + async (request: Request, response: Response) => { + const authContext = request.user as AuthContext + console.log(authContext) + const user = await UserAccount.findByPk(authContext.userId) + if (user === null) { + // Penalize + await new Promise((resolve) => setTimeout(resolve, penaltySeconds * 1000)) + return response.status(401).end() + } + if (userHash(user) !== authContext.userHash) { + // Penalize + await new Promise((resolve) => setTimeout(resolve, penaltySeconds * 1000)) + return response.status(401).end() + } + // Generate new token + response + .status(204) + .cookie(...authCookie(user)) + .end() + } + +export default renewCookie diff --git a/src/routes/register.ts b/src/routes/register.ts new file mode 100644 index 000000000..edcd13aa4 --- /dev/null +++ b/src/routes/register.ts @@ -0,0 +1,56 @@ +import { Type } from '@sinclair/typebox' +import { UserInputError } from 'apollo-server-express' +import bcrypt from 'bcrypt' +import { Request, Response } from 'express' +import { authCookie } from '../authenticateRequest' +import { trimAll } from '../input-validation/trimAll' +import { validateWithJSONSchema } from '../input-validation/validateWithJSONSchema' +import UserAccount from '../models/user_account' + +export const usernameInput = Type.String({ + pattern: '^[a-z0-9._-]{1,255}$', + title: 'Username', +}) + +export const passwordInput = Type.String({ + pattern: '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$', + title: 'Password', +}) + +const registerUserInput = Type.Object( + { + username: usernameInput, + name: Type.String({ minLength: 1, maxLength: 255 }), + password: passwordInput, + }, + { additionalProperties: false }, +) + +const validateRegisterUserInput = validateWithJSONSchema(registerUserInput) + +const registerUser = + (saltRounds = 10) => + async (request: Request, response: Response) => { + const valid = validateRegisterUserInput(trimAll(request.body)) + if ('errors' in valid) { + return response + .status(400) + .json( + new UserInputError('User registration input invalid', valid.errors), + ) + .end() + } + + const user = await UserAccount.create({ + passwordHash: bcrypt.hashSync(valid.value.password, saltRounds), + username: valid.value.username, + name: valid.value.name, + }) + + return response + .status(202) + .cookie(...authCookie(user)) + .end() + } + +export default registerUser diff --git a/src/server.ts b/src/server.ts index 012d464d6..340584bb7 100644 --- a/src/server.ts +++ b/src/server.ts @@ -15,22 +15,28 @@ import './loadEnv' import './sequelize' import apolloServer from './apolloServer' -import getProfile from './getProfile' +import getProfile from './routes/me' import getAllFilesSync from './getAllFilesSync' import sendShipmentExportCsv from './sendShipmentExportCsv' import { cookieAuthStrategy } from './authenticateRequest' -import registerUser from './registerUser' -import login from './login' +import registerUser from './routes/register' +import login from './routes/login' +import { v4 } from 'uuid' +import renewCookie from './routes/me/cookie' const app = express() -app.use(cookieParser(process.env.COOKIE_SECRET ?? 'cookie-secret')) +/** + * @see ./docs/authentication.md + */ +app.use(cookieParser(process.env.COOKIE_SECRET ?? v4())) app.use(json()) const cookieAuth = passport.authenticate('cookie', { session: false }) passport.use(cookieAuthStrategy) app.get('/me', cookieAuth, getProfile) +app.get('/me/cookie', cookieAuth, renewCookie()) app.get('/login', login()) -app.get('/user', registerUser) +app.get('/register', registerUser()) app.get('/shipment-exports/:id', cookieAuth, sendShipmentExportCsv) app.use( diff --git a/src/testServer.ts b/src/testServer.ts index be582f8d7..ad9483e64 100644 --- a/src/testServer.ts +++ b/src/testServer.ts @@ -1,7 +1,7 @@ import { ApolloServer, ApolloServerExpressConfig } from 'apollo-server-express' import { merge } from 'lodash' import { serverConfig, Services } from './apolloServer' -import { fakeAdminAuth, fakeUserAuth } from './authenticateRequest' +import { userToAuthContext } from './authenticateRequest' import { CsvRow } from './generateCsv' import UserAccount from './models/user_account' @@ -29,14 +29,15 @@ export const makeTestServer = async ( overrides: Partial = {}, ): Promise => { if (overrides.context == null) { - const userAccount = await UserAccount.create({ - username: 'user-id', + const user = await UserAccount.create({ + isAdmin: false, + name: 'User', + username: 'user', passwordHash: '', - token: '', }) overrides.context = () => ({ - auth: { ...fakeUserAuth, userAccount }, + auth: userToAuthContext(user), services: { generateCsv: makeFakeGenerateCsvFn().generateCsv, }, @@ -53,21 +54,22 @@ export const makeAdminTestServer = async ( export const makeAdminTestServerWithServices = async ( overrides: Partial = {}, ) => { - const userAccount = await UserAccount.create({ - username: 'admin-auth0-id', - passwordHash: '', - token: '', - }) - const fakeGenrateCsv = makeFakeGenerateCsvFn() const services = { ...fakeGenrateCsv, } + const admin = await UserAccount.create({ + isAdmin: true, + name: 'Admin', + username: 'admin', + passwordHash: '', + }) + const testServer = await makeTestServer({ context: () => ({ - auth: { ...fakeAdminAuth, userAccount }, + auth: userToAuthContext(admin), services, }), ...overrides, diff --git a/src/tests/user_account_api.test.ts b/src/tests/authentication.test.ts similarity index 58% rename from src/tests/user_account_api.test.ts rename to src/tests/authentication.test.ts index 84f0db810..b261fb772 100644 --- a/src/tests/user_account_api.test.ts +++ b/src/tests/authentication.test.ts @@ -5,19 +5,20 @@ import { createServer, Server } from 'http' import passport from 'passport' import request, { SuperTest, Test } from 'supertest' import '../sequelize' -import { authTokenCookieName, cookieAuthStrategy } from '../authenticateRequest' -import getProfile from '../getProfile' +import { authCookieName, cookieAuthStrategy } from '../authenticateRequest' +import getProfile from '../routes/me' import cookieParser from 'cookie-parser' import { json } from 'body-parser' -import registerUser from '../registerUser' -import login from '../login' +import registerUser from '../routes/register' +import login from '../routes/login' +import renewCookie from '../routes/me/cookie' -jest.setTimeout(60 * 1000) +jest.setTimeout(15 * 1000) const cookieAuth = passport.authenticate('cookie', { session: false }) passport.use(cookieAuthStrategy) -const tokenCookieRx = new RegExp(`${authTokenCookieName}=([^;]+); Path=/`) +const tokenCookieRx = new RegExp(`${authCookieName}=([^;]+);`, 'i') const generateUsername = async () => (await randomWords({ numWords: 3 })).join('-') @@ -28,7 +29,7 @@ describe('User account API', () => { let r: SuperTest let username: string let password: string - let token: string + let authCookie: string beforeAll(async () => { username = await generateUsername() password = 'y{uugBmw"9,?=L_' @@ -36,8 +37,9 @@ describe('User account API', () => { app.use(cookieParser(process.env.COOKIE_SECRET ?? 'cookie-secret')) app.use(json()) app.get('/me', cookieAuth, getProfile) - app.post('/user', registerUser(1)) + app.post('/register', registerUser(1)) app.post('/login', login(0)) + app.get('/me/cookie', cookieAuth, renewCookie(0)) httpServer = createServer(app) await new Promise((resolve) => httpServer.listen(8888, '127.0.0.1', undefined, resolve), @@ -50,24 +52,51 @@ describe('User account API', () => { describe('/register', () => { it('should register a new user account', async () => { const res = await r - .post('/user') + .post('/register') .set('Accept', 'application/json') .set('Content-type', 'application/json; charset=utf-8') .send({ username, password, + name: 'Alex', }) .expect(202) .expect('set-cookie', tokenCookieRx) - token = tokenCookieRx.exec(res.header['set-cookie'])?.[1] as string + const cookieInfo = (res.header['set-cookie'][0] as string) + .split('; ') + .map((s) => s.split('=', 2)) + .reduce( + (c, [k, v], i) => + i === 0 + ? { + [decodeURIComponent(k)]: v ? decodeURIComponent(v) : true, + } + : { + ...c, + options: { + ...c.options, + [decodeURIComponent(k)]: v ? decodeURIComponent(v) : true, + }, + }, + {} as Record, + ) + + expect(cookieInfo[authCookieName]).toBeDefined() + expect(cookieInfo.options).toMatchObject({ Path: '/', HttpOnly: true }) + const expiresIn = + new Date(cookieInfo.options.Expires).getTime() - Date.now() + expect(expiresIn).toBeLessThan(30 * 60 * 1000) + expect(expiresIn).toBeGreaterThan(0) + + authCookie = tokenCookieRx.exec(res.header['set-cookie'])?.[1] as string }) }) describe('/me', () => { it('should return the user account of the current user', async () => { const res = await r .get('/me') - .set('Cookie', [`${authTokenCookieName}=${token}`]) + .set('Cookie', [`${authCookieName}=${authCookie}`]) .set('Accept', 'application/json') .send() .expect(200) @@ -80,9 +109,16 @@ describe('User account API', () => { it('should deny request for unknown token', async () => r .get('/me') - .set('Cookie', [`${authTokenCookieName}=foo`]) + .set('Cookie', [`${authCookieName}=foo`]) .send() .expect(401)) + describe('/cookie', () => { + it('should send a new cookie', () => + r + .get('/me/cookie') + .set('Cookie', [`${authCookieName}=${authCookie}`]) + .expect(204)) + }) }) describe('/login', () => { it('should return a token on login', () => @@ -99,7 +135,7 @@ describe('User account API', () => { .post('/login') .send({ username, - password: 'foo', + password: "Y @@ -107,7 +143,7 @@ describe('User account API', () => { .post('/login') .send({ username: 'foo', - password: 'foo', + password: "Y { // Create test servers captain = await UserAccount.create({ - username: 'captain-id', + username: 'captain', passwordHash: '', - token: '', + name: 'Captain A', }) newCaptain = await UserAccount.create({ - username: 'new-captain-id', + username: 'new-captain', passwordHash: '', - token: '', + name: 'New Captain', }) testServer = await makeTestServer({ - context: () => ({ auth: { ...fakeUserAuth, userAccount: captain } }), + context: () => ({ auth: userToAuthContext(captain) }), }) adminTestServer = await makeAdminTestServer() }) @@ -279,18 +279,18 @@ describe('Groups API', () => { beforeAll(async () => { captain1 = await UserAccount.create({ username: captain1Name, + name: captain1Name, passwordHash: '', - token: '', }) captain2 = await UserAccount.create({ username: captain2Name, + name: captain2Name, passwordHash: '', - token: '', }) daCaptain = await UserAccount.create({ username: daCaptainName, + name: daCaptainName, passwordHash: '', - token: '', }) sendingGroup1 = await Group.create({ name: sendingGroup1Name, @@ -323,7 +323,7 @@ describe('Groups API', () => { ...commonGroupData, }) testServer = await makeTestServer({ - context: () => ({ auth: { ...fakeUserAuth, userAccount: captain1 } }), + context: () => ({ auth: userToAuthContext(captain1) }), }) }) diff --git a/src/tests/helpers/index.ts b/src/tests/helpers/index.ts index 13c8d13d8..3a8f625cf 100644 --- a/src/tests/helpers/index.ts +++ b/src/tests/helpers/index.ts @@ -20,7 +20,7 @@ async function createGroup( const groupCaptain = await UserAccount.create({ username: `fake-auth-id-${fakeusername++}`, passwordHash: '', - token: '', + name: 'Captain', }) captainId = groupCaptain.id } diff --git a/src/tests/input-validation/errorsToProblemDetails.test.ts b/src/tests/input-validation/errorsToProblemDetails.test.ts index f3523259c..eb5f55327 100644 --- a/src/tests/input-validation/errorsToProblemDetails.test.ts +++ b/src/tests/input-validation/errorsToProblemDetails.test.ts @@ -1,11 +1,8 @@ import { Type } from '@sinclair/typebox' import { ErrorObject } from 'ajv' -import { errorsToProblemDetails } from '../../resolvers/input-validation/errorsToProblemDetails' -import { - NonEmptyShortString, - URI, -} from '../../resolvers/input-validation/types' -import { validateWithJSONSchema } from '../../resolvers/input-validation/validateWithJSONSchema' +import { errorsToProblemDetails } from '../../input-validation/errorsToProblemDetails' +import { NonEmptyShortString, URI } from '../../input-validation/types' +import { validateWithJSONSchema } from '../../input-validation/validateWithJSONSchema' describe('errorsToProblemDetails() should turn validation errors into Problem Details for HTTP APIs (RFC7807)', () => { it('should format an unknown property in the top-level object', () => { diff --git a/src/tests/input-validation/types.test.ts b/src/tests/input-validation/types.test.ts index 101728e65..ecd8e9bc2 100644 --- a/src/tests/input-validation/types.test.ts +++ b/src/tests/input-validation/types.test.ts @@ -7,8 +7,8 @@ import { TwoLetterCountryCode, URI, ValueOrUnset, -} from '../../resolvers/input-validation/types' -import { validateWithJSONSchema } from '../../resolvers/input-validation/validateWithJSONSchema' +} from '../../input-validation/types' +import { validateWithJSONSchema } from '../../input-validation/validateWithJSONSchema' describe('input validation types', () => { describe('validate country code input', () => { diff --git a/src/tests/input-validation/validateWithJSONSchema.test.ts b/src/tests/input-validation/validateWithJSONSchema.test.ts index 15581c5a5..a281c2847 100644 --- a/src/tests/input-validation/validateWithJSONSchema.test.ts +++ b/src/tests/input-validation/validateWithJSONSchema.test.ts @@ -1,5 +1,5 @@ import { Type } from '@sinclair/typebox' -import { validateWithJSONSchema } from '../../resolvers/input-validation/validateWithJSONSchema' +import { validateWithJSONSchema } from '../../input-validation/validateWithJSONSchema' describe('validateWithJSONSchema', () => { it('should validate input against a JSON schema', () => { diff --git a/src/tests/line_items_api.test.ts b/src/tests/line_items_api.test.ts index 2e1040f31..2ce641c99 100644 --- a/src/tests/line_items_api.test.ts +++ b/src/tests/line_items_api.test.ts @@ -1,6 +1,6 @@ import { ApolloServer } from 'apollo-server-express' import gql from 'graphql-tag' -import { fakeUserAuth } from '../authenticateRequest' +import { userToAuthContext } from '../authenticateRequest' import Group from '../models/group' import LineItem from '../models/line_item' import Offer from '../models/offer' @@ -37,13 +37,13 @@ describe('LineItems API', () => { await sequelize.sync({ force: true }) captain = await UserAccount.create({ - username: 'captain-id', + username: 'captain', passwordHash: '', - token: '', + name: 'Captain', }) captainTestServer = await makeTestServer({ - context: () => ({ auth: { ...fakeUserAuth, userAccount: captain } }), + context: () => ({ auth: userToAuthContext(captain) }), }) adminTestServer = await makeAdminTestServer() otherUserTestServer = await makeTestServer() diff --git a/src/tests/offers_api.test.ts b/src/tests/offers_api.test.ts index 461c25d40..81c207008 100644 --- a/src/tests/offers_api.test.ts +++ b/src/tests/offers_api.test.ts @@ -1,6 +1,6 @@ import { ApolloServer } from 'apollo-server-express' import gql from 'graphql-tag' -import { fakeUserAuth } from '../authenticateRequest' +import { userToAuthContext } from '../authenticateRequest' import Group from '../models/group' import Offer from '../models/offer' import Shipment from '../models/shipment' @@ -34,13 +34,13 @@ describe('Offers API', () => { await Shipment.truncate({ cascade: true, force: true }) captain = await UserAccount.create({ - username: 'captain-id', + username: 'captain', passwordHash: '', - token: '', + name: 'Captain', }) captainTestServer = await makeTestServer({ - context: () => ({ auth: { ...fakeUserAuth, userAccount: captain } }), + context: () => ({ auth: userToAuthContext(captain) }), }) otherUserTestServer = await makeTestServer() adminTestServer = await makeAdminTestServer() diff --git a/src/tests/pallets_api.test.ts b/src/tests/pallets_api.test.ts index e09054d27..9906902e4 100644 --- a/src/tests/pallets_api.test.ts +++ b/src/tests/pallets_api.test.ts @@ -1,6 +1,6 @@ import { ApolloServer } from 'apollo-server-express' import gql from 'graphql-tag' -import { fakeUserAuth } from '../authenticateRequest' +import { userToAuthContext } from '../authenticateRequest' import Group from '../models/group' import LineItem from '../models/line_item' import Offer from '../models/offer' @@ -40,13 +40,13 @@ describe('Pallets API', () => { await Pallet.truncate({ cascade: true, force: true }) captain = await UserAccount.create({ - username: 'captain-id', + username: 'captain', passwordHash: '', - token: '', + name: 'Captain', }) captainTestServer = await makeTestServer({ - context: () => ({ auth: { ...fakeUserAuth, userAccount: captain } }), + context: () => ({ auth: userToAuthContext(captain) }), }) adminTestServer = await makeAdminTestServer() otherUserTestServer = await makeTestServer() diff --git a/src/tests/shipment_exports_api.test.ts b/src/tests/shipment_exports_api.test.ts index 78730329d..054ddf973 100644 --- a/src/tests/shipment_exports_api.test.ts +++ b/src/tests/shipment_exports_api.test.ts @@ -38,9 +38,9 @@ describe('ShipmentExports API', () => { await sequelize.sync({ force: true }) captain = await UserAccount.create({ - username: 'captain-id', + username: 'captain', passwordHash: '', - token: '', + name: 'Captain', }) const serverWithContext = await makeAdminTestServerWithServices() diff --git a/src/tests/trimAll.test.ts b/src/tests/trimAll.test.ts new file mode 100644 index 000000000..c306468f0 --- /dev/null +++ b/src/tests/trimAll.test.ts @@ -0,0 +1,18 @@ +import { trimAll } from '../input-validation/trimAll' + +describe('trimAll', () => { + it('should trim all whitespace in an object', () => + expect( + trimAll({ + foo: ' bar ', + trimEnd: 'bar ', + trimStart: ' bar', + keepSpace: ' foo bar ', + }), + ).toMatchObject({ + foo: 'bar', + trimEnd: 'bar', + trimStart: 'bar', + keepSpace: 'foo bar', + })) +}) From 26644e6646b2509942c35636751a7ba9ec7bc39e Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 26 Aug 2021 00:43:06 +0200 Subject: [PATCH 03/65] fix: use uuid to generate usernames --- src/tests/authentication.test.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/tests/authentication.test.ts b/src/tests/authentication.test.ts index b261fb772..db93913ae 100644 --- a/src/tests/authentication.test.ts +++ b/src/tests/authentication.test.ts @@ -1,5 +1,4 @@ // tslint:disable:ordered-imports -import { randomWords } from '@nordicsemiconductor/random-words' import express, { Express } from 'express' import { createServer, Server } from 'http' import passport from 'passport' @@ -12,6 +11,7 @@ import { json } from 'body-parser' import registerUser from '../routes/register' import login from '../routes/login' import renewCookie from '../routes/me/cookie' +import { v4 } from 'uuid' jest.setTimeout(15 * 1000) @@ -20,9 +20,6 @@ passport.use(cookieAuthStrategy) const tokenCookieRx = new RegExp(`${authCookieName}=([^;]+);`, 'i') -const generateUsername = async () => - (await randomWords({ numWords: 3 })).join('-') - describe('User account API', () => { let app: Express let httpServer: Server @@ -31,7 +28,7 @@ describe('User account API', () => { let password: string let authCookie: string beforeAll(async () => { - username = await generateUsername() + username = v4() password = 'y{uugBmw"9,?=L_' app = express() app.use(cookieParser(process.env.COOKIE_SECRET ?? 'cookie-secret')) From 93327f9e9cb4a163e33ff05898aa1b45ed3617b6 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 26 Aug 2021 00:49:00 +0200 Subject: [PATCH 04/65] fix: update docs --- docs/authentication.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/authentication.md b/docs/authentication.md index ea55b315f..4446cf0ab 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -1,18 +1,16 @@ # Authentication -The backend authenticates requests using signed cookies so they can contain user information so that it does not have to be fetched for every request. +The backend authenticates requests using signed cookies which contains user's id so that it does not have to be fetched for every request. -The cookie contains the user's id. +Cookies are sent [`secure` and `HttpOnly`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) when users register their account, or when they log in using username and password. -Cookies are sent [`secure` and `HttpOnly`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) when users register their account, or when they login using username and password. +Cookies expire after 30 minutes and the client is responsible for renewing cookies by calling the `GET /me/cookie` endpoint before they expire. -Cookies expire after 30 minutes and the client is responsible to renew cookies by calling the `GET /me/cookie` endpoint before they expire. - -When renewing cookies the server will re-check if the user still exists and if they haven't changed their password. For this a hash of the user's password hash, email, username, and id will be generated and included in the cookie. If any of these properties changes, the cookie cannot be renewed and the user has to log-in again. +When renewing cookies, the server will re-check if the user still exists and if they haven't changed their password. For this a hash of the user's password hash, email, username, and id will be generated and included in the cookie. If any of these properties changes, the cookie cannot be renewed and the user has to log-in again. ## Admin permissions -Admin permission are granted via the `isAdmin` flag on the `UserAccount`. +Admin permission are granted via the `isAdmin` flag on the `UserAccount` model. ## Configuration From 3fd89756ad3292dfd020291346098f0333690377 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Fri, 27 Aug 2021 00:38:34 +0200 Subject: [PATCH 05/65] feat: add login and registration to frontend --- frontend/.env | 11 -- frontend/package.json | 1 - frontend/src/AppRoot.tsx | 119 +++++++++--------- frontend/src/apolloClient.ts | 41 ++++++ .../src/components/ApolloAuthProvider.tsx | 80 ------------ frontend/src/components/LogInButton.tsx | 12 +- frontend/src/components/PrivateRoute.tsx | 4 +- frontend/src/components/TopNavigation.tsx | 9 +- .../src/components/UserProfileContext.tsx | 16 +-- frontend/src/hooks/useAuth.ts | 34 +++++ frontend/src/index.tsx | 10 +- frontend/src/pages/PublicHome.tsx | 103 ++++++++++++++- frontend/src/pages/offers/CreateOfferForm.tsx | 2 +- .../src/pages/shipments/DownloadCSVMenu.tsx | 5 - frontend/yarn.lock | 59 +-------- src/authenticateRequest.ts | 12 ++ src/routes/me/cookie.ts | 16 ++- src/server.ts | 3 +- src/tests/authentication.test.ts | 55 +++++--- 19 files changed, 316 insertions(+), 276 deletions(-) create mode 100644 frontend/src/apolloClient.ts delete mode 100644 frontend/src/components/ApolloAuthProvider.tsx create mode 100644 frontend/src/hooks/useAuth.ts diff --git a/frontend/.env b/frontend/.env index 81b11609d..c47b5fc10 100644 --- a/frontend/.env +++ b/frontend/.env @@ -7,16 +7,5 @@ REACT_APP_SERVER_URL="http://localhost:3000" # The URL where the Apollo server runs REACT_APP_GRAPHQL_URL="http://localhost:3000/graphql" -# The URL where the client runs -REACT_APP_CLIENT_URL="http://localhost:8080" - -# Auth0 configuration -## Tenant -REACT_APP_AUTH0_DOMAIN="distributeaid.eu.auth0.com" -## Client ID -REACT_APP_AUTH0_CLIENT_ID="hfNo3Nw2ZrAGv0Vwh7tjU4nJ7lAuPTNH" -## audience -REACT_APP_AUTH0_AUDIENCE="https://da-shipping-tracker-dev" - # See this issue https://github.com/facebook/create-react-app/issues/1795#issuecomment-357353472 SKIP_PREFLIGHT_CHECK=true diff --git a/frontend/package.json b/frontend/package.json index f470b30d4..a694085c7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,6 @@ "private": true, "dependencies": { "@apollo/client": "3.3.20", - "@auth0/auth0-react": "1.5.0", "@hookform/error-message": "2.0.0", "@types/body-scroll-lock": "2.6.1", "@types/classnames": "2.3.0", diff --git a/frontend/src/AppRoot.tsx b/frontend/src/AppRoot.tsx index 374c2d25c..3c3f1e1ff 100644 --- a/frontend/src/AppRoot.tsx +++ b/frontend/src/AppRoot.tsx @@ -1,8 +1,7 @@ -import { useAuth0 } from '@auth0/auth0-react' import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' -import ApolloAuthProvider from './components/ApolloAuthProvider' import PrivateRoute from './components/PrivateRoute' import { UserProfileProvider } from './components/UserProfileContext' +import { useAuth } from './hooks/useAuth' import AdminPage from './pages/AdminPage' import ApolloDemoPage from './pages/demo/ApolloDemo' import GroupCreatePage from './pages/groups/GroupCreatePage' @@ -25,69 +24,67 @@ import ROUTES from './utils/routes' const isDev = process.env.NODE_ENV === 'development' const AppRoot = () => { - const { isLoading, isAuthenticated } = useAuth0() + const { isLoading, isAuthenticated } = useAuth() return ( - - - - - {isDev && ( - - - - )} - {isLoading && ( - - - - )} - - {isAuthenticated ? : } + + + + {isDev && ( + + - - - - - + )} + {isLoading && ( + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + )} + + {isAuthenticated ? : } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) } diff --git a/frontend/src/apolloClient.ts b/frontend/src/apolloClient.ts new file mode 100644 index 000000000..5ad6ca62e --- /dev/null +++ b/frontend/src/apolloClient.ts @@ -0,0 +1,41 @@ +import { + ApolloClient, + FieldMergeFunction, + HttpLink, + InMemoryCache, +} from '@apollo/client' + +/** + * Merge fields by replacing them with the incoming value. + * + * This custom merge function is APPARENTLY necessary to avoid the + * following warning: + * "Cache data may be lost when replacing the pallets field of a + * object" + */ +const mergeByReplacement: { merge: FieldMergeFunction } = { + merge: (existing = [], incoming = []) => { + return [...incoming] + }, +} + +export const apolloClient = new ApolloClient({ + link: new HttpLink({ + uri: process.env.REACT_APP_GRAPHQL_URL, + credentials: 'include', + }), + cache: new InMemoryCache({ + typePolicies: { + Offer: { + fields: { + pallets: mergeByReplacement, + }, + }, + Pallet: { + fields: { + lineItems: mergeByReplacement, + }, + }, + }, + }), +}) diff --git a/frontend/src/components/ApolloAuthProvider.tsx b/frontend/src/components/ApolloAuthProvider.tsx deleted file mode 100644 index c5233df24..000000000 --- a/frontend/src/components/ApolloAuthProvider.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { - ApolloClient, - ApolloLink, - ApolloProvider, - FieldMergeFunction, - fromPromise, - HttpLink, - InMemoryCache, -} from '@apollo/client' -import { useAuth0 } from '@auth0/auth0-react' -import { FunctionComponent } from 'react' - -const clientUrl = process.env.REACT_APP_CLIENT_URL - -// https://www.apollographql.com/docs/link/links/http/ -const httpLink = new HttpLink({ - uri: process.env.REACT_APP_GRAPHQL_URL, - credentials: 'include', -}) - -/** - * Merge fields by replacing them with the incoming value. - * - * This custom merge function is APPARENTLY necessary to avoid the - * following warning: - * "Cache data may be lost when replacing the pallets field of a - * object" - */ -const mergeByReplacement: { merge: FieldMergeFunction } = { - merge: (existing = [], incoming = []) => { - return [...incoming] - }, -} - -const ApolloAuthProvider: FunctionComponent = ({ children }) => { - const { getAccessTokenSilently } = useAuth0() - - // https://www.apollographql.com/docs/react/networking/authentication/ - const authLink = new ApolloLink((operation, forward) => { - return fromPromise( - getAccessTokenSilently() - .then((token: string) => { - operation.setContext({ - headers: { - authorization: token ? `Bearer ${token}` : '', - 'Access-Control-Allow-Origin': clientUrl, - }, - }) - - return operation - }) - .catch(() => { - // not signed in - return operation - }), - ).flatMap(forward) - }) - - const apolloClient = new ApolloClient({ - link: authLink.concat(httpLink), - cache: new InMemoryCache({ - typePolicies: { - Offer: { - fields: { - pallets: mergeByReplacement, - }, - }, - Pallet: { - fields: { - lineItems: mergeByReplacement, - }, - }, - }, - }), - }) - - return {children} -} - -export default ApolloAuthProvider diff --git a/frontend/src/components/LogInButton.tsx b/frontend/src/components/LogInButton.tsx index 3e321b573..dc15d6816 100644 --- a/frontend/src/components/LogInButton.tsx +++ b/frontend/src/components/LogInButton.tsx @@ -1,4 +1,3 @@ -import { useAuth0 } from '@auth0/auth0-react' import { FunctionComponent } from 'react' interface Props { @@ -6,10 +5,15 @@ interface Props { } const LogInButton: FunctionComponent = ({ className }) => { - const { loginWithRedirect } = useAuth0() - return ( - ) diff --git a/frontend/src/components/PrivateRoute.tsx b/frontend/src/components/PrivateRoute.tsx index c3f9d422c..f5e5e6e6a 100644 --- a/frontend/src/components/PrivateRoute.tsx +++ b/frontend/src/components/PrivateRoute.tsx @@ -1,13 +1,13 @@ -import { useAuth0 } from '@auth0/auth0-react' import { FunctionComponent } from 'react' import { Redirect, Route, RouteProps } from 'react-router-dom' +import { useAuth } from '../hooks/useAuth' /** * This component will redirect to the sign-in screen if the user isn't logged * in, or render the component otherwise. */ const PrivateRoute: FunctionComponent = (props) => { - const { isAuthenticated } = useAuth0() + const { isAuthenticated } = useAuth() const { children, ...rest } = props return ( diff --git a/frontend/src/components/TopNavigation.tsx b/frontend/src/components/TopNavigation.tsx index 1519909fd..6f7142a65 100644 --- a/frontend/src/components/TopNavigation.tsx +++ b/frontend/src/components/TopNavigation.tsx @@ -1,6 +1,6 @@ -import { useAuth0 } from '@auth0/auth0-react' import { FunctionComponent, ReactNode, useContext } from 'react' import { Link } from 'react-router-dom' +import { useAuth } from '../hooks/useAuth' import ROUTES from '../utils/routes' import Badge from './Badge' import DistributeAidLogo from './branding/DistributeAidLogo' @@ -47,7 +47,7 @@ interface Props { * branding and a dropdown-menu with some account information. */ const TopNavigation: FunctionComponent = ({ hideControls }) => { - const { user, logout } = useAuth0() + const { logout } = useAuth() const { profile } = useContext(UserProfileContext) const userIsAdmin = profile?.isAdmin @@ -66,7 +66,7 @@ const TopNavigation: FunctionComponent = ({ hideControls }) => { - {!hideControls && user && ( + {!hideControls && profile && (
= ({ hideControls }) => { >
- {user.name} + {profile.name} {userIsAdmin && Admin}
-
{user.email}
logout()}> diff --git a/frontend/src/components/UserProfileContext.tsx b/frontend/src/components/UserProfileContext.tsx index 75e5278c3..49ec56835 100644 --- a/frontend/src/components/UserProfileContext.tsx +++ b/frontend/src/components/UserProfileContext.tsx @@ -1,9 +1,11 @@ -import { useAuth0 } from '@auth0/auth0-react' import { createContext, FunctionComponent, useEffect, useState } from 'react' +const SERVER_URL = process.env.REACT_APP_SERVER_URL + export interface UserProfile { id: number isAdmin: boolean + name: string groupId?: number } @@ -15,18 +17,11 @@ interface UserProfileData { refetch: () => void } -const fetchProfile = (token: string) => { - return fetch('/me', { - headers: { Authorization: `Bearer ${token}` }, - }) -} - const UserProfileContext = createContext({ refetch: () => {}, }) const UserProfileProvider: FunctionComponent = ({ children }) => { - const { getAccessTokenSilently } = useAuth0() const [tokenWasFetched, setTokenWasFetched] = useState(false) const [profile, setProfile] = useState() @@ -36,8 +31,7 @@ const UserProfileProvider: FunctionComponent = ({ children }) => { // We fetch the token again in case the client-side cookie has expired but // the remote session hasn't if (!tokenWasFetched) { - getAccessTokenSilently() - .then(fetchProfile) + fetch(`${SERVER_URL}/me`) .then((response) => response.json()) .catch(() => { // The user is not logged in @@ -49,7 +43,7 @@ const UserProfileProvider: FunctionComponent = ({ children }) => { setTokenWasFetched(true) }) } - }, [tokenWasFetched, getAccessTokenSilently]) + }, [tokenWasFetched]) return ( diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts new file mode 100644 index 000000000..60acb771b --- /dev/null +++ b/frontend/src/hooks/useAuth.ts @@ -0,0 +1,34 @@ +import { useState } from 'react' + +const SERVER_URL = process.env.REACT_APP_SERVER_URL + +export const useAuth = () => { + const [isLoading, setIsLoading] = useState(false) + const [isAuthenticated, setIsAuthenticated] = useState(false) + return { + isLoading, + isAuthenticated, + logout: () => { + // FIXME: clear cookies and reload + fetch(`${SERVER_URL}/me/cookie`, { method: 'DELETE' }).then(() => { + setIsAuthenticated(false) + document.location.reload() + }) + }, + login: ({ username, password }: { username: string; password: string }) => { + setIsLoading(true) + fetch(`${SERVER_URL}/me/login`, { + method: 'POST', + body: JSON.stringify({ username, password }), + }) + .then(() => { + setIsAuthenticated(true) + setIsLoading(false) + }) + .catch((err) => { + console.error(err) + setIsLoading(false) + }) + }, + } +} diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 181609a90..0843cf007 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,4 +1,3 @@ -import { Auth0Provider } from '@auth0/auth0-react' import React from 'react' import ReactDOM from 'react-dom' import App from './AppRoot' @@ -6,14 +5,7 @@ import './stylesheets/index.output.css' ReactDOM.render( - - - + , document.getElementById('root'), ) diff --git a/frontend/src/pages/PublicHome.tsx b/frontend/src/pages/PublicHome.tsx index cef7c32cd..fc6ac1e31 100644 --- a/frontend/src/pages/PublicHome.tsx +++ b/frontend/src/pages/PublicHome.tsx @@ -1,8 +1,26 @@ -import { FunctionComponent } from 'react' +import { FunctionComponent, useState } from 'react' import DistributeAidWordmark from '../components/branding/DistributeAidWordmark' -import LogInButton from '../components/LogInButton' +import TextField from '../components/forms/TextField' +import { useAuth } from '../hooks/useAuth' + +const SERVER_URL = process.env.REACT_APP_SERVER_URL const PublicHomePage: FunctionComponent = () => { + const { login } = useAuth() + const [showRegisterForm, setShowRegisterForm] = useState(false) + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + const [password2, setPassword2] = useState('') + + const loginFormValid = + username.length > 0 && + /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/.test( + password, + ) + + const registerFormValid = loginFormValid && password === password2 + + const showLoginForm = !showRegisterForm return (
@@ -15,7 +33,86 @@ const PublicHomePage: FunctionComponent = () => { Welcome to Distribute Aid's shipment tracker! Please log in to continue.

- + {showLoginForm && ( +
+ setUsername(value)} + /> + setPassword(value)} + /> + + + + )} + {showRegisterForm && ( +
+ setUsername(value)} + /> + setPassword(value)} + /> + setPassword2(value)} + /> + + + + )}
diff --git a/frontend/src/pages/offers/CreateOfferForm.tsx b/frontend/src/pages/offers/CreateOfferForm.tsx index f762e390d..aa7f95343 100644 --- a/frontend/src/pages/offers/CreateOfferForm.tsx +++ b/frontend/src/pages/offers/CreateOfferForm.tsx @@ -109,7 +109,7 @@ const CreateOfferForm: FunctionComponent = (props) => { } } }, - [watchSendingGroupId, groups, reset], + [watchSendingGroupId, groups, reset, profile], ) const onSubmitForm = (input: OfferCreateInput) => { diff --git a/frontend/src/pages/shipments/DownloadCSVMenu.tsx b/frontend/src/pages/shipments/DownloadCSVMenu.tsx index 8c85d0a81..4cae8a632 100644 --- a/frontend/src/pages/shipments/DownloadCSVMenu.tsx +++ b/frontend/src/pages/shipments/DownloadCSVMenu.tsx @@ -1,4 +1,3 @@ -import { useAuth0 } from '@auth0/auth0-react' import format from 'date-fns/format' import { FunctionComponent, useMemo } from 'react' import Button from '../../components/Button' @@ -16,7 +15,6 @@ interface Props { const SERVER_URL = process.env.REACT_APP_SERVER_URL const DownloadCSVMenu: FunctionComponent = ({ shipment }) => { - const { getAccessTokenSilently } = useAuth0() const [modalIsVisible, showModal, hideModal] = useModalState() const [exportShipment, { loading: exportIsProcessing }] = @@ -36,10 +34,7 @@ const DownloadCSVMenu: FunctionComponent = ({ shipment }) => { } const downloadShipment = async (downloadPath: string) => { - const accessToken = await getAccessTokenSilently() - const downloadUrl = new URL(downloadPath, SERVER_URL) - downloadUrl.searchParams.append('authorization', `Bearer ${accessToken}`) window.open(downloadUrl.href) } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 2a62846e0..48370cefc 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -21,26 +21,6 @@ tslib "^1.10.0" zen-observable "^0.8.14" -"@auth0/auth0-react@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@auth0/auth0-react/-/auth0-react-1.5.0.tgz#f2dcfef1cab59f8555ade4e2b5ec13befce67395" - integrity sha512-LFJUd3V6aKCKxblJvrz3JIHzyBCz2X3ax5RdSjxZwGr5XxPwdhogeBWcyijnuB8moKD2ncl56OpOslbVGLIJ/w== - dependencies: - "@auth0/auth0-spa-js" "^1.15.0" - -"@auth0/auth0-spa-js@^1.15.0": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@auth0/auth0-spa-js/-/auth0-spa-js-1.15.0.tgz#9fa563b7b2e49dc4c6a465a0c240078322e80159" - integrity sha512-d/crchAbhncl9irIMuw1zNSZgX+id0U7mzASQr2htMJ73JCYaAvBSdGXL0WcYS4yBm1Xsx1JYm3b5tEZ5p/ncg== - dependencies: - abortcontroller-polyfill "^1.7.1" - browser-tabs-lock "^1.2.13" - core-js "^3.11.0" - es-cookie "^1.3.2" - fast-text-encoding "^1.0.3" - promise-polyfill "^8.2.0" - unfetch "^4.2.0" - "@babel/code-frame@7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" @@ -3419,11 +3399,6 @@ abab@^2.0.3, abab@^2.0.5: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== -abortcontroller-polyfill@^1.7.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5" - integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q== - accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -4248,13 +4223,6 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browser-tabs-lock@^1.2.13: - version "1.2.14" - resolved "https://registry.yarnpkg.com/browser-tabs-lock/-/browser-tabs-lock-1.2.14.tgz#f4ba30810d20199a1858c102da1f91e339250728" - integrity sha512-ssSpCRcvFe4vc098LDnrJOQDfZiG35KhQGB9hthTbwJk5mmUkePwhcMlW61NH3YuIE2Y9uGLqf9yxEBKbaDlaw== - dependencies: - lodash ">=4.17.21" - browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -4978,11 +4946,6 @@ core-js@^2.4.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -core-js@^3.11.0: - version "3.15.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.1.tgz#6c08ab88abdf56545045ccf5fd81f47f407e7f1a" - integrity sha512-h8VbZYnc9pDzueiS2610IULDkpFFPunHwIpl8yRwFahAEEdSpHlTy3h3z3rKq5h11CaUdBEeRViu9AYvbxiMeg== - core-js@^3.6.5: version "3.9.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.9.1.tgz#cec8de593db8eb2a85ffb0dbdeb312cb6e5460ae" @@ -5878,11 +5841,6 @@ es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: string.prototype.trimend "^1.0.3" string.prototype.trimstart "^1.0.3" -es-cookie@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/es-cookie/-/es-cookie-1.3.2.tgz#80e831597f72a25721701bdcb21d990319acd831" - integrity sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q== - es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -6439,11 +6397,6 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fast-text-encoding@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" - integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== - fastq@^1.6.0: version "1.11.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" @@ -9060,7 +9013,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -"lodash@>=3.5 <5", lodash@>=4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@^4.7.0: +"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -11038,11 +10991,6 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -promise-polyfill@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0" - integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g== - promise@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" @@ -13129,11 +13077,6 @@ typescript@4.4.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324" integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA== -unfetch@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" - integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== - unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" diff --git a/src/authenticateRequest.ts b/src/authenticateRequest.ts index 1f8670915..f02694d83 100644 --- a/src/authenticateRequest.ts +++ b/src/authenticateRequest.ts @@ -63,6 +63,18 @@ export const authCookie = ( }, ] +// Sends an expired cookie to the client so it will be removed +export const expireAuthCookie = (): [string, string, CookieOptions] => [ + authCookieName, + '', + { + signed: true, + secure: true, + httpOnly: true, + expires: new Date(Date.now() - 60 * 1000), + }, +] + export const userToAuthContext = (user: UserAccount): AuthContext => ({ isAdmin: user.isAdmin, userId: user.id, diff --git a/src/routes/me/cookie.ts b/src/routes/me/cookie.ts index ffa92a0e6..a98d51811 100644 --- a/src/routes/me/cookie.ts +++ b/src/routes/me/cookie.ts @@ -1,12 +1,16 @@ import { Request, Response } from 'express' -import { AuthContext, authCookie, userHash } from '../../authenticateRequest' +import { + AuthContext, + authCookie, + expireAuthCookie, + userHash, +} from '../../authenticateRequest' import UserAccount from '../../models/user_account' -const renewCookie = +export const renewCookie = (penaltySeconds = 10) => async (request: Request, response: Response) => { const authContext = request.user as AuthContext - console.log(authContext) const user = await UserAccount.findByPk(authContext.userId) if (user === null) { // Penalize @@ -25,4 +29,8 @@ const renewCookie = .end() } -export default renewCookie +export const deleteCookie = (_: Request, response: Response) => + response + .status(204) + .cookie(...expireAuthCookie()) + .end() diff --git a/src/server.ts b/src/server.ts index 340584bb7..d0e42cf7b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -22,7 +22,7 @@ import { cookieAuthStrategy } from './authenticateRequest' import registerUser from './routes/register' import login from './routes/login' import { v4 } from 'uuid' -import renewCookie from './routes/me/cookie' +import { renewCookie, deleteCookie } from './routes/me/cookie' const app = express() /** @@ -35,6 +35,7 @@ passport.use(cookieAuthStrategy) app.get('/me', cookieAuth, getProfile) app.get('/me/cookie', cookieAuth, renewCookie()) +app.delete('/me/cookie', cookieAuth, deleteCookie) app.get('/login', login()) app.get('/register', registerUser()) app.get('/shipment-exports/:id', cookieAuth, sendShipmentExportCsv) diff --git a/src/tests/authentication.test.ts b/src/tests/authentication.test.ts index db93913ae..bc60161b3 100644 --- a/src/tests/authentication.test.ts +++ b/src/tests/authentication.test.ts @@ -10,7 +10,7 @@ import cookieParser from 'cookie-parser' import { json } from 'body-parser' import registerUser from '../routes/register' import login from '../routes/login' -import renewCookie from '../routes/me/cookie' +import { renewCookie, deleteCookie } from '../routes/me/cookie' import { v4 } from 'uuid' jest.setTimeout(15 * 1000) @@ -20,6 +20,26 @@ passport.use(cookieAuthStrategy) const tokenCookieRx = new RegExp(`${authCookieName}=([^;]+);`, 'i') +const parseCookie = (cookie: string) => + cookie + .split('; ') + .map((s) => s.split('=', 2)) + .reduce( + (c, [k, v], i) => + i === 0 + ? { + [decodeURIComponent(k)]: v ? decodeURIComponent(v) : true, + } + : { + ...c, + options: { + ...c.options, + [decodeURIComponent(k)]: v ? decodeURIComponent(v) : true, + }, + }, + {} as Record, + ) + describe('User account API', () => { let app: Express let httpServer: Server @@ -37,6 +57,7 @@ describe('User account API', () => { app.post('/register', registerUser(1)) app.post('/login', login(0)) app.get('/me/cookie', cookieAuth, renewCookie(0)) + app.delete('/me/cookie', cookieAuth, deleteCookie) httpServer = createServer(app) await new Promise((resolve) => httpServer.listen(8888, '127.0.0.1', undefined, resolve), @@ -60,25 +81,7 @@ describe('User account API', () => { .expect(202) .expect('set-cookie', tokenCookieRx) - const cookieInfo = (res.header['set-cookie'][0] as string) - .split('; ') - .map((s) => s.split('=', 2)) - .reduce( - (c, [k, v], i) => - i === 0 - ? { - [decodeURIComponent(k)]: v ? decodeURIComponent(v) : true, - } - : { - ...c, - options: { - ...c.options, - [decodeURIComponent(k)]: v ? decodeURIComponent(v) : true, - }, - }, - {} as Record, - ) - + const cookieInfo = parseCookie(res.header['set-cookie'][0] as string) expect(cookieInfo[authCookieName]).toBeDefined() expect(cookieInfo.options).toMatchObject({ Path: '/', HttpOnly: true }) const expiresIn = @@ -115,6 +118,18 @@ describe('User account API', () => { .get('/me/cookie') .set('Cookie', [`${authCookieName}=${authCookie}`]) .expect(204)) + it('should delete a cookie', async () => { + const res = await r + .delete('/me/cookie') + .set('Cookie', [`${authCookieName}=${authCookie}`]) + .expect(204) + const cookieInfo = parseCookie(res.header['set-cookie'][0] as string) + expect(cookieInfo[authCookieName]).toBeDefined() + expect(cookieInfo.options).toMatchObject({ Path: '/', HttpOnly: true }) + const expiresIn = + new Date(cookieInfo.options.Expires).getTime() - Date.now() + expect(expiresIn).toBeLessThan(0) // Expires is in the past + }) }) }) describe('/login', () => { From 6e815031166f02ed08e37e6c5297dc77ef0fd950 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 30 Aug 2021 00:06:40 +0200 Subject: [PATCH 06/65] fix: update depdendencies --- package.json | 16 +-- yarn.lock | 336 +++++++++++++++++++++++++-------------------------- 2 files changed, 174 insertions(+), 178 deletions(-) diff --git a/package.json b/package.json index 25ecd80c8..af44d5575 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,11 @@ "license": "(ISC OR GPL-3.0)", "dependencies": { "@graphql-tools/utils": "8.2.2", - "@sinclair/typebox": "0.20.0", + "@sinclair/typebox": "0.20.5", "ajv": "8.6.2", "ajv-formats": "2.1.1", - "apollo-server": "3.0.0", - "apollo-server-express": "3.0.0", + "apollo-server": "3.3.0", + "apollo-server-express": "3.3.0", "bcrypt": "5.0.1", "body-parser": "1.19.0", "compression": "1.7.4", @@ -44,10 +44,10 @@ "graphql-import-node": "0.0.4", "graphql-scalars": "1.10.0", "lodash": "4.17.21", - "nodemon": "2.0.7", + "nodemon": "2.0.12", "passport": "0.4.1", "passport-cookie": "1.0.9", - "pg": "8.6.0", + "pg": "8.7.1", "reflect-metadata": "0.1.13", "sequelize": "6.6.2", "sequelize-typescript": "2.1.0", @@ -68,8 +68,8 @@ "@types/express": "4.17.13", "@types/graphql-depth-limit": "1.1.2", "@types/jest": "27.0.1", - "@types/lodash": "4.14.170", - "@types/node": "14.17.16", + "@types/lodash": "4.14.172", + "@types/node": "16.7.6", "@types/passport": "1.0.7", "@types/supertest": "2.0.11", "@types/uuid": "8.3.1", @@ -81,7 +81,7 @@ "jest-extended": "0.11.5", "npm-run-all": "4.1.5", "prettier": "2.4.0", - "prettier-plugin-organize-imports": "2.2.0", + "prettier-plugin-organize-imports": "2.3.3", "pretty-quick": "3.1.1", "sequelize-cli": "6.2.0", "supertest": "6.1.6", diff --git a/yarn.lock b/yarn.lock index af9af3df0..731e9119b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -724,121 +724,111 @@ tslib "~2.3.0" "@graphql-tools/apollo-engine-loader@^7.0.5": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-7.0.5.tgz#d6710b9d2a8aa5fc62966c34ccc0f92da91cd7d6" - integrity sha512-ABprEk3ziv4af2XCzRVFTsJXKHC9g2TN3nL/hfGt3G7oelZpLUJp+cdZaGn3i8H9NoASuaMOPorIvVC1LsF84Q== + version "7.1.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-7.1.0.tgz#12d58a459da976b496c7632bd41b76f3aceed48e" + integrity sha512-JKK34xQiB1l2sBfi8G5c1HZZkleQbwOlLAkySycKTrU+VMzu5lEjhzYwowIbLLjTthjUHQkRFANHkxvB42t5SQ== dependencies: - "@graphql-tools/utils" "^8.1.1" + "@graphql-tools/utils" "^8.2.0" cross-fetch "^3.1.4" sync-fetch "0.3.0" tslib "~2.3.0" -"@graphql-tools/batch-execute@^8.0.5": - version "8.0.5" - resolved "https://registry.yarnpkg.com/@graphql-tools/batch-execute/-/batch-execute-8.0.5.tgz#a0f8a9ff2c61209974c626faf3dd922a5c68d2b0" - integrity sha512-Zx+zs12BLGNvrQtfESIhitzwIkrWnKyKOkAfcaMNuOLGOO2pDmhwIRzbHj+6Jtq9V1/JTaVkSnm/4ozaCRck5A== +"@graphql-tools/batch-execute@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/batch-execute/-/batch-execute-8.1.0.tgz#fd463bab0e870a662bb00f12d5ce0013b11ae990" + integrity sha512-PPf8SZto4elBtkaV65RldkjvxCuwYV7tLYKH+w6QnsxogfjrtiwijmewtqIlfnpPRnuhmMzmOlhoDyf0I8EwHw== dependencies: - "@graphql-tools/utils" "^8.1.1" + "@graphql-tools/utils" "^8.2.0" dataloader "2.0.0" tslib "~2.3.0" value-or-promise "1.0.10" "@graphql-tools/code-file-loader@^7.0.6": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@graphql-tools/code-file-loader/-/code-file-loader-7.0.7.tgz#ae5d4560da65eed89461f7dc0d56a5c938b0d7a7" - integrity sha512-GCiABoEmYEXo9TSgkLLQS/AxaIfN6efOOWNHGPOvXQ2DkMgg+5uX3hGd1h0nvPIzL4gC61Rr9llwMjovkoJlfw== + version "7.1.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/code-file-loader/-/code-file-loader-7.1.0.tgz#3fd040ce92510a12c361bac85d0d954951e231f5" + integrity sha512-1EVuKGzTDcZoPQAjJYy0Fw2vwLN1ZnWYDlCZLaqml87tCUzJcqcHlQw26SRhDEvVnJC/oCV+mH+2QE55UxqWuA== dependencies: - "@graphql-tools/graphql-tag-pluck" "^7.0.5" - "@graphql-tools/utils" "^8.1.2" + "@graphql-tools/graphql-tag-pluck" "^7.1.0" + "@graphql-tools/utils" "^8.2.0" globby "^11.0.3" tslib "~2.3.0" unixify "^1.0.0" -"@graphql-tools/delegate@^8.1.0", "@graphql-tools/delegate@^8.1.1": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/delegate/-/delegate-8.1.1.tgz#d20e6d81a2900b1c8a69c2c0a3a8a0df2f9030c2" - integrity sha512-Vttd0nfYTqRnRMKLvk8s4cIi9U+OMXGc9CMZAlKkHrBJ6dGXjdSM+4n3p9rfWZc/FtbVk1FnNS4IFyMeKwFuxA== +"@graphql-tools/delegate@^8.1.1", "@graphql-tools/delegate@^8.2.0": + version "8.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/delegate/-/delegate-8.2.1.tgz#750df27be7641836e0cb654b953581de30f2d1a6" + integrity sha512-fvQjSrCJCfchSQlLNHPcj1TwojyV1CPtXmwtSEVKvyp9axokuP37WGyliOWUYCepfwpklklLFUeTEiWlCoxv2Q== dependencies: - "@graphql-tools/batch-execute" "^8.0.5" - "@graphql-tools/schema" "^8.1.2" - "@graphql-tools/utils" "^8.1.2" + "@graphql-tools/batch-execute" "^8.1.0" + "@graphql-tools/schema" "^8.2.0" + "@graphql-tools/utils" "^8.2.0" dataloader "2.0.0" tslib "~2.3.0" value-or-promise "1.0.10" "@graphql-tools/git-loader@^7.0.5": - version "7.0.6" - resolved "https://registry.yarnpkg.com/@graphql-tools/git-loader/-/git-loader-7.0.6.tgz#f66221efbc8b93b2c2889fdf485615aaa0a45f66" - integrity sha512-6YoLqKdf1AiVQ7mJXWUQZI3un0r23JDFhp2ChIcR5uDKCHZkikSMUph6JvjajCLnPryPBGwr10659sRnESujmA== + version "7.1.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/git-loader/-/git-loader-7.1.0.tgz#6d4978752e058b69047bccca1d11c50b1e29b401" + integrity sha512-n6JBKc7Po8aE9/pueH0wO2EXicueuCT3VCo9YcFcCkdEl1tUZwIoIUgNUqgki2eS8u2Z76F2EWZwWBWO2ipXEw== dependencies: - "@graphql-tools/graphql-tag-pluck" "^7.0.5" - "@graphql-tools/utils" "^8.1.2" + "@graphql-tools/graphql-tag-pluck" "^7.1.0" + "@graphql-tools/utils" "^8.2.0" is-glob "4.0.1" micromatch "^4.0.4" tslib "~2.3.0" unixify "^1.0.0" "@graphql-tools/github-loader@^7.0.5": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@graphql-tools/github-loader/-/github-loader-7.0.5.tgz#f08f1c5f4b01e922e7fc24cafb0142a66781bf2f" - integrity sha512-dDoHZa5Fi9JkqjgugASaafGRwXe+sUkTZdCmxf5m+vl1OFy5S/bw3bPML18tzm+e7bu+Vb/sVHSb2Drlo2q3VA== + version "7.1.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/github-loader/-/github-loader-7.1.0.tgz#2309460f3b5c931b406ffc2506f3d7504b6c6c8c" + integrity sha512-RL8bREscN+CO/gP58BE+aA+PXUIwMY7okmrWtrupjRyo1IYv0bfaZXKHRF+qb2gqlJxksM5Q9HevO/MIj6T1eQ== dependencies: - "@graphql-tools/graphql-tag-pluck" "^7.0.5" - "@graphql-tools/utils" "^8.1.1" + "@graphql-tools/graphql-tag-pluck" "^7.1.0" + "@graphql-tools/utils" "^8.2.0" cross-fetch "3.1.4" tslib "~2.3.0" "@graphql-tools/graphql-file-loader@^7.0.1", "@graphql-tools/graphql-file-loader@^7.0.5": - version "7.0.6" - resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-file-loader/-/graphql-file-loader-7.0.6.tgz#602a2013dc926e17542c979b8f9af45187329bcb" - integrity sha512-jndtcNwPUQxEiY/3FKNbAb4dpNXO8tXoDZzrnvv+z/tf27ViiZW0KUBkO4Mw4b0sqaDq+fS4d6BXzVFQQUPolA== + version "7.1.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-file-loader/-/graphql-file-loader-7.1.0.tgz#6249074a7d268a30c81e923ee2bb991ec9a4c7b7" + integrity sha512-VWGetkBuyWrUxSpMnbpzZs2yHWBFeo9nTYjc8+o+xyJKpG/CRfvQrhBRNRFfV/5C0j5/Z1FMfUgsroEAG5H3HQ== dependencies: - "@graphql-tools/import" "^6.2.6" - "@graphql-tools/utils" "^8.1.2" + "@graphql-tools/import" "^6.4.0" + "@graphql-tools/utils" "^8.2.0" globby "^11.0.3" tslib "~2.3.0" unixify "^1.0.0" -"@graphql-tools/graphql-tag-pluck@^7.0.5": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-7.0.5.tgz#b21137c0fc364da283fdf7c7de8973cf183d3dc0" - integrity sha512-/eNFl/4gshnlgNvx1Koaey3036edcv5/Nko+px/DbkfjKedwikRWubURcCaMp/4VE6CyIUibxRrP6TzVByhVrw== +"@graphql-tools/graphql-tag-pluck@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-7.1.0.tgz#1116ef046370723b7d63ee1f66167129a6fcb8c9" + integrity sha512-Y0iRqHKoB0i1RCpskuFKmvnehBcKSu9awEq7ml4/RWSMHRkVlJs8PAFuChyOO6jASC2ttkyfssB6qLJugvc+DQ== dependencies: "@babel/parser" "7.15.3" "@babel/traverse" "7.15.0" "@babel/types" "7.15.0" - "@graphql-tools/utils" "^8.1.1" + "@graphql-tools/utils" "^8.2.0" tslib "~2.3.0" -"@graphql-tools/import@^6.2.6": - version "6.3.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/import/-/import-6.3.1.tgz#731c47ab6c6ac9f7994d75c76b6c2fa127d2d483" - integrity sha512-1szR19JI6WPibjYurMLdadHKZoG9C//8I/FZ0Dt4vJSbrMdVNp8WFxg4QnZrDeMG4MzZc90etsyF5ofKjcC+jw== +"@graphql-tools/import@^6.4.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/import/-/import-6.4.0.tgz#eb2178d6df8d964e7b9d6b1ed75f80d12f9060a7" + integrity sha512-jfE01oPcmc4vzAcYLs6xT7XC4jJWrM1HNtIwc7HyyHTxrC3nf36XrF3txEZ2l20GT53+OWnMgYx1HhauLGdJmA== dependencies: resolve-from "5.0.0" - tslib "~2.2.0" + tslib "~2.3.0" "@graphql-tools/json-file-loader@^7.0.1", "@graphql-tools/json-file-loader@^7.1.2": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@graphql-tools/json-file-loader/-/json-file-loader-7.1.3.tgz#fef87d40cdad2afd61e0303155f2021587a7bcc0" - integrity sha512-Z6B1mwgmmit4BUc44NNSelF67Q7laJ98+QPNaIHBBpEHdCD2ToLPaIo2P+xrzf9BTS+hypXvupbEtxtKTUe9uQ== + version "7.2.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/json-file-loader/-/json-file-loader-7.2.0.tgz#0d5cdc372a5d925a470c7b2269f26dd659eef841" + integrity sha512-YMGjYtjYoalmGHUynypSqxm99fSBOXWqoDeDVYTHAZy970yo61yTi72owEtg7dwB4fQndL2wCoh6ZDS5ruiDDg== dependencies: - "@graphql-tools/utils" "^8.1.2" + "@graphql-tools/utils" "^8.2.0" globby "^11.0.3" tslib "~2.3.0" unixify "^1.0.0" -"@graphql-tools/load@^7.1.0": - version "7.1.9" - resolved "https://registry.yarnpkg.com/@graphql-tools/load/-/load-7.1.9.tgz#6bcff0a2866051ecdf514e6a8f82c2b4c0f5401d" - integrity sha512-4R0JLXRynPgHRWkGvL778XNH3IYX1sXjmEqGy3LPpJ4KR6woCO0us8oXNMAJMjjU4ZcOX6I8Jdj16G7qcHYTLA== - dependencies: - "@graphql-tools/schema" "8.1.2" - "@graphql-tools/utils" "^8.1.2" - p-limit "3.1.0" - tslib "~2.3.0" - -"@graphql-tools/load@^7.3.0": +"@graphql-tools/load@^7.1.0", "@graphql-tools/load@^7.3.0": version "7.3.0" resolved "https://registry.yarnpkg.com/@graphql-tools/load/-/load-7.3.0.tgz#dc4177bb4b7ae537c833a2fcd97ab07b6c789c65" integrity sha512-ZVipT7yzOpf/DJ2sLI3xGwnULVFp/icu7RFEgDo2ZX0WHiS7EjWZ0cegxEm87+WN4fMwpiysRLzWx67VIHwKGA== @@ -857,7 +847,7 @@ "@graphql-tools/utils" "8.0.2" tslib "~2.3.0" -"@graphql-tools/merge@^8.1.0": +"@graphql-tools/merge@^8.0.2", "@graphql-tools/merge@^8.1.0": version "8.1.2" resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.1.2.tgz#50f5763927c51de764d09c5bfd20261671976e24" integrity sha512-kFLd4kKNJXYXnKIhM8q9zgGAtbLmsy3WmGdDxYq3YHBJUogucAxnivQYyRIseUq37KGmSAIWu3pBQ23TKGsGOw== @@ -883,12 +873,12 @@ tslib "~2.0.1" "@graphql-tools/prisma-loader@^7.0.6": - version "7.0.6" - resolved "https://registry.yarnpkg.com/@graphql-tools/prisma-loader/-/prisma-loader-7.0.6.tgz#4b791eacd26d8fbf3783fba48c0239ec6cc5c621" - integrity sha512-HxtlW+15bRd6fDdtJO391+PPVpuJngMN5H6ND3ChNxVvcJzGuG+6r3oi5GNe5BZUgc12N0pEZ0eGIK3+TU6QSg== + version "7.1.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/prisma-loader/-/prisma-loader-7.1.0.tgz#790dd29ad682f6c1fc7ceb3cf48b04cb1064b7f4" + integrity sha512-fehVzximGYuVkbl4mIbXjPz3XlL+7N4BlnXI5QEAif2DJ8fkTpPN7E9PoV80lxWkLDNokFFDHH6qq7hr99JyOg== dependencies: - "@graphql-tools/url-loader" "^7.0.11" - "@graphql-tools/utils" "^8.1.1" + "@graphql-tools/url-loader" "^7.1.0" + "@graphql-tools/utils" "^8.2.0" "@types/js-yaml" "^4.0.0" "@types/json-stable-stringify" "^1.0.32" "@types/jsonwebtoken" "^8.5.0" @@ -917,17 +907,7 @@ relay-compiler "11.0.2" tslib "~2.3.0" -"@graphql-tools/schema@8.1.2", "@graphql-tools/schema@^8.0.0", "@graphql-tools/schema@^8.0.2", "@graphql-tools/schema@^8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.1.2.tgz#913879da1a7889a9488e9b7dc189e7c83eff74be" - integrity sha512-rX2pg42a0w7JLVYT+f/yeEKpnoZL5PpLq68TxC3iZ8slnNBNjfVfvzzOn8Q8Q6Xw3t17KP9QespmJEDfuQe4Rg== - dependencies: - "@graphql-tools/merge" "^8.0.2" - "@graphql-tools/utils" "^8.1.1" - tslib "~2.3.0" - value-or-promise "1.0.10" - -"@graphql-tools/schema@8.2.0": +"@graphql-tools/schema@8.2.0", "@graphql-tools/schema@^8.0.2", "@graphql-tools/schema@^8.2.0": version "8.2.0" resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.2.0.tgz#ae75cbb2df6cee9ed6d89fce56be467ab23758dc" integrity sha512-ufmI5mJQa8NJczzfkh0pUttKvspqDcT5LLakA3jUmOrrE4d4NVj6onZlazdTzF5sAepSNqanFnwhrxZpCAJMKg== @@ -937,6 +917,16 @@ tslib "~2.3.0" value-or-promise "1.0.10" +"@graphql-tools/schema@^8.0.0", "@graphql-tools/schema@^8.1.2": + version "8.1.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.1.2.tgz#913879da1a7889a9488e9b7dc189e7c83eff74be" + integrity sha512-rX2pg42a0w7JLVYT+f/yeEKpnoZL5PpLq68TxC3iZ8slnNBNjfVfvzzOn8Q8Q6Xw3t17KP9QespmJEDfuQe4Rg== + dependencies: + "@graphql-tools/merge" "^8.0.2" + "@graphql-tools/utils" "^8.1.1" + tslib "~2.3.0" + value-or-promise "1.0.10" + "@graphql-tools/url-loader@^7.0.11", "@graphql-tools/url-loader@^7.0.3": version "7.0.12" resolved "https://registry.yarnpkg.com/@graphql-tools/url-loader/-/url-loader-7.0.12.tgz#846e358d7e8b6a24b501778f42422007d175b134" @@ -965,6 +955,34 @@ value-or-promise "1.0.10" ws "8.2.0" +"@graphql-tools/url-loader@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/url-loader/-/url-loader-7.1.0.tgz#371fb4bc975364033cab892a344cc9f979d96ac4" + integrity sha512-h0NAcjPBei/Zf69OeFZWUbPXKA2aK5YXyfAZkAc+LP8pnBDBZW+mPr2lOZZJPJ8LMZPPjf5hwVHqXGImduHgmA== + dependencies: + "@ardatan/fetch-event-source" "2.0.2" + "@graphql-tools/delegate" "^8.2.0" + "@graphql-tools/utils" "^8.2.0" + "@graphql-tools/wrap" "^8.1.0" + "@n1ru4l/graphql-live-query" "0.7.1" + "@types/websocket" "1.0.4" + "@types/ws" "^7.4.7" + abort-controller "3.0.0" + cross-fetch "3.1.4" + extract-files "11.0.0" + form-data "4.0.0" + graphql-ws "^5.4.1" + is-promise "4.0.0" + isomorphic-ws "4.0.1" + lodash "4.17.21" + meros "1.1.4" + subscriptions-transport-ws "^0.10.0" + sync-fetch "0.3.0" + tslib "~2.3.0" + valid-url "1.0.9" + value-or-promise "1.0.10" + ws "8.2.1" + "@graphql-tools/utils@8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.0.2.tgz#795a8383cdfdc89855707d62491c576f439f3c51" @@ -972,28 +990,28 @@ dependencies: tslib "~2.3.0" -"@graphql-tools/utils@8.2.2", "@graphql-tools/utils@^8.2.0", "@graphql-tools/utils@^8.2.2": +"@graphql-tools/utils@8.2.2", "@graphql-tools/utils@^8.0.1", "@graphql-tools/utils@^8.1.2", "@graphql-tools/utils@^8.2.0", "@graphql-tools/utils@^8.2.2": version "8.2.2" resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.2.2.tgz#d29420bf1003d2876cb30f373145be432c7f7c4b" integrity sha512-29FFY5U4lpXuBiW9dRvuWnBVwGhWbGLa2leZcAMU/Pz47Cr/QLZGVgpLBV9rt+Gbs7wyIJM7t7EuksPs0RDm3g== dependencies: tslib "~2.3.0" -"@graphql-tools/utils@^8.0.0", "@graphql-tools/utils@^8.0.1", "@graphql-tools/utils@^8.1.1", "@graphql-tools/utils@^8.1.2": +"@graphql-tools/utils@^8.0.0", "@graphql-tools/utils@^8.1.1": version "8.1.2" resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.1.2.tgz#a376259fafbca7532fda657e3abeec23b545e5d3" integrity sha512-3G+NIBR5mHjPm78jAD0l07JRE0XH+lr9m7yL/wl69jAzK0Jr/H+/Ok4ljEolI70iglz+ZhIShVPAwyesF6rnFg== dependencies: tslib "~2.3.0" -"@graphql-tools/wrap@^8.0.13": - version "8.0.13" - resolved "https://registry.yarnpkg.com/@graphql-tools/wrap/-/wrap-8.0.13.tgz#17a4096efbc64e15b27a74135d920c8bd3e5385a" - integrity sha512-GTkHbN+Zgs+D7bFniFx3/YqNIDv8ET17prM7FewmU8LNRc2P48y6d4/dkQLcwQmryy1TZF87es6yA9FMNfQtWg== +"@graphql-tools/wrap@^8.0.13", "@graphql-tools/wrap@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/wrap/-/wrap-8.1.0.tgz#2e618c78de910725130e5231666ca69a75f6cf99" + integrity sha512-WLT/bFewOIY8KJMzgOJSM/02fXJSFktFvI+JRu39wDH+hwFy1y7pFC0Bs1TP8B/hAEJ+t9+7JspX0LQWAUFjDg== dependencies: - "@graphql-tools/delegate" "^8.1.0" - "@graphql-tools/schema" "^8.1.2" - "@graphql-tools/utils" "^8.1.1" + "@graphql-tools/delegate" "^8.2.0" + "@graphql-tools/schema" "^8.2.0" + "@graphql-tools/utils" "^8.2.0" tslib "~2.3.0" value-or-promise "1.0.10" @@ -1212,7 +1230,7 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@jest/types@^27.1.1": +"@jest/types@^27.1.0", "@jest/types@^27.1.1": version "27.1.1" resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.1.1.tgz#77a3fc014f906c65752d12123a0134359707c0ad" integrity sha512-yqJPDDseb0mXgKqmNqypCsb85C22K1aY5+LUxh7syIM9n/b0AsaltxNy+o6tt29VcfGDpYEve175bm3uOhcehA== @@ -1329,10 +1347,10 @@ dependencies: any-observable "^0.3.0" -"@sinclair/typebox@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.20.0.tgz#f0e620ea3a4f82b2fbbd16d89a92fbe5c869ba90" - integrity sha512-qFKbghRt4sutWEQYchceiErGVhunbCoCssCcOjLXN6vtMMyaycCQGGEgd1kGyueucONnnO+nNYUXLXATU35j7w== +"@sinclair/typebox@0.20.5": + version "0.20.5" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.20.5.tgz#d5828bbb8237876d7937dd229e3dac71d1220771" + integrity sha512-WNiVFcS1rdz5KyEutpl3Wmp/AwSQHBUFTyJz7KqMLkpLhOXCj1dnvMytBM6uMS5OTwhwwq877T7EC4vDGrX5Eg== "@sindresorhus/is@^0.14.0": version "0.14.0" @@ -1466,25 +1484,11 @@ resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8" integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog== -"@types/cors@2.8.11": - version "2.8.11" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.11.tgz#0bbd036cc6c8c63e0e5d64115fa9692eabb7eaa3" - integrity sha512-64aQQZXPSo1fdLEE/utClOFVUqDUjyh5j3JorcCTlYQm4r5wsfggx6yhSY6hNudJLkbmIt+pO6xWyCnM0EQgPw== - "@types/cors@2.8.12": version "2.8.12" resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== -"@types/express-serve-static-core@4.17.23": - version "4.17.23" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.23.tgz#721c371fc53fe7b3ea40d8977b209b90cb275f58" - integrity sha512-WYqTtTPTJn9kXMdnAH5HPPb7ctXvBpP4PfuOb8MV4OHPQWHhDZixGlhgR159lJPpKm23WOdoCkt2//cCEaOJkw== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/express-serve-static-core@4.17.24", "@types/express-serve-static-core@^4.17.18": version "4.17.24" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz#ea41f93bf7e0d59cd5a76665068ed6aab6815c07" @@ -1570,10 +1574,10 @@ dependencies: "@types/node" "*" -"@types/lodash@4.14.170": - version "4.14.170" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6" - integrity sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q== +"@types/lodash@4.14.172": + version "4.14.172" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a" + integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw== "@types/long@^4.0.0": version "4.0.1" @@ -1590,16 +1594,11 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== -"@types/node@*": +"@types/node@*", "@types/node@16.7.6": version "16.7.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.6.tgz#8666478db8095aa66e25b7e469f3e7b53ea2855e" integrity sha512-VESVNFoa/ahYA62xnLBjo5ur6gPsgEE5cNRy8SrdnkZ2nwJSW0kJ4ufbFr2zuU9ALtHM8juY53VcRoTA7htXSg== -"@types/node@14.17.16": - version "14.17.16" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.16.tgz#2b9252bd4fdf0393696190cd9550901dd967c777" - integrity sha512-WiFf2izl01P1CpeY8WqFAeKWwByMueBEkND38EcN8N68qb0aDG3oIS1P5MhAX5kUdr469qRyqsY/MjanLjsFbQ== - "@types/node@^10.1.0": version "10.17.60" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" @@ -1907,7 +1906,7 @@ apollo-server-caching@^3.1.0: dependencies: lru-cache "^6.0.0" -apollo-server-core@^3.0.0, apollo-server-core@^3.3.0: +apollo-server-core@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-3.3.0.tgz#f973c6f755884f8e17452cb9022672ae6f0ed9e7" integrity sha512-KmkzKVG3yjybouDyUX6Melv39u1EOFipvAKP17IlPis/TjVbubJmb6hkE0am/g2RipyhRvlpxAjHqPaCTXR1dQ== @@ -1946,24 +1945,7 @@ apollo-server-errors@^3.1.0: resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-3.1.0.tgz#0b890dc7ae36a1f0ca4841d353e8d1c3c6524ee2" integrity sha512-bUmobPEvtcBFt+OVHYqD390gacX/Cm5s5OI5gNZho8mYKAA6OjgnRlkm/Lti6NzniXVxEQyD5vjkC6Ox30mGFg== -apollo-server-express@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-3.0.0.tgz#5692cb74f5ab32ea378453842e82a368ba7ad20b" - integrity sha512-5xLr7B1Ky+OEk1aicBv3cU/ynVSsAij92sZsJD2MjokR0+VHM40aoi9++ZFAaXgUh2/amsWBjPC5dFN+1VMdJQ== - dependencies: - "@types/accepts" "^1.3.5" - "@types/body-parser" "1.19.1" - "@types/cors" "2.8.11" - "@types/express" "4.17.13" - "@types/express-serve-static-core" "4.17.23" - accepts "^1.3.5" - apollo-server-core "^3.0.0" - apollo-server-types "^3.0.0" - body-parser "^1.19.0" - cors "^2.8.5" - parseurl "^1.3.3" - -apollo-server-express@^3.0.0: +apollo-server-express@3.3.0, apollo-server-express@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-3.3.0.tgz#23ec8b102a4758548c1416fb4770334e814ffb12" integrity sha512-qJedh77IxbfT+HpYsDraC2CGdy08wiWTwoKYXjRK4S/DHbe94A4957/1blw4boYO4n44xRKQd1k6zxiixCp+XQ== @@ -1987,7 +1969,7 @@ apollo-server-plugin-base@^3.2.0: dependencies: apollo-server-types "^3.2.0" -apollo-server-types@^3.0.0, apollo-server-types@^3.2.0: +apollo-server-types@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-3.2.0.tgz#6243b34d35fbb09ded2cc84bf7e5f59968ccfa21" integrity sha512-Fh7QP84ufDZHbLzoLyyxyzznlW8cpgEZYYkGsS1i36zY4VaAt5OUOp1f+FxWdLGehq0Arwb6D1W7y712IoZ/JQ== @@ -1996,15 +1978,14 @@ apollo-server-types@^3.0.0, apollo-server-types@^3.2.0: apollo-server-caching "^3.1.0" apollo-server-env "^4.0.3" -apollo-server@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-3.0.0.tgz#0485f3fae5ca3d8fbcd4f1235462472a7b9d7d90" - integrity sha512-ugNy3kYs7sAAX4nhcWBeCH9V9lQAKefj8S/sNYCVBSdqagpj7fwHT5wpnF1BLAiY1Z1O1qulnK3WBEdttSnKUA== +apollo-server@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-3.3.0.tgz#4c0c9d20185d89cbe83aec2c797564b5b5968ecb" + integrity sha512-vJE+lIOQMgno3IaJvwo66S53m0CU4fq7qAgnOCubitIGNKEHiUL6yXKL0rMGfOPZqeed2S2QWQrOsCGQDIudMw== dependencies: - apollo-server-core "^3.0.0" - apollo-server-express "^3.0.0" + apollo-server-core "^3.3.0" + apollo-server-express "^3.3.0" express "^4.17.1" - stoppable "^1.1.0" aproba@^1.0.3: version "1.2.0" @@ -3590,9 +3571,9 @@ fast-safe-stringify@^2.0.7: integrity sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag== fastq@^1.6.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.12.0.tgz#ed7b6ab5d62393fb2cc591c853652a5c318bf794" - integrity sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg== + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== dependencies: reusify "^1.0.4" @@ -3955,10 +3936,10 @@ graphql-tag@2.12.5, graphql-tag@^2.11.0: dependencies: tslib "^2.1.0" -graphql-ws@^5.0.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.4.1.tgz#76fb4c39dfa44a961546995b6bb5320726ff5f71" - integrity sha512-maBduPneZOFYb3otLgc9/U+fU3f9MVXLKg6lVVLqA9BDQQ6YR7YhQO21Rf9+44IRBi4l8m35lynFMFQTzN3eAQ== +graphql-ws@^5.0.0, graphql-ws@^5.4.1: + version "5.5.0" + resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.5.0.tgz#79f10248d23d104369eaef93acb9f887276a2c42" + integrity sha512-WQepPMGQQoqS2VsrI2I3RMLCVz3CW4/6ZqGV6ABDOwH4R62DzjxwMlwZbj6vhSI/7IM3/C911yITwgs77iO/hw== graphql@15.5.1: version "15.5.1" @@ -5910,10 +5891,10 @@ node-releases@^1.1.75: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.75.tgz#6dd8c876b9897a1b8e5a02de26afa79bb54ebbfe" integrity sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw== -nodemon@2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.7.tgz#6f030a0a0ebe3ea1ba2a38f71bf9bab4841ced32" - integrity sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA== +nodemon@2.0.12: + version "2.0.12" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.12.tgz#5dae4e162b617b91f1873b3bfea215dd71e144d5" + integrity sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA== dependencies: chokidar "^3.2.2" debug "^3.2.6" @@ -6342,7 +6323,7 @@ pg-int8@1.0.1: resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== -pg-pool@^3.3.0: +pg-pool@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.4.1.tgz#0e71ce2c67b442a5e862a9c182172c37eda71e9c" integrity sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ== @@ -6363,15 +6344,15 @@ pg-types@^2.1.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" -pg@8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.6.0.tgz#e222296b0b079b280cce106ea991703335487db2" - integrity sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ== +pg@8.7.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.7.1.tgz#9ea9d1ec225980c36f94e181d009ab9f4ce4c471" + integrity sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA== dependencies: buffer-writer "2.0.0" packet-reader "1.0.0" pg-connection-string "^2.5.0" - pg-pool "^3.3.0" + pg-pool "^3.4.1" pg-protocol "^1.5.0" pg-types "^2.1.0" pgpass "1.x" @@ -6449,10 +6430,10 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier-plugin-organize-imports@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.2.0.tgz#dcdd5222416a90bd819734f131fea4a22b1c613a" - integrity sha512-2WM3moc/cAPCCsSneYhaL4+mMws0Bypbxz+98wuRyaA7GMokhOECVkQCG7l2hcH+9/4d5NsgZs9yktfwDy4r7A== +prettier-plugin-organize-imports@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.3.3.tgz#6877bcf8ca02569198d8f419c954a0d90a95de15" + integrity sha512-PBOwQ8vEIB2b7B3gCuBG7D+dqsr1fsTR4TSAjNacRVdHJrD0yzgz9grOLPSyfwJm+NUTZLyWeHoZ+1mHaUrk+g== prettier@2.4.0: version "2.4.0" @@ -6487,6 +6468,16 @@ pretty-format@^27.0.0: ansi-styles "^5.0.0" react-is "^17.0.1" +pretty-format@^27.1.0: + version "27.1.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.1.1.tgz#cbaf9ec6cd7cfc3141478b6f6293c0ccdbe968e0" + integrity sha512-zdBi/xlstKJL42UH7goQti5Hip/B415w1Mfj+WWWYMBylAYtKESnXGUtVVcMVid9ReVjypCotUV6CEevYPHv2g== + dependencies: + "@jest/types" "^27.1.1" + ansi-regex "^5.0.0" + ansi-styles "^5.0.0" + react-is "^17.0.1" + pretty-format@^27.2.0: version "27.2.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.2.0.tgz#ee37a94ce2a79765791a8649ae374d468c18ef19" @@ -7286,11 +7277,6 @@ static-extend@^0.1.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -stoppable@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b" - integrity sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw== - string-env-interpolation@1.0.1, string-env-interpolation@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz#ad4397ae4ac53fe6c91d1402ad6f6a52862c7152" @@ -8172,7 +8158,17 @@ ws@8.2.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.0.tgz#0b738cd484bfc9303421914b11bb4011e07615bb" integrity sha512-uYhVJ/m9oXwEI04iIVmgLmugh2qrZihkywG9y5FfZV2ATeLIzHf93qs+tUNqlttbQK957/VX3mtwAS+UfIwA4g== -"ws@^5.2.0 || ^6.0.0 || ^7.0.0", ws@^7.4.6: +ws@8.2.1: + version "8.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.1.tgz#bdd92b3c56fdb47d2379b5ae534281922cc5bd12" + integrity sha512-XkgWpJU3sHU7gX8f13NqTn6KQ85bd1WU7noBHTT8fSohx7OS1TPY8k+cyRPCzFkia7C4mM229yeHr1qK9sM4JQ== + +"ws@^5.2.0 || ^6.0.0 || ^7.0.0": + version "7.5.5" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" + integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== + +ws@^7.4.6: version "7.5.4" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.4.tgz#56bfa20b167427e138a7795de68d134fe92e21f9" integrity sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg== From b4e8a1f6b702067354e4ab5c9c00eecd45422c40 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 30 Aug 2021 00:20:17 +0200 Subject: [PATCH 07/65] fix: switch to Node.js 16 --- .github/workflows/ci.yml | 2 +- .github/workflows/frontend-unit-tests.yml | 4 ++++ .node-version | 1 - .tool-versions | 2 +- README.md | 2 +- package.json | 2 +- script/dev_setup | 2 +- 7 files changed, 9 insertions(+), 6 deletions(-) delete mode 100644 .node-version diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1dc57bd3..b2679d430 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v2 with: - node-version: 14.15.5 + node-version: '16.x' # Cache node_modules between builds based on the yarn.lock file's hash. - uses: actions/cache@v2 diff --git a/.github/workflows/frontend-unit-tests.yml b/.github/workflows/frontend-unit-tests.yml index e92e8bc4e..76fd0768d 100644 --- a/.github/workflows/frontend-unit-tests.yml +++ b/.github/workflows/frontend-unit-tests.yml @@ -12,6 +12,10 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Install Node.js + uses: actions/setup-node@v2 + with: + node-version: '16.x' - name: Install modules run: yarn --frozen-lockfile - name: Run codegen diff --git a/.node-version b/.node-version deleted file mode 100644 index 5595ae1aa..000000000 --- a/.node-version +++ /dev/null @@ -1 +0,0 @@ -14.17.6 diff --git a/.tool-versions b/.tool-versions index 1f6451a60..f3134c2d4 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 14.15.5 +nodejs 16 diff --git a/README.md b/README.md index e256aa4eb..f04b47231 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ If the pull request does not require additional changes, the reviewer should mer ## Dev setup -Install node.js v14: +Install node.js v16: - Install with [nodenv](https://github.com/nodenv/nodenv) - or [nvm](https://github.com/nvm-sh/nvm) diff --git a/package.json b/package.json index af44d5575..1006c0c69 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "migrate-db": "npx sequelize-cli db:migrate" }, "engines": { - "node": ">=14.0.0", + "node": ">=16.0.0", "yarn": ">= 1.22.5" }, "keywords": [], diff --git a/script/dev_setup b/script/dev_setup index 81f020835..6924a39fa 100755 --- a/script/dev_setup +++ b/script/dev_setup @@ -2,7 +2,7 @@ set -eu -REQUIRED_NODE_VERSION=v14 +REQUIRED_NODE_VERSION=v16 _current_os=$(uname) From 8134299438fa34b8796ddebf95a391834c234a63 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 30 Aug 2021 23:03:54 +0200 Subject: [PATCH 08/65] fix: use email instead of username --- db/seeders/20210302050835-demo-groups.js | 2 +- docs/authentication.md | 4 +-- frontend/src/hooks/useAuth.ts | 9 ++++-- frontend/src/pages/PublicHome.tsx | 36 ++++++++++++------------ src/authenticateRequest.ts | 2 +- src/models/user_account.ts | 4 +-- src/routes/login.ts | 6 ++-- src/routes/me.ts | 2 +- src/routes/register.ts | 10 +++---- src/testServer.ts | 4 +-- src/tests/authentication.test.ts | 14 ++++----- src/tests/groups_api.test.ts | 19 +++++++------ src/tests/helpers/index.ts | 4 +-- src/tests/line_items_api.test.ts | 2 +- src/tests/offers_api.test.ts | 2 +- src/tests/pallets_api.test.ts | 2 +- src/tests/shipment_exports_api.test.ts | 2 +- 17 files changed, 65 insertions(+), 59 deletions(-) diff --git a/db/seeders/20210302050835-demo-groups.js b/db/seeders/20210302050835-demo-groups.js index 1d98d385a..33bd52158 100644 --- a/db/seeders/20210302050835-demo-groups.js +++ b/db/seeders/20210302050835-demo-groups.js @@ -6,7 +6,7 @@ module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.bulkInsert('UserAccounts', [ { - username: 'seeded-account-id', + email: 'seeded-account-id@example.com', createdAt: new Date(), updatedAt: new Date(), }, diff --git a/docs/authentication.md b/docs/authentication.md index 4446cf0ab..0de517c08 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -2,11 +2,11 @@ The backend authenticates requests using signed cookies which contains user's id so that it does not have to be fetched for every request. -Cookies are sent [`secure` and `HttpOnly`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) when users register their account, or when they log in using username and password. +Cookies are sent [`secure` and `HttpOnly`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) when users register their account, or when they log in using email and password. Cookies expire after 30 minutes and the client is responsible for renewing cookies by calling the `GET /me/cookie` endpoint before they expire. -When renewing cookies, the server will re-check if the user still exists and if they haven't changed their password. For this a hash of the user's password hash, email, username, and id will be generated and included in the cookie. If any of these properties changes, the cookie cannot be renewed and the user has to log-in again. +When renewing cookies, the server will re-check if the user still exists and if they haven't changed their password. For this a hash of the user's password hash, email, and id will be generated and included in the cookie. If any of these properties changes, the cookie cannot be renewed and the user has to log-in again. ## Admin permissions diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts index 60acb771b..5c6435ed4 100644 --- a/frontend/src/hooks/useAuth.ts +++ b/frontend/src/hooks/useAuth.ts @@ -9,17 +9,20 @@ export const useAuth = () => { isLoading, isAuthenticated, logout: () => { - // FIXME: clear cookies and reload + // Delete cookies (since the auth cookie is httpOnly we cannot access + // it using JavaScript, e.g. cookie.delete() will not work). + // Therefore we ask the server to send us an invalid cookie. fetch(`${SERVER_URL}/me/cookie`, { method: 'DELETE' }).then(() => { setIsAuthenticated(false) + // Reload the page (no need to handle logout in the app) document.location.reload() }) }, - login: ({ username, password }: { username: string; password: string }) => { + login: ({ email, password }: { email: string; password: string }) => { setIsLoading(true) fetch(`${SERVER_URL}/me/login`, { method: 'POST', - body: JSON.stringify({ username, password }), + body: JSON.stringify({ email, password }), }) .then(() => { setIsAuthenticated(true) diff --git a/frontend/src/pages/PublicHome.tsx b/frontend/src/pages/PublicHome.tsx index fc6ac1e31..7210491be 100644 --- a/frontend/src/pages/PublicHome.tsx +++ b/frontend/src/pages/PublicHome.tsx @@ -5,18 +5,18 @@ import { useAuth } from '../hooks/useAuth' const SERVER_URL = process.env.REACT_APP_SERVER_URL +const emailRegEx = /.+@.+\..+/ +const passwordRegEx = + /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/ + const PublicHomePage: FunctionComponent = () => { const { login } = useAuth() const [showRegisterForm, setShowRegisterForm] = useState(false) - const [username, setUsername] = useState('') + const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [password2, setPassword2] = useState('') - const loginFormValid = - username.length > 0 && - /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/.test( - password, - ) + const loginFormValid = emailRegEx.test(email) && passwordRegEx.test(password) const registerFormValid = loginFormValid && password === password2 @@ -36,11 +36,11 @@ const PublicHomePage: FunctionComponent = () => { {showLoginForm && (
setUsername(value)} + label="email" + type="email" + name="email" + value={email} + onChange={({ target: { value } }) => setEmail(value)} /> { + + {isConfirmed && ( + + )} + + + + ) +} + +export default ConfirmEmailWithTokenPage diff --git a/frontend/src/pages/PublicHome.tsx b/frontend/src/pages/PublicHome.tsx index 7210491be..17163eeeb 100644 --- a/frontend/src/pages/PublicHome.tsx +++ b/frontend/src/pages/PublicHome.tsx @@ -1,24 +1,21 @@ import { FunctionComponent, useState } from 'react' +import { Redirect } from 'react-router-dom' import DistributeAidWordmark from '../components/branding/DistributeAidWordmark' import TextField from '../components/forms/TextField' -import { useAuth } from '../hooks/useAuth' - -const SERVER_URL = process.env.REACT_APP_SERVER_URL - -const emailRegEx = /.+@.+\..+/ -const passwordRegEx = - /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/ +import { emailRegEx, passwordRegEx, useAuth } from '../hooks/useAuth' const PublicHomePage: FunctionComponent = () => { - const { login } = useAuth() + const { login, register, isRegistered } = useAuth() const [showRegisterForm, setShowRegisterForm] = useState(false) + const [name, setName] = useState('') const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [password2, setPassword2] = useState('') const loginFormValid = emailRegEx.test(email) && passwordRegEx.test(password) - const registerFormValid = loginFormValid && password === password2 + const registerFormValid = + loginFormValid && password === password2 && name.trim().length > 0 const showLoginForm = !showRegisterForm return ( @@ -39,6 +36,7 @@ const PublicHomePage: FunctionComponent = () => { label="email" type="email" name="email" + autoComplete="email" value={email} onChange={({ target: { value } }) => setEmail(value)} /> @@ -46,6 +44,7 @@ const PublicHomePage: FunctionComponent = () => { label="password" type="password" name="password" + autoComplete="password" value={password} onChange={({ target: { value } }) => setPassword(value)} /> @@ -66,27 +65,38 @@ const PublicHomePage: FunctionComponent = () => { )} - {showRegisterForm && ( + {showRegisterForm && !isRegistered && (
setName(value)} + /> + setEmail(value)} /> setPassword(value)} /> setPassword2(value)} @@ -95,10 +105,7 @@ const PublicHomePage: FunctionComponent = () => { className="bg-navy-800 text-white text-lg px-4 py-2 rounded-sm w-full hover:bg-opacity-90" type="button" onClick={() => { - fetch(`${SERVER_URL}/register`, { - method: 'POST', - body: JSON.stringify({ email, password }), - }) + register({ name, email, password }) }} disabled={!registerFormValid} > @@ -113,6 +120,14 @@ const PublicHomePage: FunctionComponent = () => { )} + {isRegistered && ( + + )} diff --git a/frontend/src/utils/routes.ts b/frontend/src/utils/routes.ts index f9a21f304..2b21ec2c6 100644 --- a/frontend/src/utils/routes.ts +++ b/frontend/src/utils/routes.ts @@ -17,6 +17,7 @@ const ROUTES = { SHIPMENT_EDIT: '/shipment/:shipmentId/edit', KITCHEN_SINK: '/kitchen-sink', FORM_DEMO: '/form-demo', + CONFIRM_EMAIL_WITH_TOKEN: '/register/confirm', } export function groupViewRoute(groupId: number | string) { From c26dd5e9ab360d3c850332f2bbc00675b5e78ea7 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 6 Sep 2021 00:43:09 +0200 Subject: [PATCH 22/65] fix: use context in hook for shared state --- frontend/src/AppRoot.tsx | 12 ++++- .../src/hooks/{useAuth.ts => useAuth.tsx} | 49 +++++++++++++------ 2 files changed, 44 insertions(+), 17 deletions(-) rename frontend/src/hooks/{useAuth.ts => useAuth.tsx} (68%) diff --git a/frontend/src/AppRoot.tsx b/frontend/src/AppRoot.tsx index 64c8a5874..e885d6ef8 100644 --- a/frontend/src/AppRoot.tsx +++ b/frontend/src/AppRoot.tsx @@ -1,7 +1,7 @@ import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' import PrivateRoute from './components/PrivateRoute' import { UserProfileProvider } from './components/UserProfileContext' -import { useAuth } from './hooks/useAuth' +import { AuthProvider, useAuth } from './hooks/useAuth' import AdminPage from './pages/AdminPage' import ConfirmEmailWithTokenPage from './pages/ConfirmEmailWithTokenPage' import ApolloDemoPage from './pages/demo/ApolloDemo' @@ -24,7 +24,7 @@ import ROUTES from './utils/routes' const isDev = process.env.NODE_ENV === 'development' -const AppRoot = () => { +const App = () => { const { isLoading, isAuthenticated } = useAuth() return ( @@ -92,4 +92,12 @@ const AppRoot = () => { ) } +const AppRoot = () => { + return ( + + + + ) +} + export default AppRoot diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.tsx similarity index 68% rename from frontend/src/hooks/useAuth.ts rename to frontend/src/hooks/useAuth.tsx index b868d28d7..9d4871143 100644 --- a/frontend/src/hooks/useAuth.ts +++ b/frontend/src/hooks/useAuth.tsx @@ -1,4 +1,29 @@ -import { useState } from 'react' +import { createContext, useContext, useState } from 'react' +import { PropsWithChildren } from 'react-router/node_modules/@types/react' + +type AuthInfo = { + isLoading: boolean + isAuthenticated: boolean + isRegistered: boolean + isConfirmed: boolean + logout: () => void + login: (_: { email: string; password: string }) => void + register: (_: { name: string; email: string; password: string }) => void + confirm: (_: { email: string; token: string }) => void +} + +export const AuthContext = createContext({ + isLoading: false, + isAuthenticated: false, + isRegistered: false, + isConfirmed: false, + logout: () => undefined, + login: () => undefined, + register: () => undefined, + confirm: () => undefined, +}) + +export const useAuth = () => useContext(AuthContext) const SERVER_URL = process.env.REACT_APP_SERVER_URL @@ -11,12 +36,13 @@ export const emailRegEx = /.+@.+\..+/ export const passwordRegEx = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/ -export const useAuth = () => { +export const AuthProvider = ({ children }: PropsWithChildren) => { const [isLoading, setIsLoading] = useState(false) const [isAuthenticated, setIsAuthenticated] = useState(false) const [isRegistered, setIsRegistered] = useState(false) const [isConfirmed, setIsConfirmed] = useState(false) - return { + + const auth: AuthInfo = { isLoading, isAuthenticated, isRegistered, @@ -34,7 +60,7 @@ export const useAuth = () => { document.location.reload() }) }, - login: ({ email, password }: { email: string; password: string }) => { + login: ({ email, password }) => { setIsLoading(true) fetch(`${SERVER_URL}/login`, { method: 'POST', @@ -45,22 +71,13 @@ export const useAuth = () => { .then(() => { setIsAuthenticated(true) setIsLoading(false) - console.log('authenticated') }) .catch((err) => { console.error(err) setIsLoading(false) }) }, - register: ({ - name, - email, - password, - }: { - name: string - email: string - password: string - }) => { + register: ({ name, email, password }) => { setIsLoading(true) fetch(`${SERVER_URL}/register`, { method: 'POST', @@ -77,7 +94,7 @@ export const useAuth = () => { setIsLoading(false) }) }, - confirm: ({ email, token }: { email: string; token: string }) => { + confirm: ({ email, token }) => { setIsLoading(true) fetch(`${SERVER_URL}/register/confirm`, { method: 'POST', @@ -95,4 +112,6 @@ export const useAuth = () => { }) }, } + + return {children} } From 618003d3efe99d3963bd07c46511f59b2232add4 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 6 Sep 2021 00:43:25 +0200 Subject: [PATCH 23/65] fix: show which server is used for emails sending --- src/mailer/smtp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mailer/smtp.ts b/src/mailer/smtp.ts index 0eae2c7b5..8cacf25ed 100644 --- a/src/mailer/smtp.ts +++ b/src/mailer/smtp.ts @@ -41,7 +41,7 @@ export const smtpMailer = (omnibus: EventEmitter): void => { console.error(`[email] Sending of emails DISABLED!`) return } - console.debug(`[email] Sending of emails ENABLED!`) + console.debug(`[email] Sending of emails ENABLED via ${host}!`) omnibus.on( 'user_registered', async (user: UserAccount, token: VerificationToken) => { From 96991437037825d98b403e4be9b881eac92fa61d Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 6 Sep 2021 00:44:39 +0200 Subject: [PATCH 24/65] fix: re-add ApolloProvider --- frontend/src/AppRoot.tsx | 122 ++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/frontend/src/AppRoot.tsx b/frontend/src/AppRoot.tsx index e885d6ef8..4f26f0832 100644 --- a/frontend/src/AppRoot.tsx +++ b/frontend/src/AppRoot.tsx @@ -1,4 +1,6 @@ +import { ApolloProvider } from '@apollo/client' import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' +import { apolloClient } from './apolloClient' import PrivateRoute from './components/PrivateRoute' import { UserProfileProvider } from './components/UserProfileContext' import { AuthProvider, useAuth } from './hooks/useAuth' @@ -28,67 +30,69 @@ const App = () => { const { isLoading, isAuthenticated } = useAuth() return ( - - - - {isDev && ( - - + + + + + {isDev && ( + + + + )} + {isLoading && ( + + + + )} + + {isAuthenticated ? : } - )} - {isLoading && ( - - + + - )} - - {isAuthenticated ? : } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) } From bed510ad9b8fb361dfd5059ee71a25dda3af9cf9 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 6 Sep 2021 08:53:38 +0200 Subject: [PATCH 25/65] fix: use token instead of code --- src/routes/password/new.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/password/new.ts b/src/routes/password/new.ts index 9b0683129..5da21b167 100644 --- a/src/routes/password/new.ts +++ b/src/routes/password/new.ts @@ -12,7 +12,7 @@ const newPasswordUsingTokenInput = Type.Object( { email: emailInput, newPassword: passwordInput, - code: Type.String({ pattern: '^[0-9]{6}$' }), + token: Type.String({ pattern: '^[0-9]{6}$' }), }, { additionalProperties: false }, ) @@ -39,7 +39,7 @@ const setNewPasswordUsingTokenAndEmail = const token = await VerificationToken.findByUserAccountAndToken( user, - valid.value.code, + valid.value.token, ) if (token === undefined) return response.status(401).end() From 4ef293c51e524801782898be549f4c8f3a5ff629 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 6 Sep 2021 08:59:52 +0200 Subject: [PATCH 26/65] fix(db): add migration for ShipmentReceivingHub and ShipmentSendingHub table --- db/migrations/000-initial.ts | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/db/migrations/000-initial.ts b/db/migrations/000-initial.ts index 53ad2f102..69d7e940e 100644 --- a/db/migrations/000-initial.ts +++ b/db/migrations/000-initial.ts @@ -201,6 +201,48 @@ export const up = async (queryInterface: QueryInterface) => { }, ...autoTimestampFields, }) + // ShipmentReceivingHub + await queryInterface.createTable(`ShipmentReceivingHubs`, { + id, + shipmentId: { + type: DataTypes.INTEGER, + references: { model: `Shipments`, key: 'id' }, + allowNull: false, + }, + hubId: { + type: DataTypes.INTEGER, + references: { model: `Groups`, key: 'id' }, + allowNull: false, + }, + ...autoTimestampFields, + }) + await queryInterface.addIndex(`ShipmentReceivingHubs`, ['shipmentId'], { + unique: false, + }) + await queryInterface.addIndex(`ShipmentReceivingHubs`, ['hubId'], { + unique: false, + }) + // ShipmentSendingHub + await queryInterface.createTable(`ShipmentSendingHubs`, { + id, + shipmentId: { + type: DataTypes.INTEGER, + references: { model: `Shipments`, key: 'id' }, + allowNull: false, + }, + hubId: { + type: DataTypes.INTEGER, + references: { model: `Groups`, key: 'id' }, + allowNull: false, + }, + ...autoTimestampFields, + }) + await queryInterface.addIndex(`ShipmentSendingHubs`, ['shipmentId'], { + unique: false, + }) + await queryInterface.addIndex(`ShipmentSendingHubs`, ['hubId'], { + unique: false, + }) // Offer await queryInterface.createTable(`Offers`, { id, From 7eb975550ec1db50c6f74f9d10b7b5b3774eeac0 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 6 Sep 2021 21:16:37 +0000 Subject: [PATCH 27/65] test(email): add test for appMailer --- src/mailer/console.ts | 9 +++-- src/mailer/nodemailer.ts | 75 +++++++++++++++++++++++++++++++++++++ src/mailer/smtp.ts | 60 ----------------------------- src/server.ts | 11 ++++-- src/tests/appMailer.test.ts | 36 ++++++++++++++++++ 5 files changed, 123 insertions(+), 68 deletions(-) create mode 100644 src/mailer/nodemailer.ts delete mode 100644 src/mailer/smtp.ts create mode 100644 src/tests/appMailer.test.ts diff --git a/src/mailer/console.ts b/src/mailer/console.ts index 2d69b886f..d246fbfbd 100644 --- a/src/mailer/console.ts +++ b/src/mailer/console.ts @@ -2,13 +2,14 @@ import EventEmitter from 'events' import UserAccount from '../models/user_account' import VerificationToken from '../models/verification_token' -export const consoleMailer = (omnibus: EventEmitter): void => { +export const consoleMailer = ( + omnibus: EventEmitter, + debug: (...args: any[]) => void, +): void => { omnibus.on( 'user_registered', (user: UserAccount, token: VerificationToken) => { - console.debug( - `[email] > ${user.email}: confirmation token ${token.token}`, - ) + debug(`${user.email}: confirmation token ${token.token}`) }, ) } diff --git a/src/mailer/nodemailer.ts b/src/mailer/nodemailer.ts new file mode 100644 index 000000000..58f0a800c --- /dev/null +++ b/src/mailer/nodemailer.ts @@ -0,0 +1,75 @@ +import EventEmitter from 'events' +import nodemailer, { Transporter } from 'nodemailer' +import UserAccount from '../models/user_account' +import VerificationToken from '../models/verification_token' + +const host = process.env.SMTP_SERVER +const port = parseInt(process.env.SMTP_PORT ?? '587', 10) +const secure = (process.env.SMTP_SECURE ?? 'false') === 'true' +const user = process.env.SMTP_USER +const pass = process.env.SMTP_PASSWORD +const fromEmail = process.env.SMTP_FROM + +const canSendEmails = + [host, port, secure, user, pass, fromEmail].find((v) => v === undefined) === + undefined + +export const transportFromConfig = ( + debug?: (...args: any[]) => void, +): + | { + transport: Transporter + fromEmail: string + } + | undefined => { + if (canSendEmails) { + debug?.(`Sending of emails ENABLED via ${host}!`) + return { + transport: nodemailer.createTransport({ + host, + port, + secure, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASSWORD, + }, + }), + fromEmail: fromEmail as string, + } + } + console.error(`Sending of emails DISABLED!`) +} + +export const verificationEmail = ( + user: UserAccount, + token: VerificationToken, + fromEmail: string, +) => ({ + from: `"Distribute Aid Shipment Tracker" <${fromEmail}>`, + to: `"${user.name}" <${user.email}>`, + subject: `Verification token: ${token.token}`, + text: `Hei 👋 ${user.name},\n\nPlease use the token ${token.token} to verify your email address.\n\nPlease do not reply to this email.`, +}) + +export const appMailer = ( + omnibus: EventEmitter, + { + transport, + fromEmail, + }: { transport: Transporter; fromEmail: string }, + debug?: (...args: any[]) => void, +): void => { + omnibus.on( + 'user_registered', + async (user: UserAccount, token: VerificationToken) => { + debug?.(`> ${user.email}: confirmation token ${token.token}`) + try { + await transport.sendMail(verificationEmail(user, token, fromEmail)) + debug?.('> message sent') + } catch (error) { + console.error(`Failed to sent email: ${(error as Error).message}`) + console.error(error) + } + }, + ) +} diff --git a/src/mailer/smtp.ts b/src/mailer/smtp.ts deleted file mode 100644 index 8cacf25ed..000000000 --- a/src/mailer/smtp.ts +++ /dev/null @@ -1,60 +0,0 @@ -import EventEmitter from 'events' -import nodemailer, { Transporter } from 'nodemailer' -import SMTPTransport from 'nodemailer/lib/smtp-transport' -import UserAccount from '../models/user_account' -import VerificationToken from '../models/verification_token' - -const host = process.env.SMTP_SERVER -const port = parseInt(process.env.SMTP_PORT ?? '587', 10) -const secure = (process.env.SMTP_SECURE ?? 'false') === 'true' -const user = process.env.SMTP_USER -const pass = process.env.SMTP_PASSWORD - -export const canSendEmails = () => - [host, port, secure, user, pass].find((v) => v === undefined) === undefined - -let transport: Transporter | undefined - -if (canSendEmails()) - transport = nodemailer.createTransport({ - host, - port, - secure, - auth: { - user: process.env.SMTP_USER, - pass: process.env.SMTP_PASSWORD, - }, - }) - -export const verificationEmail = ( - user: UserAccount, - token: VerificationToken, -) => ({ - from: `"Distribute Aid Shipment Tracker" <${process.env.SMTP_FROM}>`, - to: `"${user.name}" <${user.email}>`, - subject: `Verification token: ${token.token}`, - text: `Hei 👋 ${user.name},\n\nPlease use the token ${token.token} to verify your email address.\n\nPlease do not reply to this email.`, -}) - -export const smtpMailer = (omnibus: EventEmitter): void => { - if (transport === undefined) { - console.error(`[email] Sending of emails DISABLED!`) - return - } - console.debug(`[email] Sending of emails ENABLED via ${host}!`) - omnibus.on( - 'user_registered', - async (user: UserAccount, token: VerificationToken) => { - console.debug( - `[email] > ${user.email}: confirmation token ${token.token}`, - ) - try { - await transport!.sendMail(verificationEmail(user, token)) - console.debug('[email] > message sent') - } catch (error) { - console.error(`Failed to sent email: ${(error as Error).message}`) - console.error(error) - } - }, - ) -} diff --git a/src/server.ts b/src/server.ts index b4b80d9d0..53ec1a66d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -29,7 +29,7 @@ import setNewPasswordUsingTokenAndEmail from './routes/password/new' import confirmRegistrationByEmail from './routes/register/confirm' import EventEmitter from 'events' import { consoleMailer } from './mailer/console' -import { canSendEmails, smtpMailer } from './mailer/smtp' +import { appMailer, transportFromConfig } from './mailer/nodemailer' const omnibus = new EventEmitter() @@ -99,8 +99,11 @@ httpServer.listen({ port }, (): void => ), ) -if (canSendEmails()) { - smtpMailer(omnibus) +// Configure email sending +const emailDebug = (...args: any) => console.debug('[email]', ...args) +const maybeTransportConfig = transportFromConfig(emailDebug) +if (maybeTransportConfig !== undefined) { + appMailer(omnibus, maybeTransportConfig, emailDebug) } else { - consoleMailer(omnibus) + consoleMailer(omnibus, emailDebug) } diff --git a/src/tests/appMailer.test.ts b/src/tests/appMailer.test.ts new file mode 100644 index 000000000..7354651e9 --- /dev/null +++ b/src/tests/appMailer.test.ts @@ -0,0 +1,36 @@ +import EventEmitter from 'events' +import { Transporter } from 'nodemailer' +import { appMailer } from '../mailer/nodemailer' + +describe('appMailer', () => { + it('should send an email with a token when a user registers', () => { + const sendMailMock = jest.fn() + sendMailMock.mockImplementationOnce(() => Promise.resolve()) + const omnibus = new EventEmitter() + + appMailer(omnibus, { + transport: { + sendMail: sendMailMock, + } as unknown as Transporter, + fromEmail: 'no-reply@distributeaid.org', + }) + + omnibus.emit( + 'user_registered', + { + name: 'Alex', + email: 'alex@example.com', + }, + { + token: '123456', + }, + ) + + expect(sendMailMock).toHaveBeenCalledWith({ + from: `"Distribute Aid Shipment Tracker" `, + to: `"Alex" `, + subject: `Verification token: 123456`, + text: `Hei 👋 Alex,\n\nPlease use the token 123456 to verify your email address.\n\nPlease do not reply to this email.`, + }) + }) +}) From dfda97f6a4dd5946f98dffdde5cb50d999a45f8d Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 6 Sep 2021 22:50:02 +0000 Subject: [PATCH 28/65] feat: implement FriendlyCaptcha --- .env.example | 5 +- docs/authentication.md | 8 +++- frontend/.env | 3 ++ frontend/package.json | 1 + frontend/src/components/CaptchaSolved.tsx | 1 + frontend/src/components/FriendlyCaptcha.tsx | 38 +++++++++++++++ frontend/src/hooks/useAuth.tsx | 35 +++++++++----- .../src/pages/ConfirmEmailWithTokenPage.tsx | 13 +++++- frontend/src/pages/PublicHome.tsx | 24 ++++++++-- frontend/yarn.lock | 46 +++++++++++++++++++ src/friendlyCaptcha.ts | 43 +++++++++++++++++ src/server.ts | 12 +++-- 12 files changed, 206 insertions(+), 23 deletions(-) create mode 100644 frontend/src/components/CaptchaSolved.tsx create mode 100644 frontend/src/components/FriendlyCaptcha.tsx create mode 100644 src/friendlyCaptcha.ts diff --git a/.env.example b/.env.example index 17181bb80..82584e444 100644 --- a/.env.example +++ b/.env.example @@ -7,4 +7,7 @@ CLIENT_URL="http://localhost:8080" # SMTP_USER=user@example.com # SMTP_PASSWORD=secret # SMTP_SECURE=false -# SMTP_PORT=587 \ No newline at end of file +# SMTP_PORT=587 + +# Fill in to enable friendlycaptcha.com protection +# FRIENDLYCAPTCHA_API_KEY=... \ No newline at end of file diff --git a/docs/authentication.md b/docs/authentication.md index 0de517c08..27a4c1c96 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -8,6 +8,8 @@ Cookies expire after 30 minutes and the client is responsible for renewing cooki When renewing cookies, the server will re-check if the user still exists and if they haven't changed their password. For this a hash of the user's password hash, email, and id will be generated and included in the cookie. If any of these properties changes, the cookie cannot be renewed and the user has to log-in again. +A CAPTCHA is used to protect authentication routes against automated attacks (e.g. trying to figure out which email addresses are registered, requesting large amounts of reset emails). The CAPTCHA solution is sent in the `x-friendly-captcha` by the frontend. + ## Admin permissions Admin permission are granted via the `isAdmin` flag on the `UserAccount` model. @@ -16,4 +18,8 @@ Admin permission are granted via the `isAdmin` flag on the `UserAccount` model. These environment variables control the authentication: -- `COOKIE_SECRET`: sets the secret used to sign cookies, default value is a random string +- Backend + - `COOKIE_SECRET`: sets the secret used to sign cookies, default value is a random string + - `FRIENDLYCAPTCHA_API_KEY`: sets the API key for [FriendlyCaptcha.com](https://friendlycaptcha.com/) which allows to verify CAPTCHA challenges sent from the frontend +- Frontend + - `REACT_APP_FRIENDLYCAPTCHA_SITE_KEY` configures the [FriendlyCaptcha.com](https://friendlycaptcha.com/) site key. diff --git a/frontend/.env b/frontend/.env index c47b5fc10..0d707cd6b 100644 --- a/frontend/.env +++ b/frontend/.env @@ -9,3 +9,6 @@ REACT_APP_GRAPHQL_URL="http://localhost:3000/graphql" # See this issue https://github.com/facebook/create-react-app/issues/1795#issuecomment-357353472 SKIP_PREFLIGHT_CHECK=true + +# The FriendlyCaptcha.com site key +# REACT_APP_FRIENDLYCAPTCHA_SITE_KEY=... \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index a694085c7..179d5c661 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,7 @@ "body-scroll-lock": "3.1.5", "classnames": "2.3.1", "date-fns": "2.22.1", + "friendly-challenge": "0.8.12", "nanoid": "3.1.24", "react": "17.0.2", "react-dom": "17.0.2", diff --git a/frontend/src/components/CaptchaSolved.tsx b/frontend/src/components/CaptchaSolved.tsx new file mode 100644 index 000000000..9db6e46cf --- /dev/null +++ b/frontend/src/components/CaptchaSolved.tsx @@ -0,0 +1 @@ +export const CaptchaSolved = () =>

CAPTCHA solved.

diff --git a/frontend/src/components/FriendlyCaptcha.tsx b/frontend/src/components/FriendlyCaptcha.tsx new file mode 100644 index 000000000..99ca8f003 --- /dev/null +++ b/frontend/src/components/FriendlyCaptcha.tsx @@ -0,0 +1,38 @@ +import { WidgetInstance } from 'friendly-challenge' +import { useEffect, useRef } from 'react' + +const siteKey = process.env.REACT_APP_FRIENDLYCAPTCHA_SITE_KEY + +const FriendlyCaptcha = ({ + onSolution, +}: { + onSolution: (solution: string, reset: () => void) => unknown +}) => { + const container = useRef(null) + const widget = useRef() + + useEffect(() => { + if (!widget.current && container.current) { + widget.current = new WidgetInstance(container.current, { + startMode: 'auto', + doneCallback: (solution: string) => { + onSolution(solution, () => { + widget.current?.reset() + }) + }, + errorCallback: (err: Error) => { + console.error('There was an error when trying to solve the Captcha.') + console.error(err) + }, + }) + } + + return () => { + if (widget.current !== undefined) widget.current.reset() + } + }, [container, onSolution]) + + return
+} + +export default FriendlyCaptcha diff --git a/frontend/src/hooks/useAuth.tsx b/frontend/src/hooks/useAuth.tsx index 9d4871143..746cb04b8 100644 --- a/frontend/src/hooks/useAuth.tsx +++ b/frontend/src/hooks/useAuth.tsx @@ -7,9 +7,14 @@ type AuthInfo = { isRegistered: boolean isConfirmed: boolean logout: () => void - login: (_: { email: string; password: string }) => void - register: (_: { name: string; email: string; password: string }) => void - confirm: (_: { email: string; token: string }) => void + login: (_: { email: string; password: string; captcha: string }) => void + register: (_: { + name: string + email: string + password: string + captcha: string + }) => void + confirm: (_: { email: string; token: string; captcha: string }) => void } export const AuthContext = createContext({ @@ -60,12 +65,14 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { document.location.reload() }) }, - login: ({ email, password }) => { + login: ({ email, password, captcha }) => { setIsLoading(true) fetch(`${SERVER_URL}/login`, { method: 'POST', - credentials: 'include', - headers, + headers: { + ...headers, + 'x-friendly-captcha': captcha, + }, body: JSON.stringify({ email, password }), }) .then(() => { @@ -77,12 +84,14 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { setIsLoading(false) }) }, - register: ({ name, email, password }) => { + register: ({ name, email, password, captcha }) => { setIsLoading(true) fetch(`${SERVER_URL}/register`, { method: 'POST', - credentials: 'include', - headers, + headers: { + ...headers, + 'x-friendly-captcha': captcha, + }, body: JSON.stringify({ email, password, name }), }) .then(() => { @@ -94,12 +103,14 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { setIsLoading(false) }) }, - confirm: ({ email, token }) => { + confirm: ({ email, token, captcha }) => { setIsLoading(true) fetch(`${SERVER_URL}/register/confirm`, { method: 'POST', - credentials: 'include', - headers, + headers: { + ...headers, + 'x-friendly-captcha': captcha, + }, body: JSON.stringify({ email, token }), }) .then(() => { diff --git a/frontend/src/pages/ConfirmEmailWithTokenPage.tsx b/frontend/src/pages/ConfirmEmailWithTokenPage.tsx index 3c347fbda..da99359fe 100644 --- a/frontend/src/pages/ConfirmEmailWithTokenPage.tsx +++ b/frontend/src/pages/ConfirmEmailWithTokenPage.tsx @@ -1,15 +1,19 @@ import { FunctionComponent, useState } from 'react' import { Redirect } from 'react-router-dom' import DistributeAidWordmark from '../components/branding/DistributeAidWordmark' +import { CaptchaSolved } from '../components/CaptchaSolved' import TextField from '../components/forms/TextField' +import FriendlyCaptcha from '../components/FriendlyCaptcha' import { emailRegEx, tokenRegex, useAuth } from '../hooks/useAuth' const ConfirmEmailWithTokenPage: FunctionComponent = () => { const { confirm, isConfirmed } = useAuth() const [email, setEmail] = useState('') const [token, setToken] = useState('') + const [captcha, setCaptcha] = useState('') - const formValid = emailRegEx.test(email) && tokenRegex.test(token) + const formValid = + emailRegEx.test(email) && tokenRegex.test(token) && captcha.length > 0 return (
@@ -40,11 +44,16 @@ const ConfirmEmailWithTokenPage: FunctionComponent = () => { pattern="^[0-9]{6}" onChange={({ target: { value } }) => setToken(value)} /> + {captcha.length === 0 && ( + setCaptcha(captcha)} /> + )} + {/* We use this element to display so the CAPTCHA solving does not get reset, because this component will re-render when we call setCaptcha() */} + {captcha.length !== 0 && } + {CAPTCHA} {isConfirmed && ( { const { login, register, isRegistered } = useAuth() @@ -13,10 +12,13 @@ const PublicHomePage: FunctionComponent = () => { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [password2, setPassword2] = useState('') - const [captcha, setCaptcha] = useState('') + const container = useRef(null) + const { element: CAPTCHA, solution: captchaSolution } = useFriendlyCaptcha() const loginFormValid = - emailRegEx.test(email) && passwordRegEx.test(password) && captcha.length > 0 + emailRegEx.test(email) && + passwordRegEx.test(password) && + captchaSolution !== undefined const registerFormValid = loginFormValid && password === password2 && name.trim().length > 0 @@ -52,17 +54,13 @@ const PublicHomePage: FunctionComponent = () => { value={password} onChange={({ target: { value } }) => setPassword(value)} /> - {captcha.length === 0 && ( - setCaptcha(captcha)} - /> - )} - {/* We use this element to display so the CAPTCHA solving does not get reset, because this component will re-render when we call setCaptcha() */} - {captcha.length !== 0 && } +
+ {CAPTCHA} )} {showRegisterForm && !isRegistered && ( @@ -112,18 +111,17 @@ const PublicHomePage: FunctionComponent = () => { value={password2} onChange={({ target: { value } }) => setPassword2(value)} /> - {captcha.length === 0 && ( - setCaptcha(captcha)} - /> - )} - {/* We use this element to display so the CAPTCHA solving does not get reset, because this component will re-render when we call setCaptcha() */} - {captcha.length !== 0 && } +
+ {CAPTCHA} )} {isRegistered && ( From 436628b5381d27c9569de820d7d9250fa2468fb6 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Wed, 8 Sep 2021 23:30:24 +0200 Subject: [PATCH 32/65] fix: remove friendlyCaptch for now --- .env.example | 3 - docs/authentication.md | 5 - frontend/package.json | 1 - frontend/src/AppRoot.tsx | 117 +++++++++--------- frontend/src/hooks/useAuth.tsx | 20 +-- frontend/src/hooks/useFriendlyCaptcha.tsx | 70 ----------- .../src/pages/ConfirmEmailWithTokenPage.tsx | 10 +- frontend/src/pages/PublicHome.tsx | 14 +-- frontend/yarn.lock | 46 ------- src/friendlyCaptcha.ts | 47 ------- src/server.ts | 18 +-- src/tests/friendlyCaptcha.test.ts | 81 ------------ 12 files changed, 71 insertions(+), 361 deletions(-) delete mode 100644 frontend/src/hooks/useFriendlyCaptcha.tsx delete mode 100644 src/friendlyCaptcha.ts delete mode 100644 src/tests/friendlyCaptcha.test.ts diff --git a/.env.example b/.env.example index 82584e444..d97b4f715 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,3 @@ CLIENT_URL="http://localhost:8080" # SMTP_PASSWORD=secret # SMTP_SECURE=false # SMTP_PORT=587 - -# Fill in to enable friendlycaptcha.com protection -# FRIENDLYCAPTCHA_API_KEY=... \ No newline at end of file diff --git a/docs/authentication.md b/docs/authentication.md index 27a4c1c96..263c39c9e 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -8,8 +8,6 @@ Cookies expire after 30 minutes and the client is responsible for renewing cooki When renewing cookies, the server will re-check if the user still exists and if they haven't changed their password. For this a hash of the user's password hash, email, and id will be generated and included in the cookie. If any of these properties changes, the cookie cannot be renewed and the user has to log-in again. -A CAPTCHA is used to protect authentication routes against automated attacks (e.g. trying to figure out which email addresses are registered, requesting large amounts of reset emails). The CAPTCHA solution is sent in the `x-friendly-captcha` by the frontend. - ## Admin permissions Admin permission are granted via the `isAdmin` flag on the `UserAccount` model. @@ -20,6 +18,3 @@ These environment variables control the authentication: - Backend - `COOKIE_SECRET`: sets the secret used to sign cookies, default value is a random string - - `FRIENDLYCAPTCHA_API_KEY`: sets the API key for [FriendlyCaptcha.com](https://friendlycaptcha.com/) which allows to verify CAPTCHA challenges sent from the frontend -- Frontend - - `REACT_APP_FRIENDLYCAPTCHA_SITE_KEY` configures the [FriendlyCaptcha.com](https://friendlycaptcha.com/) site key. diff --git a/frontend/package.json b/frontend/package.json index 179d5c661..a694085c7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,6 @@ "body-scroll-lock": "3.1.5", "classnames": "2.3.1", "date-fns": "2.22.1", - "friendly-challenge": "0.8.12", "nanoid": "3.1.24", "react": "17.0.2", "react-dom": "17.0.2", diff --git a/frontend/src/AppRoot.tsx b/frontend/src/AppRoot.tsx index dbde56cd8..4f26f0832 100644 --- a/frontend/src/AppRoot.tsx +++ b/frontend/src/AppRoot.tsx @@ -4,7 +4,6 @@ import { apolloClient } from './apolloClient' import PrivateRoute from './components/PrivateRoute' import { UserProfileProvider } from './components/UserProfileContext' import { AuthProvider, useAuth } from './hooks/useAuth' -import { FriendlyCaptchaProvider } from './hooks/useFriendlyCaptcha' import AdminPage from './pages/AdminPage' import ConfirmEmailWithTokenPage from './pages/ConfirmEmailWithTokenPage' import ApolloDemoPage from './pages/demo/ApolloDemo' @@ -33,67 +32,65 @@ const App = () => { return ( - - - - {isDev && ( - - - - )} - {isLoading && ( - - - - )} - - {isAuthenticated ? : } + + + {isDev && ( + + - - + )} + {isLoading && ( + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + )} + + {isAuthenticated ? : } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) diff --git a/frontend/src/hooks/useAuth.tsx b/frontend/src/hooks/useAuth.tsx index c0f738de0..fb0fbdc9d 100644 --- a/frontend/src/hooks/useAuth.tsx +++ b/frontend/src/hooks/useAuth.tsx @@ -6,14 +6,9 @@ type AuthInfo = { isRegistered: boolean isConfirmed: boolean logout: () => void - login: (_: { email: string; password: string; captcha: string }) => void - register: (_: { - name: string - email: string - password: string - captcha: string - }) => void - confirm: (_: { email: string; token: string; captcha: string }) => void + login: (_: { email: string; password: string }) => void + register: (_: { name: string; email: string; password: string }) => void + confirm: (_: { email: string; token: string }) => void } export const AuthContext = createContext({ @@ -64,13 +59,12 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { document.location.reload() }) }, - login: ({ email, password, captcha }) => { + login: ({ email, password }) => { setIsLoading(true) fetch(`${SERVER_URL}/login`, { method: 'POST', headers: { ...headers, - 'x-friendly-captcha': captcha, }, body: JSON.stringify({ email, password }), }) @@ -83,13 +77,12 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { setIsLoading(false) }) }, - register: ({ name, email, password, captcha }) => { + register: ({ name, email, password }) => { setIsLoading(true) fetch(`${SERVER_URL}/register`, { method: 'POST', headers: { ...headers, - 'x-friendly-captcha': captcha, }, body: JSON.stringify({ email, password, name }), }) @@ -102,13 +95,12 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { setIsLoading(false) }) }, - confirm: ({ email, token, captcha }) => { + confirm: ({ email, token }) => { setIsLoading(true) fetch(`${SERVER_URL}/register/confirm`, { method: 'POST', headers: { ...headers, - 'x-friendly-captcha': captcha, }, body: JSON.stringify({ email, token }), }) diff --git a/frontend/src/hooks/useFriendlyCaptcha.tsx b/frontend/src/hooks/useFriendlyCaptcha.tsx deleted file mode 100644 index 0cb85704f..000000000 --- a/frontend/src/hooks/useFriendlyCaptcha.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { WidgetInstance } from 'friendly-challenge' -import { - createContext, - PropsWithChildren, - useContext, - useEffect, - useRef, - useState, -} from 'react' - -const siteKey = process.env.REACT_APP_FRIENDLYCAPTCHA_SITE_KEY ?? '' - -type CaptchaSolution = { - solution?: string - element: React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLDivElement - > | null -} - -export const FriendlyCaptchaContext = createContext({ - element: null, -}) - -export const useFriendlyCaptcha = () => useContext(FriendlyCaptchaContext) - -export const FriendlyCaptchaProvider = ({ - children, -}: PropsWithChildren) => { - const [solution, setSolution] = useState() - const container = useRef(null) - const widget = useRef() - - useEffect(() => { - if (!widget.current && container.current) { - widget.current = new WidgetInstance(container.current, { - startMode: 'auto', - doneCallback: (solution: string) => { - setSolution(solution) - }, - errorCallback: (err: Error) => { - console.error('There was an error when trying to solve the Captcha.') - console.error(err) - }, - }) - } - - return () => { - if (widget.current !== undefined) widget.current.reset() - } - }, [container, setSolution]) - - return ( - - ) : null, - }} - > - {children} - - ) -} diff --git a/frontend/src/pages/ConfirmEmailWithTokenPage.tsx b/frontend/src/pages/ConfirmEmailWithTokenPage.tsx index 2d49481d0..3c347fbda 100644 --- a/frontend/src/pages/ConfirmEmailWithTokenPage.tsx +++ b/frontend/src/pages/ConfirmEmailWithTokenPage.tsx @@ -3,18 +3,13 @@ import { Redirect } from 'react-router-dom' import DistributeAidWordmark from '../components/branding/DistributeAidWordmark' import TextField from '../components/forms/TextField' import { emailRegEx, tokenRegex, useAuth } from '../hooks/useAuth' -import { useFriendlyCaptcha } from '../hooks/useFriendlyCaptcha' const ConfirmEmailWithTokenPage: FunctionComponent = () => { const { confirm, isConfirmed } = useAuth() const [email, setEmail] = useState('') const [token, setToken] = useState('') - const { element: CAPTCHA, solution: captchaSolution } = useFriendlyCaptcha() - const formValid = - emailRegEx.test(email) && - tokenRegex.test(token) && - captchaSolution !== undefined + const formValid = emailRegEx.test(email) && tokenRegex.test(token) return (
@@ -49,13 +44,12 @@ const ConfirmEmailWithTokenPage: FunctionComponent = () => { className="bg-navy-800 text-white text-lg px-4 py-2 rounded-sm w-full hover:bg-opacity-90" type="button" onClick={() => { - confirm({ email, token, captcha: captchaSolution as string }) + confirm({ email, token }) }} disabled={!formValid} > Verify - {CAPTCHA} {isConfirmed && ( { const { login, register, isRegistered } = useAuth() @@ -13,12 +12,8 @@ const PublicHomePage: FunctionComponent = () => { const [password, setPassword] = useState('') const [password2, setPassword2] = useState('') const container = useRef(null) - const { element: CAPTCHA, solution: captchaSolution } = useFriendlyCaptcha() - const loginFormValid = - emailRegEx.test(email) && - passwordRegEx.test(password) && - captchaSolution !== undefined + const loginFormValid = emailRegEx.test(email) && passwordRegEx.test(password) const registerFormValid = loginFormValid && password === password2 && name.trim().length > 0 @@ -58,9 +53,7 @@ const PublicHomePage: FunctionComponent = () => { - {CAPTCHA} )} {showRegisterForm && !isRegistered && ( @@ -120,7 +112,6 @@ const PublicHomePage: FunctionComponent = () => { name, email, password, - captcha: captchaSolution as string, }) }} disabled={!registerFormValid} @@ -134,7 +125,6 @@ const PublicHomePage: FunctionComponent = () => { > Log in - {CAPTCHA} )} {isRegistered && ( diff --git a/frontend/yarn.lock b/frontend/yarn.lock index a9d66b2ce..48370cefc 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1957,14 +1957,6 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime-corejs3@^7.10.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.15.4.tgz#403139af262b9a6e8f9ba04a6fdcebf8de692bf1" - integrity sha512-lWcAqKeB624/twtTc3w6w/2o9RqJPaNBhPGK6DKLSiwuVWC7WFkypWyNg+CpZoyJH0jVzv1uMtXZ/5/lQOLtCg== - dependencies: - core-js-pure "^3.16.0" - regenerator-runtime "^0.13.4" - "@babel/runtime@7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" @@ -4949,11 +4941,6 @@ core-js-pure@^3.0.0: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.9.1.tgz#677b322267172bd490e4464696f790cbc355bec5" integrity sha512-laz3Zx0avrw9a4QEIdmIblnVuJz8W51leY9iLThatCsFawWxC3sE4guASC78JbCin+DkwMpCdp1AVAuzL/GN7A== -core-js-pure@^3.16.0: - version "3.17.2" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.17.2.tgz#ba6311b6aa1e2f2adeba4ac6ec51a9ff40bdc1af" - integrity sha512-2VV7DlIbooyTI7Bh+yzOOWL9tGwLnQKHno7qATE+fqZzDKYr6llVjVQOzpD/QLZFgXDPb8T71pJokHEZHEYJhQ== - core-js@^2.4.0: version "2.6.12" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" @@ -6625,24 +6612,6 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -friendly-challenge@0.8.12: - version "0.8.12" - resolved "https://registry.yarnpkg.com/friendly-challenge/-/friendly-challenge-0.8.12.tgz#6ae7fd85e1efc6b34861613ea2e43dc1073b7606" - integrity sha512-5yKkmh+207MrQjtcuTvcYZoehMg8q+9eIM5QCynPJEqO6bxEWq2tbSiT9tnYjTPn2dnsdvQXFdvFBOOf5uFWYg== - dependencies: - "@babel/runtime-corejs3" "^7.10.4" - core-js "^3.6.5" - friendly-pow "0.1.2" - object-assign-polyfill "^0.1.0" - promis "^1.1.4" - url-polyfill "^1.1.10" - whatwg-fetch "^3.4.1" - -friendly-pow@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/friendly-pow/-/friendly-pow-0.1.2.tgz#73a2f420d0573690f53f540b2a7d35828104db39" - integrity sha512-Ihnr6Ii9zDAPU0ylpRpjiWSIBUApuLOC8JtO7WMhh5Z5Fu+QjQHqZpV5tHwx3/pV3MOW20zSrGvdL05BpxpSjw== - from2@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" @@ -9673,11 +9642,6 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign-polyfill@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-assign-polyfill/-/object-assign-polyfill-0.1.0.tgz#2b72338ff68b770cdcda781860484bb8b2f40920" - integrity sha1-K3Izj/aLdwzc2ngYYEhLuLL0CSA= - object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -11022,11 +10986,6 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -promis@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/promis/-/promis-1.1.4.tgz#0d0ef43ccacacdd8f9679cd6e0950f7c7f52096c" - integrity sha1-DQ70PMrKzdj5Z5zW4JUPfH9SCWw= - promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -13244,11 +13203,6 @@ url-parse@^1.4.3, url-parse@^1.4.7: querystringify "^2.1.1" requires-port "^1.0.0" -url-polyfill@^1.1.10: - version "1.1.12" - resolved "https://registry.yarnpkg.com/url-polyfill/-/url-polyfill-1.1.12.tgz#6cdaa17f6b022841b3aec0bf8dbd87ac0cd33331" - integrity sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A== - url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" diff --git a/src/friendlyCaptcha.ts b/src/friendlyCaptcha.ts deleted file mode 100644 index 4dc40c97d..000000000 --- a/src/friendlyCaptcha.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { NextFunction, Request, Response } from 'express' -import { request } from 'https' - -/** - * Implements the friendlyCaptch verification as an express middleware. - * - * @see https://docs.friendlycaptcha.com/#/verification_api - */ -export const captchaCheck = - ( - secret: string, - endpoint = new URL('https://api.friendlycaptcha.com/api/v1/siteverify'), - ) => - (req: Request, expressResponse: Response, next: NextFunction) => { - if ((req.headers['x-friendly-captcha']?.length ?? 0) <= 0) - return expressResponse - .status(401) - .send('Missing CAPTCHA in x-friendly-captcha header') - const r = request( - { - host: endpoint.host, - path: endpoint.pathname, - protocol: endpoint.protocol, - method: 'POST', - port: 443, - headers: { - 'Content-Type': 'application/json; charset=utf-8', - }, - }, - (res) => { - let data: Buffer[] = [] - res.on('data', (d) => data.push(d)) - res.on('end', () => { - const { success, errors } = JSON.parse(Buffer.concat(data).toString()) - if (success === true) return next() - return expressResponse.status(401).send(JSON.stringify(errors)) - }) - }, - ) - r.write( - JSON.stringify({ - solution: req.headers['x-friendly-captcha'], - secret, - }), - ) - r.end() - } diff --git a/src/server.ts b/src/server.ts index f0dfd18f9..11e9bf1ac 100644 --- a/src/server.ts +++ b/src/server.ts @@ -30,7 +30,6 @@ import confirmRegistrationByEmail from './routes/register/confirm' import EventEmitter from 'events' import { consoleMailer } from './mailer/console' import { appMailer, transportFromConfig } from './mailer/nodemailer' -import { captchaCheck } from './friendlyCaptcha' const omnibus = new EventEmitter() @@ -50,19 +49,10 @@ app.use( }), ) -let checkCaptcha = (_: Request, __: Response, next: NextFunction) => next() -const friendlyCaptchApiKey = process.env.FRIENDLYCAPTCHA_API_KEY -if (friendlyCaptchApiKey === undefined) { - console.warn(`[CAPTCHA] Middleware DISABLED!`) -} else { - console.debug(`[CAPTCHA] Middleware enabled!`) - checkCaptcha = captchaCheck(friendlyCaptchApiKey) -} - -app.post('/register', checkCaptcha, registerUser(omnibus)) -app.post('/register/confirm', checkCaptcha, confirmRegistrationByEmail) -app.post('/login', checkCaptcha, login) -app.post('/password/token', checkCaptcha, sendVerificationTokenByEmail) +app.post('/register', registerUser(omnibus)) +app.post('/register/confirm', confirmRegistrationByEmail) +app.post('/login', login) +app.post('/password/token', sendVerificationTokenByEmail) app.post('/password/new', setNewPasswordUsingTokenAndEmail()) app.get('/me', cookieAuth, getProfile) app.post('/me/cookie', cookieAuth, renewCookie) diff --git a/src/tests/friendlyCaptcha.test.ts b/src/tests/friendlyCaptcha.test.ts deleted file mode 100644 index 01ad5eaef..000000000 --- a/src/tests/friendlyCaptcha.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Request, Response } from 'express' -import nock from 'nock' -import { captchaCheck } from '../friendlyCaptcha' - -describe('captchaCheck', () => { - it('should reject request if no CAPTCHA solution is present', () => { - const f = captchaCheck('secret') - const mockResponse: any = { - status: jest.fn(() => mockResponse), - send: jest.fn(() => mockResponse), - } - const next = jest.fn() - f({ headers: {} } as Request, mockResponse as unknown as Response, next) - - expect(mockResponse.status).toHaveBeenCalledWith(401) - expect(mockResponse.send).toHaveBeenCalledWith( - 'Missing CAPTCHA in x-friendly-captcha header', - ) - expect(next).not.toHaveBeenCalled() - }) - - describe('should verify CAPTCHA solution', () => { - test('solution is correct', async () => { - const scope = nock('https://api.friendlycaptcha.com') - .post('/api/v1/siteverify', { - solution: 'foo', - secret: 'secret', - }) - .reply(200, { success: true }) - - const f = captchaCheck('secret') - - await new Promise((resolve) => { - f( - { headers: { [`x-friendly-captcha`]: 'foo' } } as unknown as Request, - {} as unknown as Response, - resolve, - ) - }) - - expect(scope.isDone()).toBeTruthy() - }) - - test('solution is wrong', async () => { - const scope = nock('https://api.friendlycaptcha.com') - .post('/api/v1/siteverify', { - solution: 'foo', - secret: 'secret', - }) - .reply(200, { success: false, errors: ['Some error'] }) - - const f = captchaCheck('secret') - - const next = jest.fn() - - let mockResponse: any - - await new Promise((resolve) => { - mockResponse = { - status: jest.fn(() => mockResponse), - send: jest.fn(() => { - resolve() - }), - } - - f( - { headers: { [`x-friendly-captcha`]: 'foo' } } as unknown as Request, - mockResponse as unknown as Response, - next, - ) - }) - - expect(scope.isDone()).toBeTruthy() - expect(next).not.toHaveBeenCalled() - expect(mockResponse.status).toHaveBeenCalledWith(401) - expect(mockResponse.send).toHaveBeenCalledWith( - JSON.stringify(['Some error']), - ) - }) - }) -}) From 1603b93967cb2b47e2297787c56ef2cc6d658d39 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 00:01:40 +0200 Subject: [PATCH 33/65] build: add backend-only server --- package.json | 5 +- src/sequelize.ts | 6 +- src/server.ts | 109 ------------------------------------- src/server/dev.ts | 51 +++++++++++++++++ src/server/feat/backend.ts | 59 ++++++++++++++++++++ src/server/feat/emails.ts | 14 +++++ src/server/feat/express.ts | 12 ++++ src/server/prod.ts | 46 ++++++++++++++++ 8 files changed, 189 insertions(+), 113 deletions(-) delete mode 100644 src/server.ts create mode 100644 src/server/dev.ts create mode 100644 src/server/feat/backend.ts create mode 100644 src/server/feat/emails.ts create mode 100644 src/server/feat/express.ts create mode 100644 src/server/prod.ts diff --git a/package.json b/package.json index a397d9432..896f9594a 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "description": "Backend for organizing aid shipments.", "scripts": { "predev": "yarn codegen", - "dev:server": "ts-node-dev --no-notify --respawn --transpile-only src/server", + "dev:server": "ts-node-dev --no-notify --respawn --transpile-only src/server/dev", "dev": "npm-run-all --parallel dev:server codegen:watch", - "start": "./node_modules/.bin/nodemon --optimize_for_size --max_old_space_size=920 --gc_interval=100 dist/server.js", + "start": "./node_modules/.bin/nodemon --optimize_for_size --max_old_space_size=920 --gc_interval=100 dist/src/server/dev.js", + "prod:server": "./node_modules/.bin/nodemon --optimize_for_size --max_old_space_size=920 --gc_interval=100 dist/src/server/prod.js", "clean": "rm -rf dist", "build": "yarn --silent run clean && graphql-codegen && tsc", "codegen": "graphql-codegen", diff --git a/src/sequelize.ts b/src/sequelize.ts index 55adc1802..eabcea42b 100644 --- a/src/sequelize.ts +++ b/src/sequelize.ts @@ -1,15 +1,17 @@ +import path from 'path' import { Sequelize, SequelizeOptions } from 'sequelize-typescript' // DB_ENV is used soley to set the database config to "ci" for running // tests on CI. This is because DB connection config is different between // CI and local test envs. See db/config.json const env = process.env.DB_ENV || process.env.NODE_ENV || 'development' -const config = require(__dirname + '/../db/config.json')[env] +const config = require(path.join(process.cwd(), 'db', 'config.json'))[env] +console.debug(`sequelize config loaded`, config) export let sequelize: Sequelize const COMMON_CONFIG: Partial = { - models: [__dirname + '/models'], + models: [path.join(process.cwd(), 'dist', 'src', 'models')], dialect: 'postgres', protocol: 'postgres', } diff --git a/src/server.ts b/src/server.ts deleted file mode 100644 index 11e9bf1ac..000000000 --- a/src/server.ts +++ /dev/null @@ -1,109 +0,0 @@ -// tslint:disable:ordered-imports -import compression from 'compression' -import cors from 'cors' -import express, { NextFunction, Request, Response } from 'express' -import { createServer } from 'http' -import path from 'path' -import passport from 'passport' -import cookieParser from 'cookie-parser' -import { json } from 'body-parser' - -// Note: the order of these imports matters! -import './loadEnv' - -// Initialize the models -import './sequelize' - -import apolloServer from './apolloServer' -import getProfile from './routes/me' -import getAllFilesSync from './getAllFilesSync' -import sendShipmentExportCsv from './sendShipmentExportCsv' -import { cookieAuthStrategy } from './authenticateRequest' -import registerUser from './routes/register' -import login from './routes/login' -import { v4 } from 'uuid' -import { renewCookie, deleteCookie } from './routes/me/cookie' -import changePassword from './routes/me/password' -import sendVerificationTokenByEmail from './routes/password/token' -import setNewPasswordUsingTokenAndEmail from './routes/password/new' -import confirmRegistrationByEmail from './routes/register/confirm' -import EventEmitter from 'events' -import { consoleMailer } from './mailer/console' -import { appMailer, transportFromConfig } from './mailer/nodemailer' - -const omnibus = new EventEmitter() - -const app = express() -/** - * @see ./docs/authentication.md - */ -app.use(cookieParser(process.env.COOKIE_SECRET ?? v4())) -app.use(json()) -const cookieAuth = passport.authenticate('cookie', { session: false }) -passport.use(cookieAuthStrategy) - -app.use( - cors({ - origin: process.env.CLIENT_URL || 'http://localhost:8080', - credentials: true, - }), -) - -app.post('/register', registerUser(omnibus)) -app.post('/register/confirm', confirmRegistrationByEmail) -app.post('/login', login) -app.post('/password/token', sendVerificationTokenByEmail) -app.post('/password/new', setNewPasswordUsingTokenAndEmail()) -app.get('/me', cookieAuth, getProfile) -app.post('/me/cookie', cookieAuth, renewCookie) -app.delete('/me/cookie', cookieAuth, deleteCookie) -app.delete('/me/reset-password', cookieAuth, changePassword()) - -app.get('/shipment-exports/:id', cookieAuth, sendShipmentExportCsv) - -app.use(compression()) - -async function startExpressServer() { - await apolloServer.start() - - apolloServer.applyMiddleware({ - app, - path: '/graphql', - cors: false, // We use the cors plugin for this - }) -} - -startExpressServer() - -const PUBLIC_DIR = path.join(__dirname, '../frontend/build') - -// Serve static assets for the frontend -getAllFilesSync(PUBLIC_DIR).forEach((file: string) => { - app.get(file, (req, res) => { - res.sendFile(path.join(PUBLIC_DIR, file)) - }) -}) - -// Serve the browser client for any other path -app.get('*', (req, res) => { - res.sendFile(path.join(PUBLIC_DIR, 'index.html')) -}) - -const httpServer = createServer(app) - -const port = process.env.PORT || 3000 - -httpServer.listen({ port }, (): void => - console.log( - `\n🚀 GraphQL is now running on http://localhost:${port}/graphql`, - ), -) - -// Configure email sending -const emailDebug = (...args: any) => console.debug('[email]', ...args) -const maybeTransportConfig = transportFromConfig(emailDebug) -if (maybeTransportConfig !== undefined) { - appMailer(omnibus, maybeTransportConfig, emailDebug) -} else { - consoleMailer(omnibus, emailDebug) -} diff --git a/src/server/dev.ts b/src/server/dev.ts new file mode 100644 index 000000000..8a40e92da --- /dev/null +++ b/src/server/dev.ts @@ -0,0 +1,51 @@ +// tslint:disable:ordered-imports +// Note: the order of these imports matters ... +import '../loadEnv' + +// Initialize the models +import '../sequelize' + +// ... but not for these +import { createServer } from 'http' +import path from 'path' +import getAllFilesSync from '../getAllFilesSync' +import { v4 } from 'uuid' +import EventEmitter from 'events' +import { backend } from './feat/backend' +import { startExpressServer } from './feat/express' +import { setUp as setUpEmails } from './feat/emails' + +const omnibus = new EventEmitter() + +const app = backend({ + omnibus, + cookieSecret: process.env.COOKIE_SECRET ?? v4(), + origin: process.env.CLIENT_URL || 'http://localhost:8080', +}) + +startExpressServer(app) + +const PUBLIC_DIR = path.join(__dirname, '../frontend/build') + +// Serve static assets for the frontend +getAllFilesSync(PUBLIC_DIR).forEach((file: string) => { + app.get(file, (req, res) => { + res.sendFile(path.join(PUBLIC_DIR, file)) + }) +}) + +// Serve the browser client for any other path +app.get('*', (req, res) => { + res.sendFile(path.join(PUBLIC_DIR, 'index.html')) +}) + +const httpServer = createServer(app) +const port = parseInt(process.env.PORT ?? '3000', 10) +httpServer.listen({ port }, (): void => + console.log( + `\n🚀 GraphQL is now running on http://localhost:${port}/graphql`, + ), +) + +// Configure email sending +setUpEmails(omnibus) diff --git a/src/server/feat/backend.ts b/src/server/feat/backend.ts new file mode 100644 index 000000000..8880e8904 --- /dev/null +++ b/src/server/feat/backend.ts @@ -0,0 +1,59 @@ +import { json } from 'body-parser' +import compression from 'compression' +import cookieParser from 'cookie-parser' +import cors from 'cors' +import EventEmitter from 'events' +import express, { Express } from 'express' +import passport from 'passport' +import { cookieAuthStrategy } from '../../authenticateRequest' +import login from '../../routes/login' +import getProfile from '../../routes/me' +import { deleteCookie, renewCookie } from '../../routes/me/cookie' +import changePassword from '../../routes/me/password' +import setNewPasswordUsingTokenAndEmail from '../../routes/password/new' +import sendVerificationTokenByEmail from '../../routes/password/token' +import registerUser from '../../routes/register' +import confirmRegistrationByEmail from '../../routes/register/confirm' +import sendShipmentExportCsv from '../../sendShipmentExportCsv' + +export const backend = ({ + omnibus, + cookieSecret, + origin, +}: { + omnibus: EventEmitter + origin: string + cookieSecret: string +}): Express => { + const app = express() + /** + * @see ../docs/authentication.md + */ + app.use(cookieParser(cookieSecret)) + app.use(json()) + const cookieAuth = passport.authenticate('cookie', { session: false }) + passport.use(cookieAuthStrategy) + + app.use( + cors({ + origin, + credentials: true, + }), + ) + + app.post('/register', registerUser(omnibus)) + app.post('/register/confirm', confirmRegistrationByEmail) + app.post('/login', login) + app.post('/password/token', sendVerificationTokenByEmail) + app.post('/password/new', setNewPasswordUsingTokenAndEmail()) + app.get('/me', cookieAuth, getProfile) + app.post('/me/cookie', cookieAuth, renewCookie) + app.delete('/me/cookie', cookieAuth, deleteCookie) + app.delete('/me/reset-password', cookieAuth, changePassword()) + + app.get('/shipment-exports/:id', cookieAuth, sendShipmentExportCsv) + + app.use(compression()) + + return app +} diff --git a/src/server/feat/emails.ts b/src/server/feat/emails.ts new file mode 100644 index 000000000..0b5713798 --- /dev/null +++ b/src/server/feat/emails.ts @@ -0,0 +1,14 @@ +import EventEmitter from 'events' +import { consoleMailer } from '../../mailer/console' +import { appMailer, transportFromConfig } from '../../mailer/nodemailer' + +export const setUp = (omnibus: EventEmitter): void => { + // Configure email sending + const emailDebug = (...args: any) => console.debug('[email]', ...args) + const maybeTransportConfig = transportFromConfig(emailDebug) + if (maybeTransportConfig !== undefined) { + appMailer(omnibus, maybeTransportConfig, emailDebug) + } else { + consoleMailer(omnibus, emailDebug) + } +} diff --git a/src/server/feat/express.ts b/src/server/feat/express.ts new file mode 100644 index 000000000..13317d725 --- /dev/null +++ b/src/server/feat/express.ts @@ -0,0 +1,12 @@ +import { Express } from 'express' +import apolloServer from '../../apolloServer' + +export const startExpressServer = async (app: Express) => { + await apolloServer.start() + + apolloServer.applyMiddleware({ + app, + path: '/graphql', + cors: false, // We use the cors plugin for this + }) +} diff --git a/src/server/prod.ts b/src/server/prod.ts new file mode 100644 index 000000000..c25ba3fa3 --- /dev/null +++ b/src/server/prod.ts @@ -0,0 +1,46 @@ +// tslint:disable:ordered-imports +// Note: the order of these imports matters ... +import '../loadEnv' + +// Initialize the models +import '../sequelize' + +// ... but not for these +import { createServer } from 'http' +import { v4 } from 'uuid' +import EventEmitter from 'events' +import { backend } from './feat/backend' +import { startExpressServer } from './feat/express' +import { setUp as setUpEmails } from './feat/emails' + +const omnibus = new EventEmitter() + +let cookieSecret = process.env.COOKIE_SECRET +if (cookieSecret === undefined || cookieSecret.length === 0) { + console.warn(`Cookie secret not set, using random value.`) + cookieSecret = v4() +} + +const origin = process.env.ORIGIN +if (origin === undefined || !/^http/.test(origin)) { + console.error(`Must set ORIGIN!`) + process.exit(1) +} + +const app = backend({ + omnibus, + cookieSecret, + origin, +}) + +startExpressServer(app) + +const httpServer = createServer(app) +const port = parseInt(process.env.PORT ?? '8080', 10) +httpServer.listen(port, '0.0.0.0', (): void => { + console.debug(`Listening on port ${port}`) + console.debug(`Origin`, origin) +}) + +// Configure email sending +setUpEmails(omnibus) From 27bef59b2695b922d7865d81194a0ccffba1494d Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 00:04:15 +0200 Subject: [PATCH 34/65] build: add shortcut to build server and frontend --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 896f9594a..92ce5f936 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "test": "jest --detectOpenHandles --runInBand", "test-ci": "DB_ENV=ci jest --detectOpenHandles --runInBand", "prepare": "husky install", - "migrate-db": "npx sequelize-cli db:migrate" + "migrate-db": "npx sequelize-cli db:migrate", + "prod:build": "yarn build && cd frontend && yarn install && yarn build:styles && yarn build" }, "engines": { "node": ">=16.0.0", From 61c0a10c150366a506a4c03ef77f2dfc639e8fca Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 00:11:21 +0200 Subject: [PATCH 35/65] build(clevercloud): add DB configuration --- db/config.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/db/config.json b/db/config.json index 8e2ffb502..14a9af9b4 100644 --- a/db/config.json +++ b/db/config.json @@ -29,5 +29,15 @@ "rejectUnauthorized": false } } + }, + "clevercloud": { + "use_env_variable": "POSTGRESQL_ADDON_URI", + "dialect": "postgres", + "dialectOptions": { + "ssl": { + "require": true, + "rejectUnauthorized": false + } + } } } From 4baf5f458d39aefee84dec1842271554e9db0cc1 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 00:43:19 +0200 Subject: [PATCH 36/65] fix(sequelize): allow multiple configs with URI --- src/sequelize.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/sequelize.ts b/src/sequelize.ts index eabcea42b..75c89a5b9 100644 --- a/src/sequelize.ts +++ b/src/sequelize.ts @@ -5,7 +5,10 @@ import { Sequelize, SequelizeOptions } from 'sequelize-typescript' // tests on CI. This is because DB connection config is different between // CI and local test envs. See db/config.json const env = process.env.DB_ENV || process.env.NODE_ENV || 'development' -const config = require(path.join(process.cwd(), 'db', 'config.json'))[env] +console.debug(`sequelize env`, env) +const sequelizeConfig = path.join(process.cwd(), 'db', 'config.json') +console.debug(`sequelize config`, sequelizeConfig) +const config = require(sequelizeConfig)[env] console.debug(`sequelize config loaded`, config) export let sequelize: Sequelize @@ -16,12 +19,16 @@ const COMMON_CONFIG: Partial = { protocol: 'postgres', } -if (env === 'production') { - if (process.env.DATABASE_URL == null) { - throw new Error('DATABASE_URL is null!') +if (config.use_env_variable !== undefined) { + console.debug(`sequelize env variable`, config.use_env_variable) + const connectionUrl = process.env[config.use_env_variable] as + | string + | undefined + if (connectionUrl === undefined || connectionUrl.length === 0) { + throw new Error('connectionUrl is not defined!') } - sequelize = new Sequelize(process.env.DATABASE_URL, { + sequelize = new Sequelize(connectionUrl, { ...COMMON_CONFIG, dialectOptions: config.dialectOptions, }) From 1294d3aaed742ae797cfc552c6310a8a868396b4 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 00:55:30 +0200 Subject: [PATCH 37/65] fix(frontend): remove friendly captcha for now --- frontend/.env | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/.env b/frontend/.env index 0d707cd6b..c47b5fc10 100644 --- a/frontend/.env +++ b/frontend/.env @@ -9,6 +9,3 @@ REACT_APP_GRAPHQL_URL="http://localhost:3000/graphql" # See this issue https://github.com/facebook/create-react-app/issues/1795#issuecomment-357353472 SKIP_PREFLIGHT_CHECK=true - -# The FriendlyCaptcha.com site key -# REACT_APP_FRIENDLYCAPTCHA_SITE_KEY=... \ No newline at end of file From e676c25c7345811b6ca4c3d4f440e5277043e2c3 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 00:57:20 +0200 Subject: [PATCH 38/65] docs(frontend): set node.js version to 16 --- frontend/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index a694085c7..86825049f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -66,5 +66,7 @@ "last 1 safari version" ] }, - "proxy": "http://localhost:3000" + "engines": { + "node": "^16" + } } From 31511e8e96ae02e440ceb57eb72184814426e311 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 01:01:33 +0200 Subject: [PATCH 39/65] build: downgrade node version so it works on clever cloud --- frontend/package.json | 3 --- package.json | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 86825049f..eca4fb511 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -65,8 +65,5 @@ "last 1 firefox version", "last 1 safari version" ] - }, - "engines": { - "node": "^16" } } diff --git a/package.json b/package.json index 92ce5f936..f839a73f5 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "prod:build": "yarn build && cd frontend && yarn install && yarn build:styles && yarn build" }, "engines": { - "node": ">=16.0.0", + "node": ">=14.0.0", "yarn": ">= 1.22.5" }, "keywords": [], From dc3af6d70bc5f15a70df16633223df8a113b9547 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 22:19:39 +0200 Subject: [PATCH 40/65] build: add script to deploy to Clever Cloud cellar @see https://www.clever-cloud.com/blog/engineering/2020/06/24/deploy-cellar-s3-static-site/ --- .clevercloud/publish-frontend-to-cellar.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100755 .clevercloud/publish-frontend-to-cellar.sh diff --git a/.clevercloud/publish-frontend-to-cellar.sh b/.clevercloud/publish-frontend-to-cellar.sh new file mode 100755 index 000000000..02fb6b4f9 --- /dev/null +++ b/.clevercloud/publish-frontend-to-cellar.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +export RCLONE_CONFIG_MYS3_ACCESS_KEY_ID=$CELLAR_ADDON_KEY_ID +export RCLONE_CONFIG_MYS3_SECRET_ACCESS_KEY=$CELLAR_ADDON_KEY_SECRET +export RCLONE_CONFIG_MYS3_ENDPOINT=$CELLAR_ADDON_HOST +export RCLONE_CONFIG_MYS3_TYPE="s3" + +rclone sync ./frontend/build mys3:$CELLAR_BUCKET.$CELLAR_ADDON_HOST --progress --s3-acl=public-read From f40ffd10176a6000793ec42b7f63e5ce7ba4da75 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 22:37:44 +0200 Subject: [PATCH 41/65] build(clevercloud): install rclone --- .clevercloud/publish-frontend-to-cellar.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.clevercloud/publish-frontend-to-cellar.sh b/.clevercloud/publish-frontend-to-cellar.sh index 02fb6b4f9..8a8e8375b 100755 --- a/.clevercloud/publish-frontend-to-cellar.sh +++ b/.clevercloud/publish-frontend-to-cellar.sh @@ -5,4 +5,6 @@ export RCLONE_CONFIG_MYS3_SECRET_ACCESS_KEY=$CELLAR_ADDON_KEY_SECRET export RCLONE_CONFIG_MYS3_ENDPOINT=$CELLAR_ADDON_HOST export RCLONE_CONFIG_MYS3_TYPE="s3" +curl https://rclone.org/install.sh | bash + rclone sync ./frontend/build mys3:$CELLAR_BUCKET.$CELLAR_ADDON_HOST --progress --s3-acl=public-read From 6e2ff92319273c56b678c33f9ff6c603b1712c74 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 22:37:58 +0200 Subject: [PATCH 42/65] docs(frontend): node.js 16 is fine --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f839a73f5..92ce5f936 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "prod:build": "yarn build && cd frontend && yarn install && yarn build:styles && yarn build" }, "engines": { - "node": ">=14.0.0", + "node": ">=16.0.0", "yarn": ">= 1.22.5" }, "keywords": [], From 22e6709dd8535049afc4075106566a745205e783 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 22:44:27 +0200 Subject: [PATCH 43/65] build: use local rclone --- .clevercloud/publish-frontend-to-cellar.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.clevercloud/publish-frontend-to-cellar.sh b/.clevercloud/publish-frontend-to-cellar.sh index 8a8e8375b..f353ab118 100755 --- a/.clevercloud/publish-frontend-to-cellar.sh +++ b/.clevercloud/publish-frontend-to-cellar.sh @@ -5,6 +5,8 @@ export RCLONE_CONFIG_MYS3_SECRET_ACCESS_KEY=$CELLAR_ADDON_KEY_SECRET export RCLONE_CONFIG_MYS3_ENDPOINT=$CELLAR_ADDON_HOST export RCLONE_CONFIG_MYS3_TYPE="s3" -curl https://rclone.org/install.sh | bash +curl -O https://downloads.rclone.org/rclone-current-linux-amd64.zip +unzip rclone-current-linux-amd64.zip +cd rclone-*-linux-amd64 -rclone sync ./frontend/build mys3:$CELLAR_BUCKET.$CELLAR_ADDON_HOST --progress --s3-acl=public-read +rclone sync ../frontend/build mys3:$CELLAR_BUCKET.$CELLAR_ADDON_HOST --progress --s3-acl=public-read From 7bc24603522f4143582ec475dbe7a5bdbf9a3748 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 22:51:01 +0200 Subject: [PATCH 44/65] build: use rclone from local folder --- .clevercloud/publish-frontend-to-cellar.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clevercloud/publish-frontend-to-cellar.sh b/.clevercloud/publish-frontend-to-cellar.sh index f353ab118..afca7a30e 100755 --- a/.clevercloud/publish-frontend-to-cellar.sh +++ b/.clevercloud/publish-frontend-to-cellar.sh @@ -9,4 +9,4 @@ curl -O https://downloads.rclone.org/rclone-current-linux-amd64.zip unzip rclone-current-linux-amd64.zip cd rclone-*-linux-amd64 -rclone sync ../frontend/build mys3:$CELLAR_BUCKET.$CELLAR_ADDON_HOST --progress --s3-acl=public-read +./rclone sync ../frontend/build mys3:$CELLAR_BUCKET.$CELLAR_ADDON_HOST --progress --s3-acl=public-read From fc53c96ca5221ba09db4315dbc5ee4d422940d6b Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 23:06:11 +0200 Subject: [PATCH 45/65] build: use only bucket name --- .clevercloud/publish-frontend-to-cellar.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clevercloud/publish-frontend-to-cellar.sh b/.clevercloud/publish-frontend-to-cellar.sh index afca7a30e..4c34c5bae 100755 --- a/.clevercloud/publish-frontend-to-cellar.sh +++ b/.clevercloud/publish-frontend-to-cellar.sh @@ -9,4 +9,4 @@ curl -O https://downloads.rclone.org/rclone-current-linux-amd64.zip unzip rclone-current-linux-amd64.zip cd rclone-*-linux-amd64 -./rclone sync ../frontend/build mys3:$CELLAR_BUCKET.$CELLAR_ADDON_HOST --progress --s3-acl=public-read +./rclone sync ../frontend/build mys3:$CELLAR_BUCKET --progress --s3-acl=public-read From 386b5d9c9f55cb76ab911c127580bdfa654a19c2 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 23:35:06 +0200 Subject: [PATCH 46/65] build: echo progress --- .clevercloud/publish-frontend-to-cellar.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.clevercloud/publish-frontend-to-cellar.sh b/.clevercloud/publish-frontend-to-cellar.sh index 4c34c5bae..3a17abbab 100755 --- a/.clevercloud/publish-frontend-to-cellar.sh +++ b/.clevercloud/publish-frontend-to-cellar.sh @@ -9,4 +9,9 @@ curl -O https://downloads.rclone.org/rclone-current-linux-amd64.zip unzip rclone-current-linux-amd64.zip cd rclone-*-linux-amd64 +echo "Uploading site to bucket $CELLAR_BUCKET ..." + ./rclone sync ../frontend/build mys3:$CELLAR_BUCKET --progress --s3-acl=public-read + +echo "Done. Site should be available on https://$CELLAR_BUCKET.$CELLAR_ADDON_HOST now." +echo "Configured Origin: $ORIGIN" \ No newline at end of file From 296730e2407659a8b78249dc09d9e005e7328c47 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 23:43:29 +0200 Subject: [PATCH 47/65] fix(frontend): only hardcode env vars for development REACT_APP_VERSION is not used and maintained right now --- frontend/.env | 9 --------- frontend/.env.development | 5 +++++ 2 files changed, 5 insertions(+), 9 deletions(-) create mode 100644 frontend/.env.development diff --git a/frontend/.env b/frontend/.env index c47b5fc10..90c2db115 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,11 +1,2 @@ -# Access the package.json's version through process.env -REACT_APP_VERSION=$npm_package_version - -# The URL where the server runs -REACT_APP_SERVER_URL="http://localhost:3000" - -# The URL where the Apollo server runs -REACT_APP_GRAPHQL_URL="http://localhost:3000/graphql" - # See this issue https://github.com/facebook/create-react-app/issues/1795#issuecomment-357353472 SKIP_PREFLIGHT_CHECK=true diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 000000000..7941f8929 --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1,5 @@ +# The URL where the server runs +REACT_APP_SERVER_URL="http://localhost:3000" + +# The URL where the Apollo server runs +REACT_APP_GRAPHQL_URL="http://localhost:3000/graphql" \ No newline at end of file From 13caeeeaafda88fc2cd78142cb0c8cc9af379e02 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 9 Sep 2021 23:54:13 +0200 Subject: [PATCH 48/65] fix(frontend): use only one server URL env var --- frontend/.env.development | 5 +---- frontend/src/apolloClient.ts | 2 +- frontend/src/components/UserProfileContext.tsx | 2 +- frontend/src/hooks/useAuth.tsx | 2 +- frontend/src/pages/shipments/DownloadCSVMenu.tsx | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/frontend/.env.development b/frontend/.env.development index 7941f8929..57f73eb12 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -1,5 +1,2 @@ # The URL where the server runs -REACT_APP_SERVER_URL="http://localhost:3000" - -# The URL where the Apollo server runs -REACT_APP_GRAPHQL_URL="http://localhost:3000/graphql" \ No newline at end of file +REACT_APP_SERVER_URL="http://localhost:3000" \ No newline at end of file diff --git a/frontend/src/apolloClient.ts b/frontend/src/apolloClient.ts index 5ad6ca62e..4eb5e6c76 100644 --- a/frontend/src/apolloClient.ts +++ b/frontend/src/apolloClient.ts @@ -21,7 +21,7 @@ const mergeByReplacement: { merge: FieldMergeFunction } = { export const apolloClient = new ApolloClient({ link: new HttpLink({ - uri: process.env.REACT_APP_GRAPHQL_URL, + uri: `${process.env.REACT_APP_SERVER_URL?.replace(/\/$/, '')}/graphql`, credentials: 'include', }), cache: new InMemoryCache({ diff --git a/frontend/src/components/UserProfileContext.tsx b/frontend/src/components/UserProfileContext.tsx index f79e8d049..cf9d1944e 100644 --- a/frontend/src/components/UserProfileContext.tsx +++ b/frontend/src/components/UserProfileContext.tsx @@ -1,6 +1,6 @@ import { createContext, FunctionComponent, useEffect, useState } from 'react' -const SERVER_URL = process.env.REACT_APP_SERVER_URL +const SERVER_URL = process.env.REACT_APP_SERVER_URL?.replace(/\/$/, '') export interface UserProfile { id: number diff --git a/frontend/src/hooks/useAuth.tsx b/frontend/src/hooks/useAuth.tsx index fb0fbdc9d..0f5344b43 100644 --- a/frontend/src/hooks/useAuth.tsx +++ b/frontend/src/hooks/useAuth.tsx @@ -24,7 +24,7 @@ export const AuthContext = createContext({ export const useAuth = () => useContext(AuthContext) -const SERVER_URL = process.env.REACT_APP_SERVER_URL +const SERVER_URL = process.env.REACT_APP_SERVER_URL?.replace(/\/$/, '') const headers = { 'content-type': 'application/json; charset=utf-8', diff --git a/frontend/src/pages/shipments/DownloadCSVMenu.tsx b/frontend/src/pages/shipments/DownloadCSVMenu.tsx index 4cae8a632..5498d7b84 100644 --- a/frontend/src/pages/shipments/DownloadCSVMenu.tsx +++ b/frontend/src/pages/shipments/DownloadCSVMenu.tsx @@ -12,7 +12,7 @@ interface Props { shipment: ShipmentQuery['shipment'] } -const SERVER_URL = process.env.REACT_APP_SERVER_URL +const SERVER_URL = process.env.REACT_APP_SERVER_URL?.replace(/\/$/, '') const DownloadCSVMenu: FunctionComponent = ({ shipment }) => { const [modalIsVisible, showModal, hideModal] = useModalState() From e3b7f93a890b8fc090e270e8d4ed6795ade435a2 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Fri, 10 Sep 2021 00:02:28 +0200 Subject: [PATCH 49/65] fix: ensure Access-Control-Allow-Origin has no trailing slash --- src/server/dev.ts | 3 ++- src/server/feat/backend.ts | 5 +++-- src/server/prod.ts | 13 ++++++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/server/dev.ts b/src/server/dev.ts index 8a40e92da..176f6b08f 100644 --- a/src/server/dev.ts +++ b/src/server/dev.ts @@ -14,13 +14,14 @@ import EventEmitter from 'events' import { backend } from './feat/backend' import { startExpressServer } from './feat/express' import { setUp as setUpEmails } from './feat/emails' +import { URL } from 'url' const omnibus = new EventEmitter() const app = backend({ omnibus, cookieSecret: process.env.COOKIE_SECRET ?? v4(), - origin: process.env.CLIENT_URL || 'http://localhost:8080', + origin: new URL(process.env.CLIENT_URL || 'http://localhost:8080'), }) startExpressServer(app) diff --git a/src/server/feat/backend.ts b/src/server/feat/backend.ts index 8880e8904..cf2bee3c7 100644 --- a/src/server/feat/backend.ts +++ b/src/server/feat/backend.ts @@ -5,6 +5,7 @@ import cors from 'cors' import EventEmitter from 'events' import express, { Express } from 'express' import passport from 'passport' +import { URL } from 'url' import { cookieAuthStrategy } from '../../authenticateRequest' import login from '../../routes/login' import getProfile from '../../routes/me' @@ -22,7 +23,7 @@ export const backend = ({ origin, }: { omnibus: EventEmitter - origin: string + origin: URL cookieSecret: string }): Express => { const app = express() @@ -36,7 +37,7 @@ export const backend = ({ app.use( cors({ - origin, + origin: `${origin.protocol}//${origin.host}`, credentials: true, }), ) diff --git a/src/server/prod.ts b/src/server/prod.ts index c25ba3fa3..7e1368b12 100644 --- a/src/server/prod.ts +++ b/src/server/prod.ts @@ -12,6 +12,7 @@ import EventEmitter from 'events' import { backend } from './feat/backend' import { startExpressServer } from './feat/express' import { setUp as setUpEmails } from './feat/emails' +import { URL } from 'url' const omnibus = new EventEmitter() @@ -21,9 +22,15 @@ if (cookieSecret === undefined || cookieSecret.length === 0) { cookieSecret = v4() } -const origin = process.env.ORIGIN -if (origin === undefined || !/^http/.test(origin)) { - console.error(`Must set ORIGIN!`) +let origin: URL +try { + origin = new URL(process.env.ORIGIN ?? '') +} catch (err) { + console.error( + `Must set ORIGIN, current value is not a URL: "${process.env.ORIGIN}": ${ + (err as Error).message + }!`, + ) process.exit(1) } From 1c79ec3931f1f02d1d952a60f8a0a8b130521711 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Fri, 10 Sep 2021 00:15:00 +0200 Subject: [PATCH 50/65] fix(passwords): do not enforce special characters --- frontend/src/hooks/useAuth.tsx | 3 +-- src/routes/register.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/hooks/useAuth.tsx b/frontend/src/hooks/useAuth.tsx index 0f5344b43..f67aefe51 100644 --- a/frontend/src/hooks/useAuth.tsx +++ b/frontend/src/hooks/useAuth.tsx @@ -32,8 +32,7 @@ const headers = { export const tokenRegex = /^[0-9]{6}$/ export const emailRegEx = /.+@.+\..+/ -export const passwordRegEx = - /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/ +export const passwordRegEx = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$/ export const AuthProvider = ({ children }: PropsWithChildren) => { const [isLoading, setIsLoading] = useState(false) diff --git a/src/routes/register.ts b/src/routes/register.ts index 3176dece6..90183927c 100644 --- a/src/routes/register.ts +++ b/src/routes/register.ts @@ -15,7 +15,7 @@ export const emailInput = Type.String({ }) export const passwordInput = Type.String({ - pattern: '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$', + pattern: '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$', title: 'Password', }) From e3d6a096ffee687e9331c29c067b42d6187ef89f Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 13 Sep 2021 20:47:28 +0200 Subject: [PATCH 51/65] fix: sent cookies with sameSite=none So Chrome will include it in cross-site requests --- src/authenticateRequest.ts | 2 ++ src/tests/authentication.test.ts | 12 ++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/authenticateRequest.ts b/src/authenticateRequest.ts index 520cf0a99..215cfa2ff 100644 --- a/src/authenticateRequest.ts +++ b/src/authenticateRequest.ts @@ -60,6 +60,7 @@ export const authCookie = ( secure: true, httpOnly: true, expires: new Date(Date.now() + lifetimeInMinutes * 60 * 1000), + sameSite: 'none', }, ] @@ -72,6 +73,7 @@ export const expireAuthCookie = (): [string, string, CookieOptions] => [ secure: true, httpOnly: true, expires: new Date(Date.now() - 60 * 1000), + sameSite: 'none', }, ] diff --git a/src/tests/authentication.test.ts b/src/tests/authentication.test.ts index 30a7f4f16..86e8268e2 100644 --- a/src/tests/authentication.test.ts +++ b/src/tests/authentication.test.ts @@ -149,7 +149,11 @@ describe('User account API', () => { const cookieInfo = parseCookie(res.header['set-cookie'][0] as string) expect(cookieInfo[authCookieName]).toBeDefined() - expect(cookieInfo.options).toMatchObject({ Path: '/', HttpOnly: true }) + expect(cookieInfo.options).toMatchObject({ + Path: '/', + HttpOnly: true, + sameSite: 'none', + }) const expiresIn = new Date(cookieInfo.options.Expires).getTime() - Date.now() expect(expiresIn).toBeLessThan(30 * 60 * 1000) @@ -207,7 +211,11 @@ describe('User account API', () => { .expect(204) const cookieInfo = parseCookie(res.header['set-cookie'][0] as string) expect(cookieInfo[authCookieName]).toBeDefined() - expect(cookieInfo.options).toMatchObject({ Path: '/', HttpOnly: true }) + expect(cookieInfo.options).toMatchObject({ + Path: '/', + HttpOnly: true, + sameSite: 'none', + }) const expiresIn = new Date(cookieInfo.options.Expires).getTime() - Date.now() expect(expiresIn).toBeLessThan(0) // Expires is in the past From 29bdaf5ef094446e60303db90ae7b2c5f4d7d444 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 13 Sep 2021 22:56:46 +0200 Subject: [PATCH 52/65] feat: remove UserProfileContext, add more auth pages --- frontend/src/AppRoot.tsx | 135 ++++++++-------- frontend/src/components/TopNavigation.tsx | 6 +- .../src/components/UserProfileContext.tsx | 57 ------- .../src/components/forms/FormNavigation.tsx | 7 + frontend/src/hooks/useAuth.tsx | 84 +++++++++- frontend/src/pages/Home.tsx | 5 +- frontend/src/pages/Login.tsx | 66 ++++++++ frontend/src/pages/PublicHome.tsx | 144 ------------------ frontend/src/pages/Register.tsx | 101 ++++++++++++ frontend/src/pages/groups/GroupCreatePage.tsx | 9 +- frontend/src/pages/groups/GroupForm.tsx | 6 +- frontend/src/pages/groups/GroupList.tsx | 15 +- frontend/src/pages/groups/GroupViewPage.tsx | 6 +- .../src/pages/home/GroupLeaderHomePage.tsx | 6 +- .../src/pages/lost-password/RequestToken.tsx | 53 +++++++ .../pages/lost-password/SetNewPassword.tsx | 97 ++++++++++++ frontend/src/pages/offers/CreateOfferForm.tsx | 6 +- .../src/pages/offers/OfferStatusSwitcher.tsx | 8 +- frontend/src/pages/shipments/ShipmentList.tsx | 6 +- .../src/pages/shipments/ShipmentViewPage.tsx | 6 +- frontend/src/utils/routes.ts | 5 +- 21 files changed, 520 insertions(+), 308 deletions(-) delete mode 100644 frontend/src/components/UserProfileContext.tsx create mode 100644 frontend/src/components/forms/FormNavigation.tsx create mode 100644 frontend/src/pages/Login.tsx delete mode 100644 frontend/src/pages/PublicHome.tsx create mode 100644 frontend/src/pages/Register.tsx create mode 100644 frontend/src/pages/lost-password/RequestToken.tsx create mode 100644 frontend/src/pages/lost-password/SetNewPassword.tsx diff --git a/frontend/src/AppRoot.tsx b/frontend/src/AppRoot.tsx index 4f26f0832..2a28bdb66 100644 --- a/frontend/src/AppRoot.tsx +++ b/frontend/src/AppRoot.tsx @@ -2,7 +2,6 @@ import { ApolloProvider } from '@apollo/client' import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' import { apolloClient } from './apolloClient' import PrivateRoute from './components/PrivateRoute' -import { UserProfileProvider } from './components/UserProfileContext' import { AuthProvider, useAuth } from './hooks/useAuth' import AdminPage from './pages/AdminPage' import ConfirmEmailWithTokenPage from './pages/ConfirmEmailWithTokenPage' @@ -14,10 +13,13 @@ import GroupViewPage from './pages/groups/GroupViewPage' import HomePage from './pages/Home' import KitchenSink from './pages/KitchenSink' import LoadingPage from './pages/LoadingPage' +import LoginPage from './pages/Login' +import RequestTokenPage from './pages/lost-password/RequestToken' +import SetNewPasswordPage from './pages/lost-password/SetNewPassword' import NotFoundPage from './pages/NotFoundPage' import CreateOfferPage from './pages/offers/CreateOfferPage' import ViewOfferPage from './pages/offers/ViewOfferPage' -import PublicHomePage from './pages/PublicHome' +import RegisterPage from './pages/Register' import ShipmentCreatePage from './pages/shipments/ShipmentCreatePage' import ShipmentEditPage from './pages/shipments/ShipmentEditPage' import ShipmentList from './pages/shipments/ShipmentList' @@ -31,67 +33,78 @@ const App = () => { return ( - - - - {isDev && ( - - - - )} - {isLoading && ( - - - - )} - - {isAuthenticated ? : } - - - + + + {isDev && ( + + - - - - - + )} + {isLoading && ( + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + )} + + {isAuthenticated ? : } + + {!isAuthenticated && ( + <> + + + + + + + + + + + )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) } diff --git a/frontend/src/components/TopNavigation.tsx b/frontend/src/components/TopNavigation.tsx index 6f7142a65..2fefd9713 100644 --- a/frontend/src/components/TopNavigation.tsx +++ b/frontend/src/components/TopNavigation.tsx @@ -1,4 +1,4 @@ -import { FunctionComponent, ReactNode, useContext } from 'react' +import { FunctionComponent, ReactNode } from 'react' import { Link } from 'react-router-dom' import { useAuth } from '../hooks/useAuth' import ROUTES from '../utils/routes' @@ -10,7 +10,6 @@ import TruckIcon from './icons/TruckIcon' import UserIcon from './icons/UserIcon' import DesktopNavigation from './navigation/DesktopNavigation' import MobileNavigation from './navigation/MobileNavigation' -import { UserProfileContext } from './UserProfileContext' export interface NavLinkItem { path: string @@ -47,8 +46,7 @@ interface Props { * branding and a dropdown-menu with some account information. */ const TopNavigation: FunctionComponent = ({ hideControls }) => { - const { logout } = useAuth() - const { profile } = useContext(UserProfileContext) + const { logout, me: profile } = useAuth() const userIsAdmin = profile?.isAdmin diff --git a/frontend/src/components/UserProfileContext.tsx b/frontend/src/components/UserProfileContext.tsx deleted file mode 100644 index cf9d1944e..000000000 --- a/frontend/src/components/UserProfileContext.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { createContext, FunctionComponent, useEffect, useState } from 'react' - -const SERVER_URL = process.env.REACT_APP_SERVER_URL?.replace(/\/$/, '') - -export interface UserProfile { - id: number - isAdmin: boolean - name: string - groupId?: number -} - -interface UserProfileData { - profile?: UserProfile - /** - * Call this method to refresh the values stored in the UserProfile - */ - refetch: () => void -} - -const UserProfileContext = createContext({ - refetch: () => {}, -}) - -const UserProfileProvider: FunctionComponent = ({ children }) => { - const [tokenWasFetched, setTokenWasFetched] = useState(false) - const [profile, setProfile] = useState() - - const refetch = () => setTokenWasFetched(false) - - useEffect(() => { - // We fetch the token again in case the client-side cookie has expired but - // the remote session hasn't - if (!tokenWasFetched) { - fetch(`${SERVER_URL}/me`, { - credentials: 'include', - }) - .then((response) => response.json()) - .catch(() => { - // The user is not logged in - }) - .then((data) => { - setProfile(data) - }) - .finally(() => { - setTokenWasFetched(true) - }) - } - }, [tokenWasFetched]) - - return ( - - {children} - - ) -} - -export { UserProfileContext, UserProfileProvider } diff --git a/frontend/src/components/forms/FormNavigation.tsx b/frontend/src/components/forms/FormNavigation.tsx new file mode 100644 index 000000000..805a28e1d --- /dev/null +++ b/frontend/src/components/forms/FormNavigation.tsx @@ -0,0 +1,7 @@ +import { PropsWithChildren } from 'react-router/node_modules/@types/react' + +const FormNavigation = ({ children }: PropsWithChildren) => ( + +) + +export default FormNavigation diff --git a/frontend/src/hooks/useAuth.tsx b/frontend/src/hooks/useAuth.tsx index f67aefe51..9def03f48 100644 --- a/frontend/src/hooks/useAuth.tsx +++ b/frontend/src/hooks/useAuth.tsx @@ -1,14 +1,36 @@ -import { createContext, PropsWithChildren, useContext, useState } from 'react' +import { + createContext, + PropsWithChildren, + useCallback, + useContext, + useEffect, + useState, +} from 'react' + +export type UserProfile = { + id: number + isAdmin: boolean + name: string + groupId?: number +} type AuthInfo = { isLoading: boolean isAuthenticated: boolean isRegistered: boolean isConfirmed: boolean + me?: UserProfile logout: () => void login: (_: { email: string; password: string }) => void register: (_: { name: string; email: string; password: string }) => void + sendVerificationTokenByEmail: (_: { email: string }) => void + setNewPasswordUsingTokenAndEmail: (_: { + email: string + token: string + password: string + }) => void confirm: (_: { email: string; token: string }) => void + refreshMe: () => void } export const AuthContext = createContext({ @@ -18,8 +40,11 @@ export const AuthContext = createContext({ isConfirmed: false, logout: () => undefined, login: () => undefined, + sendVerificationTokenByEmail: () => undefined, + setNewPasswordUsingTokenAndEmail: () => undefined, register: () => undefined, confirm: () => undefined, + refreshMe: () => undefined, }) export const useAuth = () => useContext(AuthContext) @@ -39,6 +64,26 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { const [isAuthenticated, setIsAuthenticated] = useState(false) const [isRegistered, setIsRegistered] = useState(false) const [isConfirmed, setIsConfirmed] = useState(false) + const [me, setMe] = useState() + + const fetchMe = useCallback( + () => + fetch(`${SERVER_URL}/me`, { + credentials: 'include', + }) + .then((response) => response.json()) + .then((data) => { + setMe(data) + }) + .catch(console.error), + [], + ) + + useEffect(() => { + if (!isAuthenticated) return + if (me !== undefined) return + fetchMe() + }, [isAuthenticated, me, fetchMe]) const auth: AuthInfo = { isLoading, @@ -54,6 +99,7 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { credentials: 'include', }).then(() => { setIsAuthenticated(false) + setMe(undefined) // Reload the page (no need to handle logout in the app) document.location.reload() }) @@ -94,6 +140,40 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { setIsLoading(false) }) }, + sendVerificationTokenByEmail: ({ email }) => { + setIsLoading(true) + fetch(`${SERVER_URL}/password/token`, { + method: 'POST', + headers: { + ...headers, + }, + body: JSON.stringify({ email }), + }) + .then(() => { + setIsLoading(false) + }) + .catch((err) => { + console.error(err) + setIsLoading(false) + }) + }, + setNewPasswordUsingTokenAndEmail: ({ email, password, token }) => { + setIsLoading(true) + fetch(`${SERVER_URL}/password/new`, { + method: 'POST', + headers: { + ...headers, + }, + body: JSON.stringify({ email, password, token }), + }) + .then(() => { + setIsLoading(false) + }) + .catch((err) => { + console.error(err) + setIsLoading(false) + }) + }, confirm: ({ email, token }) => { setIsLoading(true) fetch(`${SERVER_URL}/register/confirm`, { @@ -112,6 +192,8 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { setIsLoading(false) }) }, + me, + refreshMe: fetchMe, } return {children} diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 7046ccab8..928cb226b 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -1,11 +1,10 @@ -import { useContext } from 'react' -import { UserProfileContext } from '../components/UserProfileContext' +import { useAuth } from '../hooks/useAuth' import LayoutWithNav from '../layouts/LayoutWithNav' import AdminHomePage from './home/AdminHomePage' import GroupLeaderHomePage from './home/GroupLeaderHomePage' const HomePage = () => { - const { profile } = useContext(UserProfileContext) + const { me: profile } = useAuth() const userIsGroupLeader = profile?.isAdmin === false const userIsAdmin = profile?.isAdmin diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx new file mode 100644 index 000000000..2163f6621 --- /dev/null +++ b/frontend/src/pages/Login.tsx @@ -0,0 +1,66 @@ +import { FunctionComponent, useState } from 'react' +import DistributeAidWordmark from '../components/branding/DistributeAidWordmark' +import FormNavigation from '../components/forms/FormNavigation' +import TextField from '../components/forms/TextField' +import InternalLink from '../components/InternalLink' +import { emailRegEx, passwordRegEx, useAuth } from '../hooks/useAuth' +import ROUTES from '../utils/routes' + +const LoginPage: FunctionComponent = () => { + const { login } = useAuth() + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + + const isFormValid = emailRegEx.test(email) && passwordRegEx.test(password) + + return ( +
+
+
+ +
+
+

Shipment Tracker

+

+ Welcome to Distribute Aid's shipment tracker! Please log in to + continue. +

+
+ setEmail(value)} + /> + setPassword(value)} + /> + + + Register + + Lost password + + + +
+
+
+ ) +} + +export default LoginPage diff --git a/frontend/src/pages/PublicHome.tsx b/frontend/src/pages/PublicHome.tsx deleted file mode 100644 index 092be14e7..000000000 --- a/frontend/src/pages/PublicHome.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { FunctionComponent, useRef, useState } from 'react' -import { Redirect } from 'react-router-dom' -import DistributeAidWordmark from '../components/branding/DistributeAidWordmark' -import TextField from '../components/forms/TextField' -import { emailRegEx, passwordRegEx, useAuth } from '../hooks/useAuth' - -const PublicHomePage: FunctionComponent = () => { - const { login, register, isRegistered } = useAuth() - const [showRegisterForm, setShowRegisterForm] = useState(false) - const [name, setName] = useState('') - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') - const [password2, setPassword2] = useState('') - const container = useRef(null) - - const loginFormValid = emailRegEx.test(email) && passwordRegEx.test(password) - - const registerFormValid = - loginFormValid && password === password2 && name.trim().length > 0 - - const showLoginForm = !showRegisterForm - return ( -
-
-
- -
-
-

Shipment Tracker

-

- Welcome to Distribute Aid's shipment tracker! Please log in to - continue. -

- {showLoginForm && ( -
- setEmail(value)} - /> - setPassword(value)} - /> -
- - - - )} - {showRegisterForm && !isRegistered && ( -
- setName(value)} - /> - setEmail(value)} - /> - setPassword(value)} - /> - setPassword2(value)} - /> -
- - - - )} - {isRegistered && ( - - )} -
-
-
- ) -} - -export default PublicHomePage diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/Register.tsx new file mode 100644 index 000000000..4ec95d4d8 --- /dev/null +++ b/frontend/src/pages/Register.tsx @@ -0,0 +1,101 @@ +import { FunctionComponent, useState } from 'react' +import { Redirect } from 'react-router-dom' +import DistributeAidWordmark from '../components/branding/DistributeAidWordmark' +import FormNavigation from '../components/forms/FormNavigation' +import TextField from '../components/forms/TextField' +import InternalLink from '../components/InternalLink' +import { emailRegEx, passwordRegEx, useAuth } from '../hooks/useAuth' +import ROUTES from '../utils/routes' + +const RegisterPage: FunctionComponent = () => { + const { register, isRegistered } = useAuth() + const [name, setName] = useState('') + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [password2, setPassword2] = useState('') + + const isFormValid = + emailRegEx.test(email) && + passwordRegEx.test(password) && + password === password2 && + name.trim().length > 0 + + return ( +
+
+
+ +
+
+

Register

+
+ setName(value)} + /> + setEmail(value)} + /> + setPassword(value)} + /> + setPassword2(value)} + /> + + + Login + + Set new password + + + + {isRegistered && ( + + )} +
+
+
+ ) +} + +export default RegisterPage diff --git a/frontend/src/pages/groups/GroupCreatePage.tsx b/frontend/src/pages/groups/GroupCreatePage.tsx index 872131ce6..b13f7689f 100644 --- a/frontend/src/pages/groups/GroupCreatePage.tsx +++ b/frontend/src/pages/groups/GroupCreatePage.tsx @@ -1,6 +1,6 @@ -import { FunctionComponent, useContext } from 'react' +import { FunctionComponent } from 'react' import { useHistory } from 'react-router-dom' -import { UserProfileContext } from '../../components/UserProfileContext' +import { useAuth } from '../../hooks/useAuth' import LayoutWithNav from '../../layouts/LayoutWithNav' import { AllGroupsDocument, @@ -12,8 +12,7 @@ import { groupViewRoute } from '../../utils/routes' import GroupForm from './GroupForm' const GroupCreatePage: FunctionComponent = () => { - const { refetch: refreshUserGroupAssociation } = - useContext(UserProfileContext) + const { refreshMe } = useAuth() const history = useHistory() const [addGroup, { loading: mutationIsLoading, error: mutationError }] = @@ -34,7 +33,7 @@ const GroupCreatePage: FunctionComponent = () => { // Because we cache the association between a group captain and their // group, we need to refresh that association when they create their first // group. - refreshUserGroupAssociation() + refreshMe() if (data) { const newGroupId = data.addGroup.id diff --git a/frontend/src/pages/groups/GroupForm.tsx b/frontend/src/pages/groups/GroupForm.tsx index 99a16e2a2..48d546da2 100644 --- a/frontend/src/pages/groups/GroupForm.tsx +++ b/frontend/src/pages/groups/GroupForm.tsx @@ -1,10 +1,10 @@ -import { FunctionComponent, ReactNode, useContext, useEffect } from 'react' +import { FunctionComponent, ReactNode, useEffect } from 'react' import { useForm } from 'react-hook-form' import Button from '../../components/Button' import SelectField from '../../components/forms/SelectField' import TextField from '../../components/forms/TextField' -import { UserProfileContext } from '../../components/UserProfileContext' import { COUNTRY_CODE_OPTIONS, GROUP_TYPE_OPTIONS } from '../../data/constants' +import { useAuth } from '../../hooks/useAuth' import { GroupCreateInput, GroupQuery, GroupType } from '../../types/api-types' import { stripIdAndTypename } from '../../utils/types' @@ -32,7 +32,7 @@ interface Props { * This component encapsulates a form for creating and editing groups. */ const GroupForm: FunctionComponent = (props) => { - const { profile } = useContext(UserProfileContext) + const { me: profile } = useAuth() const { register, diff --git a/frontend/src/pages/groups/GroupList.tsx b/frontend/src/pages/groups/GroupList.tsx index 655963ddb..3b25a9243 100644 --- a/frontend/src/pages/groups/GroupList.tsx +++ b/frontend/src/pages/groups/GroupList.tsx @@ -1,10 +1,10 @@ import cx from 'classnames' -import { FunctionComponent, useContext, useMemo } from 'react' +import { FunctionComponent, useMemo } from 'react' import { Link } from 'react-router-dom' import { Column, useSortBy, useTable } from 'react-table' import ButtonLink from '../../components/ButtonLink' import TableHeader from '../../components/table/TableHeader' -import { UserProfileContext } from '../../components/UserProfileContext' +import { useAuth } from '../../hooks/useAuth' import LayoutWithNav from '../../layouts/LayoutWithNav' import { AllGroupsQuery, useAllGroupsQuery } from '../../types/api-types' import { formatGroupType } from '../../utils/format' @@ -35,7 +35,7 @@ const COLUMNS: Column[] = [ * Display a list of all the groups in the database */ const GroupList: FunctionComponent = () => { - const { profile } = useContext(UserProfileContext) + const { me: profile } = useAuth() const groupLeaderHasCreatedGroup = profile?.groupId != null const { data } = useAllGroupsQuery() @@ -43,13 +43,8 @@ const GroupList: FunctionComponent = () => { // We must memoize the data for react-table to function properly const groups = useMemo(() => data?.listGroups || [], [data]) - const { - getTableProps, - getTableBodyProps, - headerGroups, - rows, - prepareRow, - } = useTable({ columns: COLUMNS, data: groups }, useSortBy) + const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = + useTable({ columns: COLUMNS, data: groups }, useSortBy) return ( diff --git a/frontend/src/pages/groups/GroupViewPage.tsx b/frontend/src/pages/groups/GroupViewPage.tsx index 069ab999b..e691d890f 100644 --- a/frontend/src/pages/groups/GroupViewPage.tsx +++ b/frontend/src/pages/groups/GroupViewPage.tsx @@ -1,16 +1,16 @@ -import { FunctionComponent, useContext } from 'react' +import { FunctionComponent } from 'react' import { useParams } from 'react-router-dom' import ButtonLink from '../../components/ButtonLink' import ReadOnlyField from '../../components/forms/ReadOnlyField' import InternalLink from '../../components/InternalLink' -import { UserProfileContext } from '../../components/UserProfileContext' +import { useAuth } from '../../hooks/useAuth' import LayoutWithNav from '../../layouts/LayoutWithNav' import { useGroupQuery } from '../../types/api-types' import { formatCountryCodeToName, formatGroupType } from '../../utils/format' import ROUTES, { groupEditRoute } from '../../utils/routes' const GroupViewPage: FunctionComponent = () => { - const { profile } = useContext(UserProfileContext) + const { me: profile } = useAuth() // Extract the group's ID from the URL const groupId = parseInt(useParams<{ groupId: string }>().groupId, 10) diff --git a/frontend/src/pages/home/GroupLeaderHomePage.tsx b/frontend/src/pages/home/GroupLeaderHomePage.tsx index 72c273090..8e5f72471 100644 --- a/frontend/src/pages/home/GroupLeaderHomePage.tsx +++ b/frontend/src/pages/home/GroupLeaderHomePage.tsx @@ -1,14 +1,14 @@ -import { FunctionComponent, useContext } from 'react' +import { FunctionComponent } from 'react' import ButtonLink from '../../components/ButtonLink' import CheckIcon from '../../components/icons/CheckIcon' import CogIcon from '../../components/icons/CogIcon' import TruckIcon from '../../components/icons/TruckIcon' import InternalLink from '../../components/InternalLink' -import { UserProfileContext } from '../../components/UserProfileContext' +import { useAuth } from '../../hooks/useAuth' import ROUTES, { groupViewRoute } from '../../utils/routes' const GroupLeaderHomePage: FunctionComponent = () => { - const { profile } = useContext(UserProfileContext) + const { me: profile } = useAuth() const groupLeaderHasCreatedGroup = profile?.groupId != null diff --git a/frontend/src/pages/lost-password/RequestToken.tsx b/frontend/src/pages/lost-password/RequestToken.tsx new file mode 100644 index 000000000..33151379f --- /dev/null +++ b/frontend/src/pages/lost-password/RequestToken.tsx @@ -0,0 +1,53 @@ +import { FunctionComponent, useState } from 'react' +import DistributeAidWordmark from '../../components/branding/DistributeAidWordmark' +import FormNavigation from '../../components/forms/FormNavigation' +import TextField from '../../components/forms/TextField' +import InternalLink from '../../components/InternalLink' +import { emailRegEx, useAuth } from '../../hooks/useAuth' +import ROUTES from '../../utils/routes' + +const RequestTokenPage: FunctionComponent = () => { + const { sendVerificationTokenByEmail } = useAuth() + const [email, setEmail] = useState('') + + const isFormValid = emailRegEx.test(email) + + return ( +
+
+
+ +
+
+

Lost password

+
+ setEmail(value)} + /> + + + Login + + Set new password + + + +
+
+
+ ) +} + +export default RequestTokenPage diff --git a/frontend/src/pages/lost-password/SetNewPassword.tsx b/frontend/src/pages/lost-password/SetNewPassword.tsx new file mode 100644 index 000000000..9d602e894 --- /dev/null +++ b/frontend/src/pages/lost-password/SetNewPassword.tsx @@ -0,0 +1,97 @@ +import { FunctionComponent, useState } from 'react' +import DistributeAidWordmark from '../../components/branding/DistributeAidWordmark' +import FormNavigation from '../../components/forms/FormNavigation' +import TextField from '../../components/forms/TextField' +import InternalLink from '../../components/InternalLink' +import { + emailRegEx, + passwordRegEx, + tokenRegex, + useAuth, +} from '../../hooks/useAuth' +import ROUTES from '../../utils/routes' + +const SetNewPasswordPage: FunctionComponent = () => { + const { setNewPasswordUsingTokenAndEmail } = useAuth() + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [password2, setPassword2] = useState('') + const [token, setToken] = useState('') + + const isFormValid = + emailRegEx.test(email) && + passwordRegEx.test(password) && + password === password2 && + tokenRegex.test(token) + + return ( +
+
+
+ +
+
+

Register

+
+ setEmail(value)} + /> + setToken(value)} + /> + setPassword(value)} + /> + setPassword2(value)} + /> + + + Login + + Request verification token + + + +
+
+
+ ) +} + +export default SetNewPasswordPage diff --git a/frontend/src/pages/offers/CreateOfferForm.tsx b/frontend/src/pages/offers/CreateOfferForm.tsx index aa7f95343..8a0f0f2f0 100644 --- a/frontend/src/pages/offers/CreateOfferForm.tsx +++ b/frontend/src/pages/offers/CreateOfferForm.tsx @@ -1,5 +1,5 @@ import _pick from 'lodash/pick' -import { FunctionComponent, useContext, useEffect } from 'react' +import { FunctionComponent, useEffect } from 'react' import { useForm } from 'react-hook-form' import { Link } from 'react-router-dom' import Button from '../../components/Button' @@ -7,7 +7,7 @@ import ReadOnlyField from '../../components/forms/ReadOnlyField' import SelectField from '../../components/forms/SelectField' import TextField from '../../components/forms/TextField' import Spinner from '../../components/Spinner' -import { UserProfileContext } from '../../components/UserProfileContext' +import { useAuth } from '../../hooks/useAuth' import { GroupType, OfferCreateInput, @@ -49,7 +49,7 @@ const CreateOfferForm: FunctionComponent = (props) => { }, ) - const { profile } = useContext(UserProfileContext) + const { me: profile } = useAuth() const [getGroups, { loading: isLoadingGroups, data: groups }] = useAllGroupsLazyQuery() diff --git a/frontend/src/pages/offers/OfferStatusSwitcher.tsx b/frontend/src/pages/offers/OfferStatusSwitcher.tsx index cb8618bd2..3ff63fc2b 100644 --- a/frontend/src/pages/offers/OfferStatusSwitcher.tsx +++ b/frontend/src/pages/offers/OfferStatusSwitcher.tsx @@ -1,9 +1,9 @@ -import { FunctionComponent, useContext } from 'react' +import { FunctionComponent } from 'react' import Badge from '../../components/Badge' import Button from '../../components/Button' import ReadOnlyField from '../../components/forms/ReadOnlyField' import ConfirmationModal from '../../components/modal/ConfirmationModal' -import { UserProfileContext } from '../../components/UserProfileContext' +import { useAuth } from '../../hooks/useAuth' import useModalState from '../../hooks/useModalState' import { OfferStatus } from '../../types/api-types' @@ -23,8 +23,8 @@ const OfferStatusSwitcher: FunctionComponent = ({ currentOfferStatus, updateStatus, }) => { - const userProfile = useContext(UserProfileContext) - const userIsAdmin = userProfile.profile?.isAdmin + const { me: profile } = useAuth() + const userIsAdmin = profile?.isAdmin const [isSubmissionModalOpen, showSubmissionModal, hideSubmissionModal] = useModalState() diff --git a/frontend/src/pages/shipments/ShipmentList.tsx b/frontend/src/pages/shipments/ShipmentList.tsx index 05849592c..0cfa0207d 100644 --- a/frontend/src/pages/shipments/ShipmentList.tsx +++ b/frontend/src/pages/shipments/ShipmentList.tsx @@ -1,5 +1,5 @@ import cx from 'classnames' -import { FunctionComponent, useContext, useMemo, useState } from 'react' +import { FunctionComponent, useMemo, useState } from 'react' import { Link } from 'react-router-dom' import { Column, useSortBy, useTable } from 'react-table' import Badge from '../../components/Badge' @@ -8,8 +8,8 @@ import DropdownMenu from '../../components/DropdownMenu' import CheckboxField from '../../components/forms/CheckboxField' import Spinner from '../../components/Spinner' import TableHeader from '../../components/table/TableHeader' -import { UserProfileContext } from '../../components/UserProfileContext' import { SHIPMENT_STATUS_OPTIONS } from '../../data/constants' +import { useAuth } from '../../hooks/useAuth' import LayoutWithNav from '../../layouts/LayoutWithNav' import { AllShipmentsQuery, @@ -77,7 +77,7 @@ const getShipmentStatusOptions = (isAdmin = false) => { } const ShipmentList: FunctionComponent = () => { - const { profile } = useContext(UserProfileContext) + const { me: profile } = useAuth() const [shipmentStatuses, setShipmentStatuses] = useState( profile?.isAdmin diff --git a/frontend/src/pages/shipments/ShipmentViewPage.tsx b/frontend/src/pages/shipments/ShipmentViewPage.tsx index 3f4fddbd1..1ddec12d6 100644 --- a/frontend/src/pages/shipments/ShipmentViewPage.tsx +++ b/frontend/src/pages/shipments/ShipmentViewPage.tsx @@ -1,10 +1,10 @@ -import { FunctionComponent, useContext } from 'react' +import { FunctionComponent } from 'react' import { Route, Switch, useParams } from 'react-router-dom' import ButtonLink from '../../components/ButtonLink' import InternalLink from '../../components/InternalLink' import TabLink from '../../components/tabs/TabLink' import TabList from '../../components/tabs/TabList' -import { UserProfileContext } from '../../components/UserProfileContext' +import { useAuth } from '../../hooks/useAuth' import LayoutWithNav from '../../layouts/LayoutWithNav' import { ShipmentStatus, useShipmentQuery } from '../../types/api-types' import { formatShipmentName } from '../../utils/format' @@ -18,7 +18,7 @@ import ShipmentDetails from './ShipmentDetails' import ShipmentOffers from './ShipmentOffers' const ShipmentViewPage: FunctionComponent = () => { - const { profile } = useContext(UserProfileContext) + const { me: profile } = useAuth() const params = useParams<{ shipmentId: string }>() const shipmentId = parseInt(params.shipmentId, 10) diff --git a/frontend/src/utils/routes.ts b/frontend/src/utils/routes.ts index 2b21ec2c6..b4c273a8e 100644 --- a/frontend/src/utils/routes.ts +++ b/frontend/src/utils/routes.ts @@ -1,7 +1,10 @@ import { generatePath } from 'react-router-dom' -const ROUTES = { +export const ROUTES = { HOME: '/', + REGISTER: '/register', + SEND_VERIFICATION_TOKEN_BY_EMAIL: '/lost-password', + SET_NEW_PASSWORD_USING_EMAIL_AND_TOKEN: '/password', ADMIN_ROOT: '/admin', APOLLO_DEMO: '/apollo-demo', GROUP_LIST: '/groups', From e2e59891e600b42389e8c125343c012d0d17ca9c Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 13 Sep 2021 23:12:14 +0200 Subject: [PATCH 53/65] fix: include credentials with log-in, too --- frontend/src/hooks/useAuth.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/hooks/useAuth.tsx b/frontend/src/hooks/useAuth.tsx index 9def03f48..25d5e213e 100644 --- a/frontend/src/hooks/useAuth.tsx +++ b/frontend/src/hooks/useAuth.tsx @@ -108,6 +108,7 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { setIsLoading(true) fetch(`${SERVER_URL}/login`, { method: 'POST', + credentials: 'include', headers: { ...headers, }, From 9bf8ebd66785575086897d8b42192cc259cd5e0a Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 13 Sep 2021 23:25:48 +0200 Subject: [PATCH 54/65] fix: remove unused LoginButton --- frontend/src/components/LogInButton.tsx | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 frontend/src/components/LogInButton.tsx diff --git a/frontend/src/components/LogInButton.tsx b/frontend/src/components/LogInButton.tsx deleted file mode 100644 index dc15d6816..000000000 --- a/frontend/src/components/LogInButton.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { FunctionComponent } from 'react' - -interface Props { - className?: string -} - -const LogInButton: FunctionComponent = ({ className }) => { - return ( - - ) -} - -export default LogInButton From a8f954c83d71659b413fd195c29f4aac32635ae0 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 13 Sep 2021 23:26:08 +0200 Subject: [PATCH 55/65] fix(logout): reload to / --- frontend/src/hooks/useAuth.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/src/hooks/useAuth.tsx b/frontend/src/hooks/useAuth.tsx index 25d5e213e..1e256e4fb 100644 --- a/frontend/src/hooks/useAuth.tsx +++ b/frontend/src/hooks/useAuth.tsx @@ -59,7 +59,10 @@ export const tokenRegex = /^[0-9]{6}$/ export const emailRegEx = /.+@.+\..+/ export const passwordRegEx = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$/ -export const AuthProvider = ({ children }: PropsWithChildren) => { +export const AuthProvider = ({ + children, + logoutUrl, +}: PropsWithChildren<{ logoutUrl?: URL }>) => { const [isLoading, setIsLoading] = useState(false) const [isAuthenticated, setIsAuthenticated] = useState(false) const [isRegistered, setIsRegistered] = useState(false) @@ -100,8 +103,10 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { }).then(() => { setIsAuthenticated(false) setMe(undefined) - // Reload the page (no need to handle logout in the app) - document.location.reload() + const current = new URL(document.location.href) + document.location.href = ( + logoutUrl ?? new URL(`${current.protocol}://${current.host}`) + ).toString() }) }, login: ({ email, password }) => { From cf7d2df35a67f8b97371d36f71a9d88e11605af0 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 13 Sep 2021 23:33:11 +0200 Subject: [PATCH 56/65] feat: include version string --- .clevercloud/publish-frontend-to-cellar.sh | 10 +++++++++- frontend/public/index.html | 1 + src/server/addVersion.ts | 7 +++++++ src/server/prod.ts | 3 +++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/server/addVersion.ts diff --git a/.clevercloud/publish-frontend-to-cellar.sh b/.clevercloud/publish-frontend-to-cellar.sh index 3a17abbab..a4924e017 100755 --- a/.clevercloud/publish-frontend-to-cellar.sh +++ b/.clevercloud/publish-frontend-to-cellar.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash +cd .. +pwd + export RCLONE_CONFIG_MYS3_ACCESS_KEY_ID=$CELLAR_ADDON_KEY_ID export RCLONE_CONFIG_MYS3_SECRET_ACCESS_KEY=$CELLAR_ADDON_KEY_SECRET export RCLONE_CONFIG_MYS3_ENDPOINT=$CELLAR_ADDON_HOST @@ -9,9 +12,14 @@ curl -O https://downloads.rclone.org/rclone-current-linux-amd64.zip unzip rclone-current-linux-amd64.zip cd rclone-*-linux-amd64 +cd frontend +export REACT_APP_VERSION=$COMMIT_ID +yarn build +cd .. + echo "Uploading site to bucket $CELLAR_BUCKET ..." -./rclone sync ../frontend/build mys3:$CELLAR_BUCKET --progress --s3-acl=public-read +./rclone sync ./frontend/build mys3:$CELLAR_BUCKET --progress --s3-acl=public-read echo "Done. Site should be available on https://$CELLAR_BUCKET.$CELLAR_ADDON_HOST now." echo "Configured Origin: $ORIGIN" \ No newline at end of file diff --git a/frontend/public/index.html b/frontend/public/index.html index cddeca56c..52491ebe8 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -10,6 +10,7 @@ +