From 40a114a774a5e9457ac6a7e9f8f7c299a9323924 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 13:55:32 -0400 Subject: [PATCH 01/16] Install passport / joi --- package-lock.json | 173 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 7 ++ 2 files changed, 180 insertions(+) diff --git a/package-lock.json b/package-lock.json index 7971fe2dd6..e7d93a397b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,49 @@ "js-tokens": "^4.0.0" } }, + "@hapi/address": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.1.tgz", + "integrity": "sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@hapi/formula": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", + "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" + }, + "@hapi/hoek": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", + "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" + }, + "@hapi/joi": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", + "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", + "requires": { + "@hapi/address": "^4.0.1", + "@hapi/formula": "^2.0.0", + "@hapi/hoek": "^9.0.0", + "@hapi/pinpoint": "^2.0.0", + "@hapi/topo": "^5.0.0" + } + }, + "@hapi/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" + }, + "@hapi/topo": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", + "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@types/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", @@ -108,6 +151,21 @@ "@types/range-parser": "*" } }, + "@types/hapi__joi": { + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/@types/hapi__joi/-/hapi__joi-17.1.2.tgz", + "integrity": "sha512-2S6+hBISRQ5Ca6/9zfQi7zPueWMGyZxox6xicqJuW1/aC/6ambLyh+gDqY5fi8JBuHmGKMHldSfEpIXJtTmGKQ==", + "dev": true + }, + "@types/jsonwebtoken": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", + "integrity": "sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/mime": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz", @@ -134,6 +192,36 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/passport": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.3.tgz", + "integrity": "sha512-nyztuxtDPQv9utCzU0qW7Gl8BY2Dn8BKlYAFFyxKipFxjaVd96celbkLCV/tRqqBUZ+JB8If3UfgV8347DTo3Q==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/passport-jwt": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.3.tgz", + "integrity": "sha512-RlOCXiTitE8kazj9jZc6/BfGCSqnv2w/eYPDm3+3iNsquHn7ratu7oIUskZx9ZtnwMdpvdpy+Z/QYClocH5NvQ==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "@types/passport-strategy": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", + "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*" + } + }, "@types/pino": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/@types/pino/-/pino-6.3.0.tgz", @@ -2325,6 +2413,23 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -2355,6 +2460,11 @@ "safe-buffer": "^5.0.1" } }, + "jwt-simple": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/jwt-simple/-/jwt-simple-0.5.6.tgz", + "integrity": "sha512-40aUybvhH9t2h71ncA1/1SbtTNCVZHgsTsTgqPUxGWDmUDrXyDf2wMNQKEbdBjbf4AI+fQhbECNTV6lWxQKUzg==" + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -2620,6 +2730,41 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "log-symbols": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", @@ -3379,6 +3524,29 @@ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, + "passport": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz", + "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-jwt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", + "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==", + "requires": { + "jsonwebtoken": "^8.2.0", + "passport-strategy": "^1.0.0" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3425,6 +3593,11 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", diff --git a/package.json b/package.json index 3545820cd5..ad6aded7b7 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,10 @@ "homepage": "https://github.com/directus/api-node#readme", "devDependencies": { "@types/express": "^4.17.6", + "@types/hapi__joi": "^17.1.2", "@types/nodemailer": "^6.4.0", + "@types/passport": "^1.0.3", + "@types/passport-jwt": "^3.0.3", "@types/pino": "^6.3.0", "copyfiles": "^2.3.0", "eslint-plugin-prettier": "^3.1.4", @@ -53,17 +56,21 @@ ] }, "dependencies": { + "@hapi/joi": "^17.1.1", "body-parser": "^1.19.0", "dotenv": "^8.2.0", "express": "^4.17.1", "express-async-handler": "^1.1.4", "get-port": "^5.1.1", + "jwt-simple": "^0.5.6", "knex": "^0.21.1", "liquidjs": "^9.12.0", "mssql": "^6.2.0", "mysql": "^2.18.1", "nodemailer": "^6.4.10", "oracledb": "^4.2.0", + "passport": "^0.4.1", + "passport-jwt": "^4.0.0", "pg": "^8.2.1", "pino": "^6.3.2", "sqlite3": "^4.2.0", From 876690a088769f66a78b46b89fb86965bdfaa84e Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 14:25:05 -0400 Subject: [PATCH 02/16] Add token settings --- example.env | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/example.env b/example.env index ad1345541c..123102faa9 100644 --- a/example.env +++ b/example.env @@ -1,6 +1,11 @@ # General PORT=3000 +# Auth +SECRET="abcdef" +ACCESS_TOKEN_EXPIRY_TIME="15m" +REFRESH_TOKEN_EXPIRY_TIME="7d" + # Database DB_CLIENT="pg" DB_HOST="localhost" From 9fbf90a0dee2553833928ecae5baa92db2d24294 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 14:25:12 -0400 Subject: [PATCH 03/16] Init auth router --- src/app.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app.ts b/src/app.ts index 30f6c5bbe2..5f96f940bb 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,9 +4,12 @@ dotenv.config(); import express from 'express'; import bodyParser from 'body-parser'; -import { errorHandler } from './error'; +import { errorHandler, ErrorCode } from './error'; + +import passport from './auth/passport'; import activityRouter from './routes/activity'; +import authRouter from './routes/auth'; import collectionPresetsRouter from './routes/collection-presets'; import extensionsRouter from './routes/extensions'; import filesRouter from './routes/files'; @@ -35,7 +38,9 @@ import notFoundHandler from './routes/not-found'; const app = express() .disable('x-powered-by') .use(bodyParser.json()) + .use(passport.initialize()) .use('/activity', activityRouter) + .use('/auth', authRouter) .use('/collection_presets', collectionPresetsRouter) .use('/extensions', extensionsRouter) .use('/files', filesRouter) From 19dc170fed0593fb0b2d799a5ac3b387629f2a27 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 14:25:29 -0400 Subject: [PATCH 04/16] Add auth router with authenticate route --- src/routes/auth.ts | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/routes/auth.ts diff --git a/src/routes/auth.ts b/src/routes/auth.ts new file mode 100644 index 0000000000..f67a80160f --- /dev/null +++ b/src/routes/auth.ts @@ -0,0 +1,50 @@ +import { Router } from 'express'; +import asyncHandler from 'express-async-handler'; +import Joi from '@hapi/joi'; +import database from '../database'; +import APIError, { ErrorCode } from '../error'; +import jwt from 'jsonwebtoken'; + +const router = Router(); + +const loginSchema = Joi.object({ + email: Joi.string().email().required(), + password: Joi.string().required(), +}); + +router.post( + '/authenticate', + asyncHandler(async (req, res) => { + await loginSchema.validateAsync(req.body); + const { email, password } = req.body; + + const user = await database + .select('id', 'password') + .from('directus_users') + .where({ email }) + .first(); + + if (!user) { + throw new APIError(ErrorCode.INVALID_USER_CREDENTIALS, 'Invalid user credentials'); + } + + /** @TODO implement password hash */ + if (password !== user.password) { + throw new APIError(ErrorCode.INVALID_USER_CREDENTIALS, 'Invalid user credentials'); + } + + const payload = { + id: user.id, + }; + + const token = jwt.sign(payload, process.env.SECRET, { + expiresIn: process.env.ACCESS_TOKEN_EXPIRY_TIME, + }); + + return res.status(200).json({ + data: { token }, + }); + }) +); + +export default router; From e1af1e4499a6fadca3184e689e0dd2953b4671b7 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 14:25:32 -0400 Subject: [PATCH 05/16] Install jsonwebtoken --- package-lock.json | 5 ----- package.json | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e7d93a397b..75c0ecc4de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2460,11 +2460,6 @@ "safe-buffer": "^5.0.1" } }, - "jwt-simple": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/jwt-simple/-/jwt-simple-0.5.6.tgz", - "integrity": "sha512-40aUybvhH9t2h71ncA1/1SbtTNCVZHgsTsTgqPUxGWDmUDrXyDf2wMNQKEbdBjbf4AI+fQhbECNTV6lWxQKUzg==" - }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", diff --git a/package.json b/package.json index ad6aded7b7..5c00c85bc9 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "express": "^4.17.1", "express-async-handler": "^1.1.4", "get-port": "^5.1.1", - "jwt-simple": "^0.5.6", + "jsonwebtoken": "^8.5.1", "knex": "^0.21.1", "liquidjs": "^9.12.0", "mssql": "^6.2.0", From de53aa01a713889afd91d08aebd1afc3a922d643 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 14:25:55 -0400 Subject: [PATCH 06/16] Setup passport + jwt strategy --- src/auth/passport.ts | 6 ++++++ src/auth/strategies/jwt.ts | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/auth/passport.ts create mode 100644 src/auth/strategies/jwt.ts diff --git a/src/auth/passport.ts b/src/auth/passport.ts new file mode 100644 index 0000000000..2429265726 --- /dev/null +++ b/src/auth/passport.ts @@ -0,0 +1,6 @@ +import passport from 'passport'; +import JWTStrategy from './strategies/jwt'; + +passport.use(JWTStrategy); + +export default passport; diff --git a/src/auth/strategies/jwt.ts b/src/auth/strategies/jwt.ts new file mode 100644 index 0000000000..41bc2a8f15 --- /dev/null +++ b/src/auth/strategies/jwt.ts @@ -0,0 +1,23 @@ +import { Strategy, ExtractJwt } from 'passport-jwt'; +import database from '../../database'; +import APIError, { ErrorCode } from '../../error'; + +const JWTStrategy = new Strategy( + { + secretOrKey: process.env.SECRET, + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + }, + async (payload, done) => { + // This is just an extra verification to make sure the user actually exists when you're trying to + // use it + const users = await database.select('id').from('directus_users').where({ id: payload.id }); + + if (users && users[0]) { + return done(null, users[0]); + } + + return done(new APIError(ErrorCode.USER_NOT_FOUND, 'User not found.')); + } +); + +export default JWTStrategy; From afa2c8128252be00a1aaafd1adfeaedc36f2ef30 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 14:26:12 -0400 Subject: [PATCH 07/16] Add support for more error types --- src/error.ts | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/error.ts b/src/error.ts index 69e8afa4a9..8c58f83908 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,4 +1,5 @@ import { ErrorRequestHandler } from 'express'; +import { ValidationError } from '@hapi/joi'; import logger from './logger'; export enum ErrorCode { @@ -8,6 +9,10 @@ export enum ErrorCode { ENOENT = 'ENOENT', EXTENSION_ILLEGAL_TYPE = 'EXTENSION_ILLEGAL_TYPE', INVALID_QUERY = 'INVALID_QUERY', + INVALID_USER_CREDENTIALS = 'INVALID_USER_CREDENTIALS', + USER_NOT_FOUND = 'USER_NOT_FOUND', + FAILED_VALIDATION = 'FAILED_VALIDATION', + MALFORMED_JSON = 'MALFORMED_JSON', } enum HTTPStatus { @@ -17,9 +22,18 @@ enum HTTPStatus { ENOENT = 501, EXTENSION_ILLEGAL_TYPE = 400, INVALID_QUERY = 400, + INVALID_USER_CREDENTIALS = 401, + USER_NOT_FOUND = 401, + FAILED_VALIDATION = 422, + MALFORMED_JSON = 400, } -export const errorHandler: ErrorRequestHandler = (error: APIError | Error, req, res, next) => { +export const errorHandler: ErrorRequestHandler = ( + error: APIError | ValidationError | Error, + req, + res, + next +) => { let response: any = {}; if (error instanceof APIError) { @@ -33,6 +47,31 @@ export const errorHandler: ErrorRequestHandler = (error: APIError | Error, req, message: error.message, }, }; + } else if (error instanceof ValidationError) { + logger.debug(error); + + res.status(HTTPStatus.FAILED_VALIDATION); + + response = { + error: { + code: ErrorCode.FAILED_VALIDATION, + message: error.message, + }, + }; + } + + // Syntax errors are most likely thrown by Body Parser when misaligned JSON is sent to the API + else if (error instanceof SyntaxError && 'entity.parse.failed') { + logger.debug(error); + + res.status(HTTPStatus.MALFORMED_JSON); + + response = { + error: { + code: ErrorCode.MALFORMED_JSON, + message: error.message, + }, + }; } else { logger.error(error); From 34e105397fab5d5a4e8a2f22eaa8a31c7da5bdc9 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 15:44:32 -0400 Subject: [PATCH 08/16] Remove passport, install atob --- package-lock.json | 510 ++++++++++++++++++++++++++++++++++++++++------ package.json | 8 +- 2 files changed, 456 insertions(+), 62 deletions(-) diff --git a/package-lock.json b/package-lock.json index 75c0ecc4de..933fecaecf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,6 +103,12 @@ "@hapi/hoek": "^9.0.0" } }, + "@types/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-8GAYQ1jDRUQkSpHzJUqXwAkYFOxuWAOGLhIR4aPd/Y/yL12Q/9m7LsKpHKlfKdNE/362Hc9wPI1Yh6opDfxVJg==", + "dev": true + }, "@types/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", @@ -192,36 +198,6 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, - "@types/passport": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.3.tgz", - "integrity": "sha512-nyztuxtDPQv9utCzU0qW7Gl8BY2Dn8BKlYAFFyxKipFxjaVd96celbkLCV/tRqqBUZ+JB8If3UfgV8347DTo3Q==", - "dev": true, - "requires": { - "@types/express": "*" - } - }, - "@types/passport-jwt": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.3.tgz", - "integrity": "sha512-RlOCXiTitE8kazj9jZc6/BfGCSqnv2w/eYPDm3+3iNsquHn7ratu7oIUskZx9ZtnwMdpvdpy+Z/QYClocH5NvQ==", - "dev": true, - "requires": { - "@types/express": "*", - "@types/jsonwebtoken": "*", - "@types/passport-strategy": "*" - } - }, - "@types/passport-strategy": { - "version": "0.2.35", - "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", - "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", - "dev": true, - "requires": { - "@types/express": "*", - "@types/passport": "*" - } - }, "@types/pino": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/@types/pino/-/pino-6.3.0.tgz", @@ -314,6 +290,18 @@ "negotiator": "0.6.2" } }, + "acorn": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "dev": true + }, "adal-node": { "version": "0.1.28", "resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.1.28.tgz", @@ -1044,6 +1032,12 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", @@ -1116,6 +1110,15 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "dotenv": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", @@ -1199,6 +1202,114 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "eslint": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.3.1.tgz", + "integrity": "sha512-cQC/xj9bhWUcyi/RuMbRtC3I0eW8MH0jhRELSvpKYkWep3C6YZ2OkvcvJVUeO6gcunABmzptbXBuDoXsjHmfTA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.0", + "eslint-utils": "^2.0.0", + "eslint-visitor-keys": "^1.2.0", + "espree": "^7.1.0", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", + "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "eslint-plugin-prettier": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz", @@ -1208,17 +1319,91 @@ "prettier-linter-helpers": "^1.0.0" } }, + "eslint-scope": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, "esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" }, + "espree": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz", + "integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==", + "dev": true, + "requires": { + "acorn": "^7.2.0", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.2.0" + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1466,6 +1651,12 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, "fast-redact": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-2.0.0.tgz", @@ -1485,6 +1676,15 @@ "escape-string-regexp": "^1.0.5" } }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -1666,11 +1866,39 @@ "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "flatstr": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, "follow-redirects": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", @@ -1759,6 +1987,12 @@ "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", "optional": true }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -1909,6 +2143,23 @@ } } }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -2117,6 +2368,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, "ignore-walk": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", @@ -2135,6 +2392,12 @@ "resolve-from": "^4.0.0" } }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -2408,6 +2671,12 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -2514,6 +2783,16 @@ } } }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "liftoff": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", @@ -3094,6 +3373,12 @@ "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", "integrity": "sha1-eJkHjmS/PIo9cyYBs9QP8F21j6A=" }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "needle": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", @@ -3387,6 +3672,20 @@ "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", "dev": true }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, "oracledb": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/oracledb/-/oracledb-4.2.0.tgz", @@ -3519,29 +3818,6 @@ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, - "passport": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz", - "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==", - "requires": { - "passport-strategy": "1.x.x", - "pause": "0.0.1" - } - }, - "passport-jwt": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", - "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==", - "requires": { - "jsonwebtoken": "^8.2.0", - "passport-strategy": "^1.0.0" - } - }, - "passport-strategy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", - "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3588,11 +3864,6 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, - "pause": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" - }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -3768,6 +4039,12 @@ "xtend": "^4.0.0" } }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, "prettier": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", @@ -3805,6 +4082,12 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", @@ -3979,6 +4262,12 @@ "safe-regex": "^1.1.0" } }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", @@ -4656,6 +4945,75 @@ "has-flag": "^3.0.0" } }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "tar": { "version": "4.4.13", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", @@ -4728,6 +5086,12 @@ } } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -4926,6 +5290,15 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, "type-fest": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", @@ -5042,6 +5415,12 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true + }, "v8flags": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", @@ -5132,6 +5511,12 @@ } } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -5175,6 +5560,15 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, "xml2js": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", diff --git a/package.json b/package.json index 5c00c85bc9..9ef069c8bd 100644 --- a/package.json +++ b/package.json @@ -28,13 +28,14 @@ }, "homepage": "https://github.com/directus/api-node#readme", "devDependencies": { + "@types/atob": "^2.1.2", "@types/express": "^4.17.6", "@types/hapi__joi": "^17.1.2", + "@types/jsonwebtoken": "^8.5.0", "@types/nodemailer": "^6.4.0", - "@types/passport": "^1.0.3", - "@types/passport-jwt": "^3.0.3", "@types/pino": "^6.3.0", "copyfiles": "^2.3.0", + "eslint": "^7.3.1", "eslint-plugin-prettier": "^3.1.4", "husky": "^4.2.5", "lint-staged": "^10.2.10", @@ -57,6 +58,7 @@ }, "dependencies": { "@hapi/joi": "^17.1.1", + "atob": "^2.1.2", "body-parser": "^1.19.0", "dotenv": "^8.2.0", "express": "^4.17.1", @@ -69,8 +71,6 @@ "mysql": "^2.18.1", "nodemailer": "^6.4.10", "oracledb": "^4.2.0", - "passport": "^0.4.1", - "passport-jwt": "^4.0.0", "pg": "^8.2.1", "pino": "^6.3.2", "sqlite3": "^4.2.0", From 8387d4ffd32695016dc6c0ae6290528a107939cd Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 15:44:50 -0400 Subject: [PATCH 09/16] Remove auth folder / passport stuff --- src/auth/passport.ts | 6 ------ src/auth/strategies/jwt.ts | 23 ----------------------- 2 files changed, 29 deletions(-) delete mode 100644 src/auth/passport.ts delete mode 100644 src/auth/strategies/jwt.ts diff --git a/src/auth/passport.ts b/src/auth/passport.ts deleted file mode 100644 index 2429265726..0000000000 --- a/src/auth/passport.ts +++ /dev/null @@ -1,6 +0,0 @@ -import passport from 'passport'; -import JWTStrategy from './strategies/jwt'; - -passport.use(JWTStrategy); - -export default passport; diff --git a/src/auth/strategies/jwt.ts b/src/auth/strategies/jwt.ts deleted file mode 100644 index 41bc2a8f15..0000000000 --- a/src/auth/strategies/jwt.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Strategy, ExtractJwt } from 'passport-jwt'; -import database from '../../database'; -import APIError, { ErrorCode } from '../../error'; - -const JWTStrategy = new Strategy( - { - secretOrKey: process.env.SECRET, - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - }, - async (payload, done) => { - // This is just an extra verification to make sure the user actually exists when you're trying to - // use it - const users = await database.select('id').from('directus_users').where({ id: payload.id }); - - if (users && users[0]) { - return done(null, users[0]); - } - - return done(new APIError(ErrorCode.USER_NOT_FOUND, 'User not found.')); - } -); - -export default JWTStrategy; From 654c00afe3f8047aa70628f6d4baafddfbc02050 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 15:44:55 -0400 Subject: [PATCH 10/16] Add is-jwt util --- src/utils/is-jwt.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/utils/is-jwt.ts diff --git a/src/utils/is-jwt.ts b/src/utils/is-jwt.ts new file mode 100644 index 0000000000..efc82a7ac1 --- /dev/null +++ b/src/utils/is-jwt.ts @@ -0,0 +1,31 @@ +import atob from 'atob'; + +/** + * Check if a given string conforms to the structure of a JWT. + */ +export default function isJWT(string: string) { + const parts = string.split('.'); + + // JWTs have the structure header.payload.signature + if (parts.length !== 3) return false; + + // Check if all segments are base64 encoded + try { + atob(parts[0]); + atob(parts[1]); + atob(parts[2]); + } catch (err) { + console.log(err); + return false; + } + + // Check if the header and payload are valid JSON + try { + JSON.parse(atob(parts[0])); + JSON.parse(atob(parts[1])); + } catch { + return false; + } + + return true; +} From 637031f53cb2dd4767af30ea267fe27929b9411d Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 15:45:14 -0400 Subject: [PATCH 11/16] Move login logic to service --- src/routes/auth.ts | 27 ++------------------------- src/services/auth.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 25 deletions(-) create mode 100644 src/services/auth.ts diff --git a/src/routes/auth.ts b/src/routes/auth.ts index f67a80160f..8d3e818629 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -1,9 +1,7 @@ import { Router } from 'express'; import asyncHandler from 'express-async-handler'; import Joi from '@hapi/joi'; -import database from '../database'; -import APIError, { ErrorCode } from '../error'; -import jwt from 'jsonwebtoken'; +import * as AuthService from '../services/auth'; const router = Router(); @@ -18,28 +16,7 @@ router.post( await loginSchema.validateAsync(req.body); const { email, password } = req.body; - const user = await database - .select('id', 'password') - .from('directus_users') - .where({ email }) - .first(); - - if (!user) { - throw new APIError(ErrorCode.INVALID_USER_CREDENTIALS, 'Invalid user credentials'); - } - - /** @TODO implement password hash */ - if (password !== user.password) { - throw new APIError(ErrorCode.INVALID_USER_CREDENTIALS, 'Invalid user credentials'); - } - - const payload = { - id: user.id, - }; - - const token = jwt.sign(payload, process.env.SECRET, { - expiresIn: process.env.ACCESS_TOKEN_EXPIRY_TIME, - }); + const token = await AuthService.authenticate(email, password); return res.status(200).json({ data: { token }, diff --git a/src/services/auth.ts b/src/services/auth.ts new file mode 100644 index 0000000000..1cb6cacf40 --- /dev/null +++ b/src/services/auth.ts @@ -0,0 +1,35 @@ +import database from '../database'; +import APIError, { ErrorCode } from '../error'; +import jwt from 'jsonwebtoken'; + +export const authenticate = async (email: string, password: string) => { + const user = await database + .select('id', 'password', 'role') + .from('directus_users') + .where({ email }) + .first(); + + if (!user) { + throw new APIError(ErrorCode.INVALID_USER_CREDENTIALS, 'Invalid user credentials'); + } + + /** @TODO implement password hash */ + if (password !== user.password) { + throw new APIError(ErrorCode.INVALID_USER_CREDENTIALS, 'Invalid user credentials'); + } + + const payload = { + id: user.id, + }; + + /** + * @TODO + * Sign token with combination of server secret + user password hash + * That way, old tokens are immediately invalidated whenever the user changes their password + */ + const token = jwt.sign(payload, process.env.SECRET, { + expiresIn: process.env.ACCESS_TOKEN_EXPIRY_TIME, + }); + + return token; +}; From 2946a5d76fff34a3e1d113c5c0e054585e20a54c Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 15:45:53 -0400 Subject: [PATCH 12/16] Extend express req with custom props --- src/types/express.d.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/types/express.d.ts diff --git a/src/types/express.d.ts b/src/types/express.d.ts new file mode 100644 index 0000000000..2287ddb5b5 --- /dev/null +++ b/src/types/express.d.ts @@ -0,0 +1,11 @@ +/** + * Custom properties on the req object in express + */ + +declare namespace Express { + export interface Request { + token?: string; + user?: string; + role?: string; + } +} From 6c2daebf30bdd4dffc302c4bbcf9337d8c047236 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 15:46:07 -0400 Subject: [PATCH 13/16] Add extract token + authenticate middleware --- src/app.ts | 6 +++-- src/middleware/authenticate.ts | 31 ++++++++++++++++++++++++++ src/middleware/extract-token.ts | 39 +++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/middleware/authenticate.ts create mode 100644 src/middleware/extract-token.ts diff --git a/src/app.ts b/src/app.ts index 5f96f940bb..5451744c7a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,7 +6,8 @@ import bodyParser from 'body-parser'; import { errorHandler, ErrorCode } from './error'; -import passport from './auth/passport'; +import extractToken from './middleware/extract-token'; +import authenticate from './middleware/authenticate'; import activityRouter from './routes/activity'; import authRouter from './routes/auth'; @@ -38,7 +39,8 @@ import notFoundHandler from './routes/not-found'; const app = express() .disable('x-powered-by') .use(bodyParser.json()) - .use(passport.initialize()) + .use(extractToken) + .use(authenticate) .use('/activity', activityRouter) .use('/auth', authRouter) .use('/collection_presets', collectionPresetsRouter) diff --git a/src/middleware/authenticate.ts b/src/middleware/authenticate.ts new file mode 100644 index 0000000000..2fcc61e1f2 --- /dev/null +++ b/src/middleware/authenticate.ts @@ -0,0 +1,31 @@ +import { RequestHandler } from 'express'; +import jwt from 'jsonwebtoken'; +import isJWT from '../utils/is-jwt'; +import database from '../database'; + +const authenticate: RequestHandler = async (req, res, next) => { + if (!req.token) return next(); + + if (isJWT(req.token)) { + const payload = jwt.verify(req.token, process.env.SECRET) as { id: string }; + const user = await database + .select('role') + .from('directus_users') + .where({ id: payload.id }) + .first(); + /** @TODO verify user status */ + req.user = payload.id; + req.role = user.role; + return next(); + } + + /** + * @TODO + * Implement static tokens + * + * We'll silently ignore wrong tokens. This makes sure we prevent brute-forcing static tokens + */ + return next(); +}; + +export default authenticate; diff --git a/src/middleware/extract-token.ts b/src/middleware/extract-token.ts new file mode 100644 index 0000000000..efa5f55593 --- /dev/null +++ b/src/middleware/extract-token.ts @@ -0,0 +1,39 @@ +/** + * Extract access token from: + * + * Authorization: Bearer + * access_token query parameter + * + * and store in req.token + */ + +import { RequestHandler } from 'express'; + +const extractToken: RequestHandler = (req, res, next) => { + let token: string | null = null; + + if (req.query && req.query.access_token) { + token = req.query.access_token as string; + } + + if (req.headers && req.headers.authorization) { + const parts = req.headers.authorization.split(' '); + + if (parts.length === 2 && parts[0] === 'Bearer') { + token = parts[1]; + } + } + + /** + * @TODO + * Look into RFC6750 compliance: + * In order to be fully compliant with RFC6750, we have to throw a 400 error when you have the + * token in more than 1 place afaik. We also might have to support "access_token" as a post body + * key + */ + + req.token = token; + next(); +}; + +export default extractToken; From f8d64dab3968f2ef12cacbcbfd29aec139315f2d Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 17:30:47 -0400 Subject: [PATCH 14/16] Start on Grant / oAUTH flow --- example.env | 31 +++- package-lock.json | 219 ++++++++++++++++++++++++++++ package.json | 5 + src/routes/auth.ts | 18 +++ src/types/grant.d.ts | 4 + src/utils/get-email-from-profile.ts | 24 +++ src/utils/get-grant-config.ts | 38 +++++ 7 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 src/types/grant.d.ts create mode 100644 src/utils/get-email-from-profile.ts create mode 100644 src/utils/get-grant-config.ts diff --git a/example.env b/example.env index 123102faa9..486ab87256 100644 --- a/example.env +++ b/example.env @@ -1,12 +1,12 @@ +#################################################################################################### # General + PORT=3000 +PUBLIC_URL="http://localhost:3000" -# Auth -SECRET="abcdef" -ACCESS_TOKEN_EXPIRY_TIME="15m" -REFRESH_TOKEN_EXPIRY_TIME="7d" - +#################################################################################################### # Database + DB_CLIENT="pg" DB_HOST="localhost" DB_PORT=5432 @@ -14,10 +14,31 @@ DB_NAME="directus" DB_USER="postgres" DB_PASSWORD="psql1234" +#################################################################################################### +# Auth + +SECRET="abcdef" +ACCESS_TOKEN_EXPIRY_TIME="15m" +REFRESH_TOKEN_EXPIRY_TIME="7d" + +#################################################################################################### +# SSO (oAuth) Providers + +OAUTH_PROVIDERS="github,facebook" + +OAUTH_GITHUB_KEY="abcdef" +OAUTH_GITHUB_SECRET="ghijkl" +OAUTH_FACEBOOK_KEY="abcdef" +OAUTH_FACEBOOK_SECRET="ghijkl" + +#################################################################################################### # Extensions + EXTENSIONS_PATH="./extensions" +#################################################################################################### # Email + EMAIL_TRANSPORT="sendmail" ## Email (Sendmail Transport) diff --git a/package-lock.json b/package-lock.json index 933fecaecf..8594531200 100644 --- a/package-lock.json +++ b/package-lock.json @@ -157,6 +157,16 @@ "@types/range-parser": "*" } }, + "@types/express-session": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.0.tgz", + "integrity": "sha512-OQEHeBFE1UhChVIBhRh9qElHUvTp4BzKKHxMDkGHT7WuYk5eL93hPG7D8YAIkoBSbhNEY0RjreF15zn+U0eLjA==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/node": "*" + } + }, "@types/hapi__joi": { "version": "17.1.2", "resolved": "https://registry.npmjs.org/@types/hapi__joi/-/hapi__joi-17.1.2.tgz", @@ -172,6 +182,12 @@ "@types/node": "*" } }, + "@types/lodash": { + "version": "4.14.156", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.156.tgz", + "integrity": "sha512-l2AgHXcKUwx2DsvP19wtRPqZ4NkONjmorOdq4sMcxIjqdIuuV/ULo2ftuv4NUpevwfW7Ju/UKLqo0ZXuEt/8lQ==", + "dev": true + }, "@types/mime": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz", @@ -461,6 +477,18 @@ "safer-buffer": "~2.1.0" } }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "optional": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -608,6 +636,12 @@ } } }, + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "optional": true + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -657,6 +691,12 @@ "fill-range": "^7.0.1" } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "optional": true + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -1154,6 +1194,21 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "optional": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1541,6 +1596,46 @@ "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.1.4.tgz", "integrity": "sha512-HdmbVF4V4w1q/iz++RV7bUxIeepTukWewiJGkoCKQMtvPF11MLTa7It9PRc/reysXXZSEyD4Pthchju+IUbMiQ==" }, + "express-session": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.1.tgz", + "integrity": "sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q==", + "requires": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.0", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2165,6 +2260,46 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, + "grant": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/grant/-/grant-5.2.0.tgz", + "integrity": "sha512-XLB6H5CYp/A4+fw7CFBYLA6Q+ayHsZHgUO7+SD+VIgSeQI4wdpW5ZA+vWd2bwlKTccuCWNuEOJBneFuQd/xOUg==", + "requires": { + "jwk-to-pem": "^2.0.3", + "jws": "^4.0.0", + "qs": "^6.9.4", + "request-compose": "^2.1.0", + "request-oauth": "^1.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" + } + } + }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -2242,6 +2377,27 @@ } } }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "optional": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -2720,6 +2876,17 @@ "safe-buffer": "^5.0.1" } }, + "jwk-to-pem": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.4.tgz", + "integrity": "sha512-4CCK9UBHNWjWtfSHdyu3I6rA8vlN5cWqnVuwY0cOMyXtw6M1tP+yrM8GZpwk+P932Dc3cLag4d35B6CqyIf89A==", + "optional": true, + "requires": { + "asn1.js": "^5.3.0", + "elliptic": "^6.5.3", + "safe-buffer": "^5.0.1" + } + }, "jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -3262,6 +3429,18 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "optional": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "optional": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -3649,6 +3828,11 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4127,6 +4311,11 @@ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz", "integrity": "sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A==" }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -4330,6 +4519,28 @@ } } }, + "request-compose": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/request-compose/-/request-compose-2.1.0.tgz", + "integrity": "sha512-mIWvU9HA2whb/fHcqeQ0LQXAImCGISqUPyjuFF2rALhym2Fu4ebZGv7wxFA78rsJO/fn2OeEaK54TSjwSwRAFw==" + }, + "request-oauth": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/request-oauth/-/request-oauth-1.0.0.tgz", + "integrity": "sha512-wsDzIq1Qu2itLDlcpFph8xh5Q+FVyUj4os5zdQTlZL/JvZYF/qOyaawVPsxxhDG4QwCB3tzSFprj6dkjqR+e8w==", + "requires": { + "oauth-sign": "^0.9.0", + "qs": "^6.9.3", + "uuid": "^3.4.0" + }, + "dependencies": { + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" + } + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5320,6 +5531,14 @@ "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", "dev": true }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", diff --git a/package.json b/package.json index 9ef069c8bd..c733bb0051 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,10 @@ "devDependencies": { "@types/atob": "^2.1.2", "@types/express": "^4.17.6", + "@types/express-session": "^1.17.0", "@types/hapi__joi": "^17.1.2", "@types/jsonwebtoken": "^8.5.0", + "@types/lodash": "^4.14.156", "@types/nodemailer": "^6.4.0", "@types/pino": "^6.3.0", "copyfiles": "^2.3.0", @@ -63,10 +65,13 @@ "dotenv": "^8.2.0", "express": "^4.17.1", "express-async-handler": "^1.1.4", + "express-session": "^1.17.1", "get-port": "^5.1.1", + "grant": "^5.2.0", "jsonwebtoken": "^8.5.1", "knex": "^0.21.1", "liquidjs": "^9.12.0", + "lodash": "^4.17.15", "mssql": "^6.2.0", "mysql": "^2.18.1", "nodemailer": "^6.4.10", diff --git a/src/routes/auth.ts b/src/routes/auth.ts index 8d3e818629..55a36e0320 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -1,7 +1,10 @@ import { Router } from 'express'; +import session from 'express-session'; import asyncHandler from 'express-async-handler'; import Joi from '@hapi/joi'; import * as AuthService from '../services/auth'; +import grant from 'grant'; +import getGrantConfig from '../utils/get-grant-config'; const router = Router(); @@ -24,4 +27,19 @@ router.post( }) ); +router.use('/sso', session({ secret: process.env.SECRET, saveUninitialized: true, resave: false })); + +router.use(grant.express()(getGrantConfig())); + +router.get('/sso/:provider/callback', (req, res) => { + console.log(req.session.grant); + + /** + * @TODO + * + */ + + res.send(req.session.grant); +}); + export default router; diff --git a/src/types/grant.d.ts b/src/types/grant.d.ts new file mode 100644 index 0000000000..d91e1ffcb7 --- /dev/null +++ b/src/types/grant.d.ts @@ -0,0 +1,4 @@ +declare module 'grant' { + const grant: any; + export default grant; +} diff --git a/src/utils/get-email-from-profile.ts b/src/utils/get-email-from-profile.ts new file mode 100644 index 0000000000..b67eee5f9a --- /dev/null +++ b/src/utils/get-email-from-profile.ts @@ -0,0 +1,24 @@ +import { get } from 'lodash'; + +// The path in JSON to fetch the email address from the profile. +const profileMap = { + github: 'email', +}; + +/** + * Extract the email address from a given user profile coming from a providers API + * + * Falls back to OAUTH__PROFILE_EMAIL if we don't have it preconfigured yet + * + * This is used in the SSO flow to extract the users + */ +export default function getEmailFromProfile(provider: string, profile: Record) { + const path = + profileMap[provider] || process.env[`OAUTH_${provider.toUpperCase()}_PROFILE_EMAIL`]; + + if (!path) { + throw new Error('Path to email in profile object is unknown.'); + } + + return get(profile, path); +} diff --git a/src/utils/get-grant-config.ts b/src/utils/get-grant-config.ts new file mode 100644 index 0000000000..2d3ece030b --- /dev/null +++ b/src/utils/get-grant-config.ts @@ -0,0 +1,38 @@ +/** + * Reads the environment variables to construct the configuration object required by Grant + */ +export default function getGrantConfig() { + const enabledProviders = process.env.OAUTH_PROVIDERS.split(',').map((provider) => + provider.trim() + ); + + const config: any = { + defaults: { + origin: process.env.PUBLIC_URL, + transport: 'session', + prefix: '/auth/sso', + response: ['tokens', 'profile'], + }, + }; + + for (const [key, value] of Object.entries(process.env)) { + if (key.startsWith('OAUTH') === false) continue; + + const parts = key.split('_'); + const provider = parts[1].toLowerCase(); + + if (enabledProviders.includes(provider) === false) continue; + + // OAUTH SETTING = VALUE + parts.splice(0, 2); + + const configKey = parts.join('_').toLowerCase(); + + config[provider] = { + ...(config[provider] || {}), + [configKey]: value, + }; + } + + return config; +} From 8e871904fbf4718b3b38dabeda2044d76f276d06 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 23 Jun 2020 17:37:55 -0400 Subject: [PATCH 15/16] Wrap up oAuth flow --- src/routes/auth.ts | 25 +++++++++++++++++-------- src/services/auth.ts | 13 ++++++++++--- src/utils/get-email-from-profile.ts | 20 ++++++++++++-------- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/routes/auth.ts b/src/routes/auth.ts index 55a36e0320..60a1add323 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -5,6 +5,7 @@ import Joi from '@hapi/joi'; import * as AuthService from '../services/auth'; import grant from 'grant'; import getGrantConfig from '../utils/get-grant-config'; +import getEmailFromProfile from '../utils/get-email-from-profile'; const router = Router(); @@ -19,6 +20,12 @@ router.post( await loginSchema.validateAsync(req.body); const { email, password } = req.body; + /** + * @TODO + * Make sure to validate the payload. AuthService.authenticate's password is optional which + * means there's a possible problem when req.body.password is undefined + */ + const token = await AuthService.authenticate(email, password); return res.status(200).json({ @@ -31,15 +38,17 @@ router.use('/sso', session({ secret: process.env.SECRET, saveUninitialized: true router.use(grant.express()(getGrantConfig())); -router.get('/sso/:provider/callback', (req, res) => { - console.log(req.session.grant); +router.get( + '/sso/:provider/callback', + asyncHandler(async (req, res) => { + const email = getEmailFromProfile(req.params.provider, req.session.grant.response.profile); - /** - * @TODO - * - */ + const token = await AuthService.authenticate(email); - res.send(req.session.grant); -}); + return res.status(200).json({ + data: { token }, + }); + }) +); export default router; diff --git a/src/services/auth.ts b/src/services/auth.ts index 1cb6cacf40..5e3ebc402e 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -2,7 +2,7 @@ import database from '../database'; import APIError, { ErrorCode } from '../error'; import jwt from 'jsonwebtoken'; -export const authenticate = async (email: string, password: string) => { +export const authenticate = async (email: string, password?: string) => { const user = await database .select('id', 'password', 'role') .from('directus_users') @@ -13,8 +13,15 @@ export const authenticate = async (email: string, password: string) => { throw new APIError(ErrorCode.INVALID_USER_CREDENTIALS, 'Invalid user credentials'); } - /** @TODO implement password hash */ - if (password !== user.password) { + /** + * @NOTE + * This undefined check is on purpose so we can login through SSO without having to rely on + * password. However, this check might be a little tricky, as we don't want this login with just + * email to leak anywhere else.. We might have to make a dedicated "copy" of this function to + * signal the difference + */ + if (password !== undefined && password !== user.password) { + /** @TODO implement password hash checking */ throw new APIError(ErrorCode.INVALID_USER_CREDENTIALS, 'Invalid user credentials'); } diff --git a/src/utils/get-email-from-profile.ts b/src/utils/get-email-from-profile.ts index b67eee5f9a..7d529c7172 100644 --- a/src/utils/get-email-from-profile.ts +++ b/src/utils/get-email-from-profile.ts @@ -1,24 +1,28 @@ import { get } from 'lodash'; // The path in JSON to fetch the email address from the profile. -const profileMap = { - github: 'email', -}; +// Note: a lot of services use `email` as the path. We fall back to that as default, so no need to +// map it here +const profileMap = {}; /** * Extract the email address from a given user profile coming from a providers API * - * Falls back to OAUTH__PROFILE_EMAIL if we don't have it preconfigured yet + * Falls back to OAUTH__PROFILE_EMAIL if we don't have it preconfigured yet, and defaults + * to `email` if nothing is set * * This is used in the SSO flow to extract the users */ export default function getEmailFromProfile(provider: string, profile: Record) { const path = - profileMap[provider] || process.env[`OAUTH_${provider.toUpperCase()}_PROFILE_EMAIL`]; + profileMap[provider] || + process.env[`OAUTH_${provider.toUpperCase()}_PROFILE_EMAIL`] || + 'email'; + const email = get(profile, path); - if (!path) { - throw new Error('Path to email in profile object is unknown.'); + if (!email) { + throw new Error("Couldn't extract email address from SSO provider response"); } - return get(profile, path); + return email; } From 7146c13528009f440427868eb5d6a9d376814a5c Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Wed, 24 Jun 2020 12:37:23 -0400 Subject: [PATCH 16/16] Handle token expired error properly --- src/error.ts | 12 ++++++++++++ src/middleware/authenticate.ts | 5 +++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/error.ts b/src/error.ts index 8c58f83908..f27f71557b 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,5 +1,6 @@ import { ErrorRequestHandler } from 'express'; import { ValidationError } from '@hapi/joi'; +import { TokenExpiredError } from 'jsonwebtoken'; import logger from './logger'; export enum ErrorCode { @@ -13,6 +14,7 @@ export enum ErrorCode { USER_NOT_FOUND = 'USER_NOT_FOUND', FAILED_VALIDATION = 'FAILED_VALIDATION', MALFORMED_JSON = 'MALFORMED_JSON', + TOKEN_EXPIRED = 'TOKEN_EXPIRED', } enum HTTPStatus { @@ -26,6 +28,7 @@ enum HTTPStatus { USER_NOT_FOUND = 401, FAILED_VALIDATION = 422, MALFORMED_JSON = 400, + TOKEN_EXPIRED = 401, } export const errorHandler: ErrorRequestHandler = ( @@ -58,6 +61,15 @@ export const errorHandler: ErrorRequestHandler = ( message: error.message, }, }; + } else if (error instanceof TokenExpiredError) { + logger.debug(error); + res.status(HTTPStatus.TOKEN_EXPIRED); + response = { + error: { + code: ErrorCode.TOKEN_EXPIRED, + message: 'The provided token is expired.', + }, + }; } // Syntax errors are most likely thrown by Body Parser when misaligned JSON is sent to the API diff --git a/src/middleware/authenticate.ts b/src/middleware/authenticate.ts index 2fcc61e1f2..f2c69bab29 100644 --- a/src/middleware/authenticate.ts +++ b/src/middleware/authenticate.ts @@ -2,8 +2,9 @@ import { RequestHandler } from 'express'; import jwt from 'jsonwebtoken'; import isJWT from '../utils/is-jwt'; import database from '../database'; +import asyncHandler from 'express-async-handler'; -const authenticate: RequestHandler = async (req, res, next) => { +const authenticate: RequestHandler = asyncHandler(async (req, res, next) => { if (!req.token) return next(); if (isJWT(req.token)) { @@ -26,6 +27,6 @@ const authenticate: RequestHandler = async (req, res, next) => { * We'll silently ignore wrong tokens. This makes sure we prevent brute-forcing static tokens */ return next(); -}; +}); export default authenticate;