From 7199f033fb16b7d9127dac556bab62fd664984d5 Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com> Date: Fri, 24 Feb 2023 12:36:53 +0530 Subject: [PATCH] feat(next-api): basic authentication setup (#49378) * feat(next-api): add fastify-auth0-verify plugin * feat(next-api): add fastify-jwt-authz plugin * feat(next-api): accept privacy endpoint with scopes support * fix(next-api): ignore eslint and ts errors They will be fixed in a future PR when the package with errors has been updated Co-authored-by: Niraj Nandish Co-authored-by: Oliver Eyton-Williams --- api/index.ts | 15 +- api/middleware/index.ts | 9 +- api/package.json | 5 +- api/plugins/fastify-jwt-authz.test.ts | 221 ++++++++++++++++++++++++++ api/plugins/fastify-jwt-authz.ts | 80 ++++++++++ api/routes/test.ts | 45 +++++- api/tsconfig.json | 3 +- package-lock.json | 145 ++++++++++++++++- sample.env | 1 + 9 files changed, 514 insertions(+), 10 deletions(-) create mode 100644 api/plugins/fastify-jwt-authz.test.ts create mode 100644 api/plugins/fastify-jwt-authz.ts diff --git a/api/index.ts b/api/index.ts index e7e0a199b0a..e00a048fdda 100644 --- a/api/index.ts +++ b/api/index.ts @@ -1,10 +1,13 @@ import { config } from 'dotenv'; config({ path: '../.env' }); +import fastifyAuth0 from 'fastify-auth0-verify'; import Fastify from 'fastify'; import middie from '@fastify/middie'; + +import jwtAuthz from './plugins/fastify-jwt-authz'; import { testRoutes } from './routes/test'; import { dbConnector } from './db'; -import { testMiddleware } from './middleware'; +import { auth0Verify, testMiddleware } from './middleware'; const fastify = Fastify({ logger: { level: process.env.NODE_ENV === 'development' ? 'debug' : 'fatal' } @@ -18,8 +21,18 @@ const start = async () => { // NOTE: Awaited to ensure `.use` is registered on `fastify` await fastify.register(middie); + // Auth0 plugin + void fastify.register(fastifyAuth0, { + domain: process.env.AUTH0_DOMAIN, + audience: process.env.AUTH0_AUDIENCE + }); + void fastify.register(jwtAuthz); + void fastify.use('/test', testMiddleware); + // Hooks + void fastify.addHook('preValidation', auth0Verify); + void fastify.register(dbConnector); void fastify.register(testRoutes); diff --git a/api/middleware/index.ts b/api/middleware/index.ts index ee50e841e7d..2e7134540f1 100644 --- a/api/middleware/index.ts +++ b/api/middleware/index.ts @@ -1,7 +1,12 @@ +import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; import type { NextFunction, NextHandleFunction } from '@fastify/middie'; -export async function auth0Verify() { - // Verify user authorization code with Auth0 +export async function auth0Verify( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply +) { + await this.authenticate(request, reply); } type MiddieRequest = Parameters[0]; diff --git a/api/package.json b/api/package.json index f456a9bce81..2119d80c6dc 100644 --- a/api/package.json +++ b/api/package.json @@ -7,7 +7,8 @@ "@fastify/middie": "8.1", "@fastify/mongodb": "6.1.0", "fastify": "4.9.2", - "fastify-plugin": "4.3.0" + "fastify-auth0-verify": "^1.0.0", + "fastify-plugin": "^4.3.0" }, "description": "The freeCodeCamp.org open-source codebase and curriculum", "engines": { @@ -35,7 +36,7 @@ "build": "tsc", "develop": "nodemon index.ts", "start": "NODE_ENV=production node index.js", - "test": "NODE_OPTIONS='--test-only' ts-node **/*.test.ts" + "test": "node --test -r ts-node/register **/*.test.ts" }, "version": "0.0.1" } diff --git a/api/plugins/fastify-jwt-authz.test.ts b/api/plugins/fastify-jwt-authz.test.ts new file mode 100644 index 00000000000..7399fb3225d --- /dev/null +++ b/api/plugins/fastify-jwt-authz.test.ts @@ -0,0 +1,221 @@ +/* +MIT License + +Copyright (c) 2018 Ethan Arrowood + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import assert from 'node:assert'; +// eslint-disable-next-line import/no-unresolved +import { describe, it } from 'node:test'; +import Fastify from 'fastify'; +import jwtAuthz from './fastify-jwt-authz'; + +interface ErrorResponse { + statusCode: number; + error: string; + message: string; +} + +describe('fastify-jwt-authz', { only: true }, () => { + it('should decorate request instance with jwtAuthz method', async () => { + const fastify = Fastify(); + await fastify.register(jwtAuthz); + + fastify.get('/test', function (request) { + assert(request.jwtAuthz); + return { foo: 'bar' }; + }); + + fastify.listen({ port: 0 }, function () { + fastify.server.unref(); + }); + + const res = await fastify.inject({ + method: 'GET', + url: '/test' + }); + + assert.strictEqual(res.statusCode, 200); + }); + + it('should throw an error "Scopes cannot be empty" with an empty scopes parameter', async () => { + const fastify = Fastify(); + await fastify.register(jwtAuthz); + + fastify.get( + '/test2', + { + preHandler: function (request, _reply, done) { + void request.jwtAuthz([], done); + } + }, + function () { + return { foo: 'bar' }; + } + ); + + fastify.listen({ port: 0 }, function () { + fastify.server.unref(); + }); + + const res = await fastify.inject({ + method: 'GET', + url: '/test2' + }); + const resData: ErrorResponse = res.json(); + + assert.strictEqual(res.statusCode, 500); + assert.strictEqual(resData.message, 'Scopes cannot be empty'); + }); + + it('should throw an error "request.user does not exist" non existing request.user', async () => { + const fastify = Fastify(); + await fastify.register(jwtAuthz); + + fastify.get( + '/test3', + { + preHandler: function (request, _reply, done) { + void request.jwtAuthz(['baz'], done); + } + }, + function () { + return { foo: 'bar' }; + } + ); + + fastify.listen({ port: 0 }, function () { + fastify.server.unref(); + }); + + const res = await fastify.inject({ + method: 'GET', + url: '/test3' + }); + const resData: ErrorResponse = res.json(); + + assert.strictEqual(res.statusCode, 500); + assert.strictEqual(resData.message, 'request.user does not exist'); + }); + + it('should throw an error "request.user.scope must be a string"', async () => { + const fastify = Fastify(); + await fastify.register(jwtAuthz); + + fastify.get( + '/test4', + { + preHandler: function (request, _reply, done) { + request.user = { + name: 'sample', + scope: 123 + }; + void request.jwtAuthz(['baz'], done); + } + }, + function () { + return { foo: 'bar' }; + } + ); + + fastify.listen({ port: 0 }, function () { + fastify.server.unref(); + }); + + const res = await fastify.inject({ + method: 'GET', + url: '/test4' + }); + const resData: ErrorResponse = res.json(); + + assert.strictEqual(res.statusCode, 500); + assert.strictEqual(resData.message, 'request.user.scope must be a string'); + }); + + it('should throw an error "Insufficient scope"', async () => { + const fastify = Fastify(); + await fastify.register(jwtAuthz); + + fastify.get( + '/test5', + { + preHandler: function (request, _reply, done) { + request.user = { + name: 'sample', + scope: 'baz' + }; + void request.jwtAuthz(['foo'], done); + } + }, + function () { + return { foo: 'bar' }; + } + ); + + fastify.listen({ port: 0 }, function () { + fastify.server.unref(); + }); + + const res = await fastify.inject({ + method: 'GET', + url: '/test5' + }); + const resData: ErrorResponse = res.json(); + + assert.strictEqual(res.statusCode, 500); + assert.strictEqual(resData.message, 'Insufficient scope'); + }); + + it('should verify user scope', async () => { + const fastify = Fastify(); + await fastify.register(jwtAuthz); + + fastify.get( + '/test6', + { + preHandler: function (request, _reply, done) { + request.user = { + name: 'sample', + scope: 'user manager' + }; + void request.jwtAuthz(['user'], done); + } + }, + function () { + return { foo: 'bar' }; + } + ); + + fastify.listen({ port: 0 }, function () { + fastify.server.unref(); + }); + + const res = await fastify.inject({ + method: 'GET', + url: '/test6' + }); + + const resData: { foo: string } = res.json(); + + assert.strictEqual(res.statusCode, 200); + assert.strictEqual(resData.foo, 'bar'); + }); +}); diff --git a/api/plugins/fastify-jwt-authz.ts b/api/plugins/fastify-jwt-authz.ts new file mode 100644 index 00000000000..c7eabb60561 --- /dev/null +++ b/api/plugins/fastify-jwt-authz.ts @@ -0,0 +1,80 @@ +/* +MIT License + +Copyright (c) 2018 Ethan Arrowood + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import { FastifyPluginCallback, FastifyRequest } from 'fastify'; +import fp from 'fastify-plugin'; + +interface UserObject { + scope: string; +} + +export interface JwtAuthz { + (scopes: string[], callback: (err?: Error) => void): Promise; +} + +const fastifyJwtAuthz: FastifyPluginCallback = (fastify, _opts, done) => { + fastify.decorateRequest('jwtAuthz', checkScopes); + + done(); + + function checkScopes( + this: FastifyRequest, + scopes: string[], + callback: (err?: Error) => void + ) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + void this.jwtAuthz(scopes, function (err) { + return err ? reject(err) : resolve(null); + }); + }); + } + + if (scopes.length === 0) { + return callback(new Error('Scopes cannot be empty')); + } + const user = this.user as UserObject; + if (!user) { + return callback(new Error('request.user does not exist')); + } + if (typeof user.scope !== 'string') { + return callback(new Error('request.user.scope must be a string')); + } + + const userScopes = user.scope.split(' '); + const allowed = scopes.some(scope => { + return userScopes.indexOf(scope) !== -1; + }); + + return callback(allowed ? undefined : new Error('Insufficient scope')); + } +}; + +declare module 'fastify' { + interface FastifyRequest { + jwtAuthz: JwtAuthz; + } +} + +export default fp(fastifyJwtAuthz); diff --git a/api/routes/test.ts b/api/routes/test.ts index 6cd0c93f0ac..73b313d764c 100644 --- a/api/routes/test.ts +++ b/api/routes/test.ts @@ -1,4 +1,4 @@ -import { FastifyPluginCallback } from 'fastify'; +import { FastifyPluginCallback, FastifyRequest } from 'fastify'; export const testRoutes: FastifyPluginCallback = (fastify, _options, done) => { const collection = fastify.mongo.db?.collection('user'); @@ -10,5 +10,48 @@ export const testRoutes: FastifyPluginCallback = (fastify, _options, done) => { const user = await collection?.findOne({ email: 'bar@bar.com' }); return { user }; }); + + fastify.put( + '/update-privacy-terms', + { + preHandler: [ + function ( + req: FastifyRequest<{ Body: { quincyEmails: boolean } }>, + _res, + done + ) { + void req.jwtAuthz(['write:user'], done); + } + ], + schema: { + body: { + required: ['quincyEmails'], + properties: { + quincyEmails: { type: 'boolean' } + } + } + } + }, + (req, res) => { + const { + body: { quincyEmails } + } = req; + + const update = { + acceptedPrivacyTerms: true, + sendQuincyEmail: !!quincyEmails + }; + + return collection + ?.updateOne({ email: 'bar@bar.com' }, { $set: update }) + .then(() => { + void res.code(200).send({ msg: 'Successfully updated' }); + }) + .catch(err => { + fastify.log.error(err); + void res.code(500).send({ msg: 'Something went wrong' }); + }); + } + ); done(); }; diff --git a/api/tsconfig.json b/api/tsconfig.json index 10dcdad3a1f..adfcc0f8660 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -6,6 +6,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "esModuleInterop": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "typeRoots": ["../node_modules/@types", "../node_modules/@fastify"] } } diff --git a/package-lock.json b/package-lock.json index ff9441d9032..ad47861adfc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -117,7 +117,8 @@ "@fastify/middie": "8.1", "@fastify/mongodb": "6.1.0", "fastify": "4.9.2", - "fastify-plugin": "4.3.0" + "fastify-auth0-verify": "^1.0.0", + "fastify-plugin": "^4.3.0" }, "engines": { "node": ">=18", @@ -4559,6 +4560,23 @@ "version": "1.0.0", "license": "MIT" }, + "node_modules/@fastify/cookie": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@fastify/cookie/-/cookie-8.3.0.tgz", + "integrity": "sha512-P9hY9GO11L20TnZ33XN3i0bt+3x0zaT7S0ohAzWO950E9PB2xnNhLYzPFJIGFi5AVN0yr5+/iZhWxeYvR6KCzg==", + "dependencies": { + "cookie": "^0.5.0", + "fastify-plugin": "^4.0.0" + } + }, + "node_modules/@fastify/cookie/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@fastify/deepmerge": { "version": "1.3.0", "license": "MIT" @@ -4574,10 +4592,21 @@ "fast-json-stringify": "^5.0.0" } }, + "node_modules/@fastify/jwt": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fastify/jwt/-/jwt-6.6.0.tgz", + "integrity": "sha512-D5lYtj8M2Geh/njiNmC9DDw+1jVq9muc36xBrl8rp4n46f4a3vFNzJrCI4dcWIZqUmuki8hwM/zRSvU+Y5wy1g==", + "dependencies": { + "@fastify/error": "^3.0.0", + "@lukeed/ms": "^2.0.0", + "fast-jwt": "^2.0.0", + "fastify-plugin": "^4.0.0", + "steed": "^1.1.3" + } + }, "node_modules/@fastify/middie": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@fastify/middie/-/middie-8.1.0.tgz", - "integrity": "sha512-VvUCLfKx2j6KSnh8puT8QW7d5YNzi2fD/4HcFvRQ3a7sHlCo+qtfX2fqzFvNqnMVbNft7GX1JL5if/riUiXsyg==", + "license": "MIT", "dependencies": { "fastify-plugin": "^4.0.0", "path-to-regexp": "^6.1.0", @@ -5946,6 +5975,14 @@ "react": "^16.3.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@lukeed/ms": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.1.tgz", + "integrity": "sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==", + "engines": { + "node": ">=8" + } + }, "node_modules/@mdx-js/mdx": { "version": "1.6.22", "dev": true, @@ -20612,6 +20649,14 @@ "node": ">=6" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "license": "MIT", @@ -25870,6 +25915,19 @@ "version": "1.0.0", "license": "MIT" }, + "node_modules/fast-jwt": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fast-jwt/-/fast-jwt-2.2.0.tgz", + "integrity": "sha512-0KSVmXROvYRCHzmmNzFMEDkd1mfbKopW1TfM2fELv5hZb/blUhfc7bxY7dJiagvR116Vhg6itk9LPUGFRQjRSg==", + "dependencies": { + "asn1.js": "^5.4.1", + "ecdsa-sig-formatter": "^1.0.11", + "mnemonist": "^0.39.5" + }, + "engines": { + "node": ">=14 <20" + } + }, "node_modules/fast-levenshtein": { "version": "2.0.6", "license": "MIT" @@ -25928,6 +25986,17 @@ "version": "1.0.12", "license": "MIT" }, + "node_modules/fastfall": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/fastfall/-/fastfall-1.5.1.tgz", + "integrity": "sha512-KH6p+Z8AKPXnmA7+Iz2Lh8ARCMr+8WNPVludm1LGkZoD2MjY6LVnRMtTKhkdzI+jr0RzQWXKzKyBJm1zoHEL4Q==", + "dependencies": { + "reusify": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fastify": { "version": "4.9.2", "license": "MIT", @@ -25948,6 +26017,22 @@ "tiny-lru": "^9.0.2" } }, + "node_modules/fastify-auth0-verify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fastify-auth0-verify/-/fastify-auth0-verify-1.0.0.tgz", + "integrity": "sha512-/nrfkq6qGgRoe72wwa7wE85aWajIbB/z2CYRXUDzQCd5OgvgsO/RRBGK33ZovuQ52DmjOJvNtqHQAPbcsPMK/Q==", + "dependencies": { + "@fastify/cookie": "^8.0.0", + "@fastify/jwt": "^6.1.0", + "fastify-plugin": "^4.0.0", + "http-errors": "^2.0.0", + "node-cache": "^5.0.1", + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/fastify-plugin": { "version": "4.3.0", "license": "MIT" @@ -25979,6 +26064,15 @@ "version": "4.0.0", "license": "ISC" }, + "node_modules/fastparallel": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/fastparallel/-/fastparallel-2.4.1.tgz", + "integrity": "sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==", + "dependencies": { + "reusify": "^1.0.4", + "xtend": "^4.0.2" + } + }, "node_modules/fastq": { "version": "1.13.0", "license": "ISC", @@ -25986,6 +26080,15 @@ "reusify": "^1.0.4" } }, + "node_modules/fastseries": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/fastseries/-/fastseries-1.7.2.tgz", + "integrity": "sha512-dTPFrPGS8SNSzAt7u/CbMKCJ3s01N04s4JFbORHcmyvVfVKmbhMD1VtRbh5enGHxkaQDqWyLefiKOGGmohGDDQ==", + "dependencies": { + "reusify": "^1.0.0", + "xtend": "^4.0.0" + } + }, "node_modules/fault": { "version": "1.0.4", "license": "MIT", @@ -38107,6 +38210,14 @@ "version": "0.5.3", "license": "MIT" }, + "node_modules/mnemonist": { + "version": "0.39.5", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.5.tgz", + "integrity": "sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==", + "dependencies": { + "obliterator": "^2.0.1" + } + }, "node_modules/mocha": { "version": "10.2.0", "dev": true, @@ -38881,6 +38992,17 @@ "version": "4.2.0", "license": "MIT" }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/node-dir": { "version": "0.1.17", "dev": true, @@ -39828,6 +39950,11 @@ "dev": true, "license": "ISC" }, + "node_modules/obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==" + }, "node_modules/obuf": { "version": "1.1.2", "license": "MIT" @@ -48621,6 +48748,18 @@ "node": ">= 0.6" } }, + "node_modules/steed": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/steed/-/steed-1.1.3.tgz", + "integrity": "sha512-EUkci0FAUiE4IvGTSKcDJIQ/eRUP2JJb56+fvZ4sdnguLTqIdKjSxUe138poW8mkvKWXW2sFPrgTsxqoISnmoA==", + "dependencies": { + "fastfall": "^1.5.0", + "fastparallel": "^2.2.0", + "fastq": "^1.3.0", + "fastseries": "^1.7.0", + "reusify": "^1.0.0" + } + }, "node_modules/store": { "version": "2.0.12", "license": "MIT", diff --git a/sample.env b/sample.env index cfd4454add3..d5ab40b925d 100644 --- a/sample.env +++ b/sample.env @@ -10,6 +10,7 @@ SENTRY_ENVIRONMENT=staging AUTH0_CLIENT_ID=client_id_from_auth0_dashboard AUTH0_CLIENT_SECRET=client_secret_from_auth0_dashboard AUTH0_DOMAIN=example.auth0.com +AUTH0_AUDIENCE=sample_audience # Session, Cookie and JWT encryption strings SESSION_SECRET=a_session_secret