├── .gitignore ├── LICENSE ├── api ├── authenticate │ ├── routes │ │ └── postUser.js │ └── schemas │ │ └── postUser.js ├── check │ ├── routes │ │ └── postUser.js │ └── schemas │ │ └── postUser.js ├── instructors │ ├── model │ │ └── Instructor.js │ ├── routes │ │ ├── deleteInstructor.js │ │ ├── getInstructor.js │ │ ├── getInstructors.js │ │ └── postInstructor.js │ └── schemas │ │ ├── deleteInstructor.js │ │ ├── getInstructor.js │ │ └── postInstructor.js └── users │ ├── model │ └── User.js │ ├── routes │ ├── getUsers.js │ ├── postUser.js │ └── updateUser.js │ └── schemas │ ├── postUser.js │ └── updateUser.js ├── package-lock.json ├── package.json ├── readme.md ├── server.js └── util ├── createGravatar.js ├── token.js └── userFunctions.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .env -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ryan Chenkie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/authenticate/routes/postUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const postUserSchema = require('../schemas/postUser'); 4 | const verifyCredentials = require('../../../util/userFunctions') 5 | .verifyCredentials; 6 | const createToken = require('../../../util/token'); 7 | 8 | module.exports = { 9 | method: 'POST', 10 | path: '/api/users/authenticate', 11 | config: { 12 | auth: false, 13 | // Check the user's password against the DB 14 | pre: [{ method: verifyCredentials, assign: 'user' }], 15 | handler: (req, res) => { 16 | // If the user's password is correct, we can issue a token. 17 | // If it was incorrect, the error will bubble up from the pre method 18 | res({ token: createToken(req.pre.user) }); 19 | }, 20 | validate: { 21 | payload: postUserSchema 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /api/authenticate/schemas/postUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Joi = require('joi'); 4 | 5 | const authenticateUserSchema = Joi.alternatives().try( 6 | Joi.object({ 7 | user: Joi.string().alphanum().min(2).max(30).required(), 8 | password: Joi.string().required() 9 | }), 10 | Joi.object({ 11 | user: Joi.string().email().required(), 12 | password: Joi.string().required() 13 | }) 14 | ); 15 | 16 | module.exports = authenticateUserSchema; 17 | -------------------------------------------------------------------------------- /api/check/routes/postUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const postUserSchema = require('../schemas/postUser'); 4 | const verifyUniqueUser = require('../../../util/userFunctions') 5 | .verifyUniqueUser; 6 | 7 | module.exports = { 8 | method: 'POST', 9 | path: '/api/users/check', 10 | config: { 11 | auth: false, 12 | pre: [{ method: verifyUniqueUser, assign: 'user' }], 13 | handler: (req, res) => { 14 | res(req.pre.user); 15 | }, 16 | // Validate the payload against the Joi schema 17 | validate: { 18 | payload: postUserSchema 19 | } 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /api/check/schemas/postUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Joi = require('joi'); 4 | 5 | const checkUserSchema = Joi.object({ 6 | username: Joi.string(), 7 | email: Joi.string() 8 | }); 9 | 10 | module.exports = checkUserSchema; 11 | -------------------------------------------------------------------------------- /api/instructors/model/Instructor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const Schema = mongoose.Schema; 5 | 6 | const instructorModel = new Schema({ 7 | first_name: { type: String, required: true }, 8 | last_name: { type: String, required: true }, 9 | email: { type: String, required: true }, 10 | company: { type: String, required: false } 11 | }); 12 | 13 | module.exports = mongoose.model('Instructor', instructorModel); 14 | -------------------------------------------------------------------------------- /api/instructors/routes/deleteInstructor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Boom = require('boom'); 4 | const Instructor = require('../model/Instructor'); 5 | const deleteInstructorSchema = require('../schemas/deleteInstructor'); 6 | 7 | module.exports = { 8 | method: 'DELETE', 9 | path: '/api/instructors/{id}', 10 | config: { 11 | auth: { 12 | strategy: 'jwt', 13 | scope: ['admin'] 14 | }, 15 | handler: (req, res) => { 16 | const _id = req.params.id; 17 | 18 | Instructor.findOneAndRemove({ _id }).exec((err, data) => { 19 | if (err) { 20 | throw Boom.badRequest(err); 21 | } 22 | 23 | if (!data) { 24 | throw Boom.notFound('Instructor not found!'); 25 | } 26 | 27 | res({ message: 'Instructor deleted!' }); 28 | }); 29 | }, 30 | // Validate the payload against the Joi schema 31 | validate: { 32 | params: deleteInstructorSchema 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /api/instructors/routes/getInstructor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Instructor = require('../model/Instructor'); 4 | const Boom = require('boom'); 5 | 6 | module.exports = { 7 | method: 'GET', 8 | path: '/api/instructors/{id}', 9 | config: { 10 | auth: { 11 | strategy: 'jwt' 12 | }, 13 | handler: (req, res) => { 14 | const _id = req.params.id; 15 | 16 | Instructor.findOne({ _id }) 17 | // Deselect the password and version fields 18 | .select('-__v') 19 | .exec((err, data) => { 20 | if (err) { 21 | res(Boom.badRequest(err)); 22 | } 23 | if (!data) { 24 | res(Boom.notFound('Instructor not found!')); 25 | } 26 | res(data); 27 | }); 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /api/instructors/routes/getInstructors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Instructor = require('../model/Instructor'); 4 | const Boom = require('boom'); 5 | 6 | module.exports = { 7 | method: 'GET', 8 | path: '/api/instructors', 9 | config: { 10 | auth: { 11 | strategy: 'jwt' 12 | }, 13 | handler: (req, res) => { 14 | Instructor.find() 15 | // Deselect the password and version fields 16 | .select('-__v') 17 | .exec((err, data) => { 18 | if (err) { 19 | throw Boom.badRequest(err); 20 | } 21 | if (!data.length) { 22 | throw Boom.notFound('No instructors found!'); 23 | } 24 | 25 | res(data); 26 | }); 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /api/instructors/routes/postInstructor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Boom = require('boom'); 4 | const Instructor = require('../model/Instructor'); 5 | const postInstructorSchema = require('../schemas/postInstructor'); 6 | 7 | module.exports = { 8 | method: 'POST', 9 | path: '/api/instructors', 10 | config: { 11 | auth: { 12 | strategy: 'jwt', 13 | scope: ['admin'] 14 | }, 15 | handler: (req, res) => { 16 | let instructor = new Instructor(req.payload); 17 | 18 | instructor.save((err, data) => { 19 | if (err) { 20 | res(Boom.badRequest(err)); 21 | return; 22 | } 23 | 24 | res({ message: 'Instructor created!', data }).code(201); 25 | }); 26 | }, 27 | // Validate the payload against the Joi schema 28 | validate: { 29 | payload: postInstructorSchema 30 | } 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /api/instructors/schemas/deleteInstructor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Joi = require('joi'); 4 | Joi.objectId = require('joi-objectid')(Joi); 5 | 6 | const deleteInstructorSchema = Joi.object({ 7 | id: Joi.objectId().required() 8 | }); 9 | 10 | module.exports = deleteInstructorSchema; 11 | -------------------------------------------------------------------------------- /api/instructors/schemas/getInstructor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Joi = require('joi'); 4 | Joi.objectId = require('joi-objectid')(Joi); 5 | 6 | const getInstructorSchema = Joi.object({ 7 | id: Joi.objectId().required() 8 | }); 9 | 10 | module.exports = getInstructorSchema; 11 | -------------------------------------------------------------------------------- /api/instructors/schemas/postInstructor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Joi = require('joi'); 4 | 5 | const postInstructorSchema = Joi.object({ 6 | first_name: Joi.string().required(), 7 | last_name: Joi.string().required(), 8 | email: Joi.string().email().required(), 9 | company: Joi.string().required() 10 | }); 11 | 12 | module.exports = postInstructorSchema; 13 | -------------------------------------------------------------------------------- /api/users/model/User.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const Schema = mongoose.Schema; 5 | 6 | const userModel = new Schema({ 7 | email: { type: String, required: true, index: { unique: true } }, 8 | username: { type: String, required: true, index: { unique: true } }, 9 | password: { type: String, required: true }, 10 | admin: { type: Boolean, required: true } 11 | }); 12 | 13 | module.exports = mongoose.model('User', userModel); 14 | -------------------------------------------------------------------------------- /api/users/routes/getUsers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const User = require('../model/User'); 4 | const Boom = require('boom'); 5 | 6 | module.exports = { 7 | method: 'GET', 8 | path: '/api/users', 9 | config: { 10 | auth: false, 11 | handler: (req, res) => { 12 | User.find() 13 | // Deselect the password and version fields 14 | .select('-password -__v') 15 | .exec((err, users) => { 16 | if (err) { 17 | throw Boom.badRequest(err); 18 | } 19 | if (!users.length) { 20 | throw Boom.notFound('No users found!'); 21 | } 22 | res(users); 23 | }); 24 | } 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /api/users/routes/postUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const bcrypt = require('bcrypt'); 4 | const Boom = require('boom'); 5 | const User = require('../model/User'); 6 | const postUserSchema = require('../schemas/postUser'); 7 | const verifyUniqueUser = require('../../../util/userFunctions') 8 | .verifyUniqueUser; 9 | const createToken = require('../../../util/token'); 10 | 11 | function hashPassword(password, cb) { 12 | // Generate a salt at level 10 strength 13 | bcrypt.genSalt(10, (err, salt) => { 14 | bcrypt.hash(password, salt, (err, hash) => { 15 | return cb(err, hash); 16 | }); 17 | }); 18 | } 19 | 20 | module.exports = { 21 | method: 'POST', 22 | path: '/api/users', 23 | config: { 24 | auth: false, 25 | // Before the route handler runs, verify that the user is unique 26 | pre: [{ method: verifyUniqueUser }], 27 | handler: (req, res) => { 28 | let user = new User(); 29 | user.email = req.payload.email; 30 | user.username = req.payload.username; 31 | user.admin = req.payload.admin || false; 32 | hashPassword(req.payload.password, (err, hash) => { 33 | if (err) { 34 | throw Boom.badRequest(err); 35 | } 36 | user.password = hash; 37 | user.save((err, user) => { 38 | if (err) { 39 | throw Boom.badRequest(err); 40 | } 41 | // If the user is saved successfully, issue a JWT 42 | res({ token: createToken(user) }); 43 | }); 44 | }); 45 | }, 46 | // Validate the payload against the Joi schema 47 | validate: { 48 | payload: postUserSchema 49 | } 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /api/users/routes/updateUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Boom = require('boom'); 4 | const User = require('../model/User'); 5 | const updateUserSchema = require('../schemas/updateUser'); 6 | const verifyUniqueUser = require('../../../util/userFunctions') 7 | .verifyUniqueUser; 8 | 9 | module.exports = { 10 | method: 'PATCH', 11 | path: '/api/users/{id}', 12 | config: { 13 | pre: [{ method: verifyUniqueUser, assign: 'user' }], 14 | auth: { 15 | strategy: 'jwt', 16 | scope: ['admin'] 17 | }, 18 | handler: (req, res) => { 19 | const id = req.params.id; 20 | User.findOneAndUpdate({ _id: id }, req.pre.user, (err, user) => { 21 | if (err) { 22 | throw Boom.badRequest(err); 23 | } 24 | if (!user) { 25 | throw Boom.notFound('User not found!'); 26 | } 27 | res({ message: 'User updated!' }); 28 | }); 29 | }, 30 | validate: { 31 | payload: updateUserSchema.payloadSchema, 32 | params: updateUserSchema.paramsSchema 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /api/users/schemas/postUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Joi = require('joi'); 4 | 5 | const createUserSchema = Joi.object({ 6 | username: Joi.string().alphanum().min(2).max(30).required(), 7 | email: Joi.string().email().required(), 8 | password: Joi.string().required(), 9 | admin: Joi.boolean() 10 | }); 11 | 12 | module.exports = createUserSchema; 13 | -------------------------------------------------------------------------------- /api/users/schemas/updateUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Joi = require('joi'); 4 | Joi.objectId = require('joi-objectid')(Joi); 5 | 6 | const payloadSchema = Joi.object({ 7 | username: Joi.string().alphanum().min(2).max(30), 8 | email: Joi.string().email(), 9 | admin: Joi.boolean() 10 | }); 11 | 12 | const paramsSchema = Joi.object({ 13 | id: Joi.objectId().required() 14 | }); 15 | 16 | module.exports = { 17 | payloadSchema: payloadSchema, 18 | paramsSchema: paramsSchema 19 | }; 20 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user-authentication-api", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "abbrev": { 8 | "version": "1.1.1", 9 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 10 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 11 | }, 12 | "ansi-regex": { 13 | "version": "2.1.1", 14 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 15 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 16 | }, 17 | "aproba": { 18 | "version": "1.2.0", 19 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 20 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 21 | }, 22 | "are-we-there-yet": { 23 | "version": "1.1.5", 24 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", 25 | "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", 26 | "requires": { 27 | "delegates": "^1.0.0", 28 | "readable-stream": "^2.0.6" 29 | } 30 | }, 31 | "balanced-match": { 32 | "version": "1.0.0", 33 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 34 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 35 | }, 36 | "bcrypt": { 37 | "version": "5.0.0", 38 | "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.0.tgz", 39 | "integrity": "sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg==", 40 | "requires": { 41 | "node-addon-api": "^3.0.0", 42 | "node-pre-gyp": "0.15.0" 43 | } 44 | }, 45 | "bl": { 46 | "version": "2.2.1", 47 | "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", 48 | "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", 49 | "requires": { 50 | "readable-stream": "^2.3.5", 51 | "safe-buffer": "^5.1.1" 52 | } 53 | }, 54 | "bluebird": { 55 | "version": "3.5.1", 56 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 57 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 58 | }, 59 | "boom": { 60 | "version": "3.2.2", 61 | "resolved": "https://registry.npmjs.org/boom/-/boom-3.2.2.tgz", 62 | "integrity": "sha1-DwzF0ErcUAO4x9cfQsynJx/vDng=", 63 | "requires": { 64 | "hoek": "4.x.x" 65 | } 66 | }, 67 | "brace-expansion": { 68 | "version": "1.1.11", 69 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 70 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 71 | "requires": { 72 | "balanced-match": "^1.0.0", 73 | "concat-map": "0.0.1" 74 | } 75 | }, 76 | "bson": { 77 | "version": "1.1.5", 78 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", 79 | "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" 80 | }, 81 | "buffer-equal-constant-time": { 82 | "version": "1.0.1", 83 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 84 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 85 | }, 86 | "charenc": { 87 | "version": "0.0.2", 88 | "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", 89 | "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" 90 | }, 91 | "chownr": { 92 | "version": "1.1.4", 93 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 94 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 95 | }, 96 | "code-point-at": { 97 | "version": "1.1.0", 98 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 99 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 100 | }, 101 | "concat-map": { 102 | "version": "0.0.1", 103 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 104 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 105 | }, 106 | "console-control-strings": { 107 | "version": "1.1.0", 108 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 109 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 110 | }, 111 | "core-util-is": { 112 | "version": "1.0.2", 113 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 114 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 115 | }, 116 | "crypt": { 117 | "version": "0.0.2", 118 | "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", 119 | "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" 120 | }, 121 | "debug": { 122 | "version": "3.2.6", 123 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 124 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 125 | "requires": { 126 | "ms": "^2.1.1" 127 | } 128 | }, 129 | "deep-extend": { 130 | "version": "0.6.0", 131 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 132 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 133 | }, 134 | "delegates": { 135 | "version": "1.0.0", 136 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 137 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 138 | }, 139 | "denque": { 140 | "version": "1.4.1", 141 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", 142 | "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" 143 | }, 144 | "detect-libc": { 145 | "version": "1.0.3", 146 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 147 | "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" 148 | }, 149 | "dotenv": { 150 | "version": "8.2.0", 151 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 152 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 153 | }, 154 | "ecdsa-sig-formatter": { 155 | "version": "1.0.11", 156 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 157 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 158 | "requires": { 159 | "safe-buffer": "^5.0.1" 160 | } 161 | }, 162 | "fs-minipass": { 163 | "version": "1.2.7", 164 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", 165 | "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", 166 | "requires": { 167 | "minipass": "^2.6.0" 168 | } 169 | }, 170 | "fs.realpath": { 171 | "version": "1.0.0", 172 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 173 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 174 | }, 175 | "gauge": { 176 | "version": "2.7.4", 177 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 178 | "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 179 | "requires": { 180 | "aproba": "^1.0.3", 181 | "console-control-strings": "^1.0.0", 182 | "has-unicode": "^2.0.0", 183 | "object-assign": "^4.1.0", 184 | "signal-exit": "^3.0.0", 185 | "string-width": "^1.0.1", 186 | "strip-ansi": "^3.0.1", 187 | "wide-align": "^1.1.0" 188 | } 189 | }, 190 | "glob": { 191 | "version": "7.1.6", 192 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 193 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 194 | "requires": { 195 | "fs.realpath": "^1.0.0", 196 | "inflight": "^1.0.4", 197 | "inherits": "2", 198 | "minimatch": "^3.0.4", 199 | "once": "^1.3.0", 200 | "path-is-absolute": "^1.0.0" 201 | } 202 | }, 203 | "hapi": { 204 | "version": "13.5.3", 205 | "resolved": "https://registry.npmjs.org/hapi/-/hapi-13.5.3.tgz", 206 | "integrity": "sha1-gYtdneXokGveaMoL0ZqTZWQTwCg=", 207 | "requires": { 208 | "accept": "2.x.x", 209 | "ammo": "2.x.x", 210 | "boom": "3.x.x", 211 | "call": "3.x.x", 212 | "catbox": "7.x.x", 213 | "catbox-memory": "2.x.x", 214 | "cryptiles": "3.x.x", 215 | "heavy": "4.x.x", 216 | "hoek": "4.x.x", 217 | "iron": "4.x.x", 218 | "items": "2.x.x", 219 | "joi": "8.x.x", 220 | "kilt": "2.x.x", 221 | "mimos": "3.x.x", 222 | "peekaboo": "2.x.x", 223 | "shot": "3.x.x", 224 | "statehood": "4.x.x", 225 | "subtext": "4.x.x", 226 | "topo": "2.x.x" 227 | }, 228 | "dependencies": { 229 | "accept": { 230 | "version": "2.1.1", 231 | "resolved": "https://registry.npmjs.org/accept/-/accept-2.1.1.tgz", 232 | "integrity": "sha1-vJxvGDwBKdxy9yWIV+fMj0idD2E=", 233 | "requires": { 234 | "boom": "3.x.x", 235 | "hoek": "4.x.x" 236 | } 237 | }, 238 | "ammo": { 239 | "version": "2.0.1", 240 | "resolved": "https://registry.npmjs.org/ammo/-/ammo-2.0.1.tgz", 241 | "integrity": "sha1-agfseOgSneLf01G6bnmDULJqMt8=", 242 | "requires": { 243 | "boom": "3.x.x", 244 | "hoek": "4.x.x" 245 | } 246 | }, 247 | "boom": { 248 | "version": "3.2.0", 249 | "resolved": "https://registry.npmjs.org/boom/-/boom-3.2.0.tgz", 250 | "integrity": "sha1-Z92RhQy0gV4FGqXBi1eKS5o7/eg=", 251 | "requires": { 252 | "hoek": "4.x.x" 253 | } 254 | }, 255 | "call": { 256 | "version": "3.0.2", 257 | "resolved": "https://registry.npmjs.org/call/-/call-3.0.2.tgz", 258 | "integrity": "sha1-y+/QTvwaB7kdxAllDrcKsuDSpx4=", 259 | "requires": { 260 | "boom": "3.x.x", 261 | "hoek": "4.x.x" 262 | } 263 | }, 264 | "catbox": { 265 | "version": "7.1.1", 266 | "resolved": "https://registry.npmjs.org/catbox/-/catbox-7.1.1.tgz", 267 | "integrity": "sha1-KBkzXfpFs3JgubkZN+mWttCg09I=", 268 | "requires": { 269 | "boom": "3.x.x", 270 | "hoek": "4.x.x", 271 | "joi": "8.x.x" 272 | } 273 | }, 274 | "catbox-memory": { 275 | "version": "2.0.2", 276 | "resolved": "https://registry.npmjs.org/catbox-memory/-/catbox-memory-2.0.2.tgz", 277 | "integrity": "sha1-XqRKtmYOUHtumE9qZW2K8sps6zs=", 278 | "requires": { 279 | "hoek": "4.x.x" 280 | } 281 | }, 282 | "cryptiles": { 283 | "version": "3.0.1", 284 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.0.1.tgz", 285 | "integrity": "sha1-4KDpAhk3Vq/Veqp2AtQ/VQczBeI=", 286 | "requires": { 287 | "boom": "3.x.x" 288 | } 289 | }, 290 | "heavy": { 291 | "version": "4.0.1", 292 | "resolved": "https://registry.npmjs.org/heavy/-/heavy-4.0.1.tgz", 293 | "integrity": "sha1-UJAaFEiCU8K5VrAHKZbI9WGHA0Y=", 294 | "requires": { 295 | "boom": "3.x.x", 296 | "hoek": "4.x.x", 297 | "joi": "8.x.x" 298 | } 299 | }, 300 | "hoek": { 301 | "version": "4.0.0", 302 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.0.0.tgz", 303 | "integrity": "sha1-NCm9h9lUj9RU5PbIVCiZropB30o=" 304 | }, 305 | "iron": { 306 | "version": "4.0.1", 307 | "resolved": "https://registry.npmjs.org/iron/-/iron-4.0.1.tgz", 308 | "integrity": "sha1-yCKCABIdU89gu5BfHN3Dxb4Akys=", 309 | "requires": { 310 | "boom": "3.x.x", 311 | "cryptiles": "3.x.x", 312 | "hoek": "4.x.x" 313 | } 314 | }, 315 | "items": { 316 | "version": "2.1.0", 317 | "resolved": "https://registry.npmjs.org/items/-/items-2.1.0.tgz", 318 | "integrity": "sha1-L9AdSJ5+xYYb8Q69td3KM4Q8hQ8=" 319 | }, 320 | "joi": { 321 | "version": "8.1.0", 322 | "resolved": "https://registry.npmjs.org/joi/-/joi-8.1.0.tgz", 323 | "integrity": "sha1-WhkJTM5oAJzhGDHzutOh4eCvotE=", 324 | "requires": { 325 | "hoek": "4.x.x", 326 | "isemail": "2.x.x", 327 | "moment": "2.x.x", 328 | "topo": "2.x.x" 329 | }, 330 | "dependencies": { 331 | "isemail": { 332 | "version": "2.1.2", 333 | "resolved": "https://registry.npmjs.org/isemail/-/isemail-2.1.2.tgz", 334 | "integrity": "sha1-YkwVWmd0MRRopQN6k/G10fFmd6Q=" 335 | }, 336 | "moment": { 337 | "version": "2.13.0", 338 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.13.0.tgz", 339 | "integrity": "sha1-JBYtmVIebUD5muaTnoBtITnqrFI=" 340 | } 341 | } 342 | }, 343 | "kilt": { 344 | "version": "2.0.1", 345 | "resolved": "https://registry.npmjs.org/kilt/-/kilt-2.0.1.tgz", 346 | "integrity": "sha1-bj33ed8bifSZgZ2pJS5vFc0BfzM=", 347 | "requires": { 348 | "hoek": "4.x.x" 349 | } 350 | }, 351 | "mimos": { 352 | "version": "3.0.1", 353 | "resolved": "https://registry.npmjs.org/mimos/-/mimos-3.0.1.tgz", 354 | "integrity": "sha1-o/H/CaELVllHNDrDNyP1JGfWlfQ=", 355 | "requires": { 356 | "hoek": "4.x.x", 357 | "mime-db": "1.x.x" 358 | }, 359 | "dependencies": { 360 | "mime-db": { 361 | "version": "1.23.0", 362 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz", 363 | "integrity": "sha1-oxtAcK2uon1zLqMzdApk0OyaZlk=" 364 | } 365 | } 366 | }, 367 | "peekaboo": { 368 | "version": "2.0.1", 369 | "resolved": "https://registry.npmjs.org/peekaboo/-/peekaboo-2.0.1.tgz", 370 | "integrity": "sha1-EQXxouquXY/p8In2/ummYCqCsus=" 371 | }, 372 | "shot": { 373 | "version": "3.1.0", 374 | "resolved": "https://registry.npmjs.org/shot/-/shot-3.1.0.tgz", 375 | "integrity": "sha1-kvOMYIWECwZNPBzZ14hC/FxPSDw=" 376 | }, 377 | "statehood": { 378 | "version": "4.0.1", 379 | "resolved": "https://registry.npmjs.org/statehood/-/statehood-4.0.1.tgz", 380 | "integrity": "sha1-hc6QUwTDCZi6zRwL93h/qKvGCVg=", 381 | "requires": { 382 | "boom": "3.x.x", 383 | "cryptiles": "3.x.x", 384 | "hoek": "4.x.x", 385 | "iron": "4.x.x", 386 | "items": "2.x.x", 387 | "joi": "8.x.x" 388 | } 389 | }, 390 | "subtext": { 391 | "version": "4.0.3", 392 | "resolved": "https://registry.npmjs.org/subtext/-/subtext-4.0.3.tgz", 393 | "integrity": "sha1-XF2cVMxl/292f/Bzu4fsITUwhSQ=", 394 | "requires": { 395 | "boom": "3.x.x", 396 | "content": "3.x.x", 397 | "hoek": "4.x.x", 398 | "pez": "2.x.x", 399 | "wreck": "7.x.x" 400 | }, 401 | "dependencies": { 402 | "content": { 403 | "version": "3.0.1", 404 | "resolved": "https://registry.npmjs.org/content/-/content-3.0.1.tgz", 405 | "integrity": "sha1-5PXwSKSw6kMiDEHADNOJWsBl8so=", 406 | "requires": { 407 | "boom": "3.x.x" 408 | } 409 | }, 410 | "pez": { 411 | "version": "2.1.1", 412 | "resolved": "https://registry.npmjs.org/pez/-/pez-2.1.1.tgz", 413 | "integrity": "sha1-S1OYWcfr5v1pO5bh7UQriSxy7fI=", 414 | "requires": { 415 | "b64": "3.x.x", 416 | "boom": "3.x.x", 417 | "content": "3.x.x", 418 | "hoek": "4.x.x", 419 | "nigel": "2.x.x" 420 | }, 421 | "dependencies": { 422 | "b64": { 423 | "version": "3.0.1", 424 | "resolved": "https://registry.npmjs.org/b64/-/b64-3.0.1.tgz", 425 | "integrity": "sha1-F9ez+b8IG1/zGviRc55pUo1yYFo=", 426 | "requires": { 427 | "hoek": "4.x.x" 428 | } 429 | }, 430 | "nigel": { 431 | "version": "2.0.1", 432 | "resolved": "https://registry.npmjs.org/nigel/-/nigel-2.0.1.tgz", 433 | "integrity": "sha1-ujcV/GMZsR6dYjKnPbDh3KprnBo=", 434 | "requires": { 435 | "hoek": "4.x.x", 436 | "vise": "2.x.x" 437 | }, 438 | "dependencies": { 439 | "vise": { 440 | "version": "2.0.1", 441 | "resolved": "https://registry.npmjs.org/vise/-/vise-2.0.1.tgz", 442 | "integrity": "sha1-RbryamPuomyvGp06bd9xaJVQh/Q=", 443 | "requires": { 444 | "hoek": "4.x.x" 445 | } 446 | } 447 | } 448 | } 449 | } 450 | }, 451 | "wreck": { 452 | "version": "7.2.1", 453 | "resolved": "https://registry.npmjs.org/wreck/-/wreck-7.2.1.tgz", 454 | "integrity": "sha1-7s0R0NkeozTWa7PsUjFUBdpokVg=", 455 | "requires": { 456 | "boom": "3.x.x", 457 | "hoek": "4.x.x" 458 | } 459 | } 460 | } 461 | }, 462 | "topo": { 463 | "version": "2.0.1", 464 | "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.1.tgz", 465 | "integrity": "sha1-yh9pqDQhjCG4JquywDNSEu3gS3U=", 466 | "requires": { 467 | "hoek": "4.x.x" 468 | } 469 | } 470 | } 471 | }, 472 | "hapi-auth-jwt": { 473 | "version": "4.0.0", 474 | "resolved": "https://registry.npmjs.org/hapi-auth-jwt/-/hapi-auth-jwt-4.0.0.tgz", 475 | "integrity": "sha1-yhoN2oauyX6oOxhN/f9XwTI2ua4=", 476 | "requires": { 477 | "boom": "3.x.x", 478 | "hoek": "3.x.x", 479 | "jsonwebtoken": "5.x.x" 480 | }, 481 | "dependencies": { 482 | "hoek": { 483 | "version": "3.0.4", 484 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-3.0.4.tgz", 485 | "integrity": "sha1-Jorf9mu2aVxptHiaiLHghHw/MSM=" 486 | }, 487 | "jsonwebtoken": { 488 | "version": "5.7.0", 489 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-5.7.0.tgz", 490 | "integrity": "sha1-HJD5qGzlt0j1+XnBK3BAK0r83bQ=", 491 | "requires": { 492 | "jws": "^3.0.0", 493 | "ms": "^0.7.1", 494 | "xtend": "^4.0.1" 495 | } 496 | }, 497 | "ms": { 498 | "version": "0.7.3", 499 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz", 500 | "integrity": "sha1-cIFVpeROM/X9D8U+gdDUCpG+H/8=" 501 | } 502 | } 503 | }, 504 | "has-unicode": { 505 | "version": "2.0.1", 506 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 507 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 508 | }, 509 | "hoek": { 510 | "version": "4.2.1", 511 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", 512 | "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" 513 | }, 514 | "iconv-lite": { 515 | "version": "0.4.24", 516 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 517 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 518 | "requires": { 519 | "safer-buffer": ">= 2.1.2 < 3" 520 | } 521 | }, 522 | "ignore-walk": { 523 | "version": "3.0.3", 524 | "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", 525 | "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", 526 | "requires": { 527 | "minimatch": "^3.0.4" 528 | } 529 | }, 530 | "inflight": { 531 | "version": "1.0.6", 532 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 533 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 534 | "requires": { 535 | "once": "^1.3.0", 536 | "wrappy": "1" 537 | } 538 | }, 539 | "inherits": { 540 | "version": "2.0.4", 541 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 542 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 543 | }, 544 | "ini": { 545 | "version": "1.3.5", 546 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 547 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" 548 | }, 549 | "is-buffer": { 550 | "version": "1.1.6", 551 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 552 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 553 | }, 554 | "is-fullwidth-code-point": { 555 | "version": "1.0.0", 556 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 557 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 558 | "requires": { 559 | "number-is-nan": "^1.0.0" 560 | } 561 | }, 562 | "isarray": { 563 | "version": "1.0.0", 564 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 565 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 566 | }, 567 | "isemail": { 568 | "version": "3.2.0", 569 | "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", 570 | "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", 571 | "requires": { 572 | "punycode": "2.x.x" 573 | } 574 | }, 575 | "joi": { 576 | "version": "13.7.0", 577 | "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", 578 | "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", 579 | "requires": { 580 | "hoek": "5.x.x", 581 | "isemail": "3.x.x", 582 | "topo": "3.x.x" 583 | }, 584 | "dependencies": { 585 | "hoek": { 586 | "version": "5.0.4", 587 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.4.tgz", 588 | "integrity": "sha512-Alr4ZQgoMlnere5FZJsIyfIjORBqZll5POhDsF4q64dPuJR6rNxXdDxtHSQq8OXRurhmx+PWYEE8bXRROY8h0w==" 589 | } 590 | } 591 | }, 592 | "joi-objectid": { 593 | "version": "3.0.1", 594 | "resolved": "https://registry.npmjs.org/joi-objectid/-/joi-objectid-3.0.1.tgz", 595 | "integrity": "sha512-V/3hbTlGpvJ03Me6DJbdBI08hBTasFOmipsauOsxOSnsF1blxV537WTl1zPwbfcKle4AK0Ma4OPnzMH4LlvTpQ==" 596 | }, 597 | "jsonwebtoken": { 598 | "version": "8.5.1", 599 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 600 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 601 | "requires": { 602 | "jws": "^3.2.2", 603 | "lodash.includes": "^4.3.0", 604 | "lodash.isboolean": "^3.0.3", 605 | "lodash.isinteger": "^4.0.4", 606 | "lodash.isnumber": "^3.0.3", 607 | "lodash.isplainobject": "^4.0.6", 608 | "lodash.isstring": "^4.0.1", 609 | "lodash.once": "^4.0.0", 610 | "ms": "^2.1.1", 611 | "semver": "^5.6.0" 612 | } 613 | }, 614 | "jwa": { 615 | "version": "1.4.1", 616 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 617 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 618 | "requires": { 619 | "buffer-equal-constant-time": "1.0.1", 620 | "ecdsa-sig-formatter": "1.0.11", 621 | "safe-buffer": "^5.0.1" 622 | } 623 | }, 624 | "jws": { 625 | "version": "3.2.2", 626 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 627 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 628 | "requires": { 629 | "jwa": "^1.4.1", 630 | "safe-buffer": "^5.0.1" 631 | } 632 | }, 633 | "kareem": { 634 | "version": "2.3.1", 635 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", 636 | "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" 637 | }, 638 | "lodash.includes": { 639 | "version": "4.3.0", 640 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 641 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 642 | }, 643 | "lodash.isboolean": { 644 | "version": "3.0.3", 645 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 646 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 647 | }, 648 | "lodash.isinteger": { 649 | "version": "4.0.4", 650 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 651 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 652 | }, 653 | "lodash.isnumber": { 654 | "version": "3.0.3", 655 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 656 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 657 | }, 658 | "lodash.isplainobject": { 659 | "version": "4.0.6", 660 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 661 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 662 | }, 663 | "lodash.isstring": { 664 | "version": "4.0.1", 665 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 666 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 667 | }, 668 | "lodash.once": { 669 | "version": "4.1.1", 670 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 671 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 672 | }, 673 | "md5": { 674 | "version": "2.3.0", 675 | "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", 676 | "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", 677 | "requires": { 678 | "charenc": "0.0.2", 679 | "crypt": "0.0.2", 680 | "is-buffer": "~1.1.6" 681 | } 682 | }, 683 | "memory-pager": { 684 | "version": "1.5.0", 685 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 686 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 687 | "optional": true 688 | }, 689 | "minimatch": { 690 | "version": "3.0.4", 691 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 692 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 693 | "requires": { 694 | "brace-expansion": "^1.1.7" 695 | } 696 | }, 697 | "minimist": { 698 | "version": "1.2.5", 699 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 700 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 701 | }, 702 | "minipass": { 703 | "version": "2.9.0", 704 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", 705 | "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", 706 | "requires": { 707 | "safe-buffer": "^5.1.2", 708 | "yallist": "^3.0.0" 709 | } 710 | }, 711 | "minizlib": { 712 | "version": "1.3.3", 713 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", 714 | "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", 715 | "requires": { 716 | "minipass": "^2.9.0" 717 | } 718 | }, 719 | "mkdirp": { 720 | "version": "0.5.5", 721 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 722 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 723 | "requires": { 724 | "minimist": "^1.2.5" 725 | } 726 | }, 727 | "mongodb": { 728 | "version": "3.6.2", 729 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.2.tgz", 730 | "integrity": "sha512-sSZOb04w3HcnrrXC82NEh/YGCmBuRgR+C1hZgmmv4L6dBz4BkRse6Y8/q/neXer9i95fKUBbFi4KgeceXmbsOA==", 731 | "requires": { 732 | "bl": "^2.2.1", 733 | "bson": "^1.1.4", 734 | "denque": "^1.4.1", 735 | "require_optional": "^1.0.1", 736 | "safe-buffer": "^5.1.2", 737 | "saslprep": "^1.0.0" 738 | } 739 | }, 740 | "mongoose": { 741 | "version": "5.10.7", 742 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.7.tgz", 743 | "integrity": "sha512-oiofFrD4I5p3PhJXn49QyrU1nX5CY01qhPkfMMrXYPhkfGLEJVwFVO+0PsCxD91A2kQP+d/iFyk5U8e86KI8eQ==", 744 | "requires": { 745 | "bson": "^1.1.4", 746 | "kareem": "2.3.1", 747 | "mongodb": "3.6.2", 748 | "mongoose-legacy-pluralize": "1.0.2", 749 | "mpath": "0.7.0", 750 | "mquery": "3.2.2", 751 | "ms": "2.1.2", 752 | "regexp-clone": "1.0.0", 753 | "safe-buffer": "5.2.1", 754 | "sift": "7.0.1", 755 | "sliced": "1.0.1" 756 | }, 757 | "dependencies": { 758 | "safe-buffer": { 759 | "version": "5.2.1", 760 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 761 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 762 | } 763 | } 764 | }, 765 | "mongoose-legacy-pluralize": { 766 | "version": "1.0.2", 767 | "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", 768 | "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" 769 | }, 770 | "mpath": { 771 | "version": "0.7.0", 772 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz", 773 | "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==" 774 | }, 775 | "mquery": { 776 | "version": "3.2.2", 777 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", 778 | "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", 779 | "requires": { 780 | "bluebird": "3.5.1", 781 | "debug": "3.1.0", 782 | "regexp-clone": "^1.0.0", 783 | "safe-buffer": "5.1.2", 784 | "sliced": "1.0.1" 785 | }, 786 | "dependencies": { 787 | "debug": { 788 | "version": "3.1.0", 789 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 790 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 791 | "requires": { 792 | "ms": "2.0.0" 793 | } 794 | }, 795 | "ms": { 796 | "version": "2.0.0", 797 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 798 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 799 | } 800 | } 801 | }, 802 | "ms": { 803 | "version": "2.1.2", 804 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 805 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 806 | }, 807 | "needle": { 808 | "version": "2.5.2", 809 | "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz", 810 | "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==", 811 | "requires": { 812 | "debug": "^3.2.6", 813 | "iconv-lite": "^0.4.4", 814 | "sax": "^1.2.4" 815 | } 816 | }, 817 | "node-addon-api": { 818 | "version": "3.0.2", 819 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.2.tgz", 820 | "integrity": "sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg==" 821 | }, 822 | "node-pre-gyp": { 823 | "version": "0.15.0", 824 | "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", 825 | "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", 826 | "requires": { 827 | "detect-libc": "^1.0.2", 828 | "mkdirp": "^0.5.3", 829 | "needle": "^2.5.0", 830 | "nopt": "^4.0.1", 831 | "npm-packlist": "^1.1.6", 832 | "npmlog": "^4.0.2", 833 | "rc": "^1.2.7", 834 | "rimraf": "^2.6.1", 835 | "semver": "^5.3.0", 836 | "tar": "^4.4.2" 837 | } 838 | }, 839 | "nopt": { 840 | "version": "4.0.3", 841 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", 842 | "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", 843 | "requires": { 844 | "abbrev": "1", 845 | "osenv": "^0.1.4" 846 | } 847 | }, 848 | "npm-bundled": { 849 | "version": "1.1.1", 850 | "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", 851 | "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", 852 | "requires": { 853 | "npm-normalize-package-bin": "^1.0.1" 854 | } 855 | }, 856 | "npm-normalize-package-bin": { 857 | "version": "1.0.1", 858 | "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", 859 | "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" 860 | }, 861 | "npm-packlist": { 862 | "version": "1.4.8", 863 | "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", 864 | "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", 865 | "requires": { 866 | "ignore-walk": "^3.0.1", 867 | "npm-bundled": "^1.0.1", 868 | "npm-normalize-package-bin": "^1.0.1" 869 | } 870 | }, 871 | "npmlog": { 872 | "version": "4.1.2", 873 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 874 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 875 | "requires": { 876 | "are-we-there-yet": "~1.1.2", 877 | "console-control-strings": "~1.1.0", 878 | "gauge": "~2.7.3", 879 | "set-blocking": "~2.0.0" 880 | } 881 | }, 882 | "number-is-nan": { 883 | "version": "1.0.1", 884 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 885 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 886 | }, 887 | "object-assign": { 888 | "version": "4.1.1", 889 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 890 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 891 | }, 892 | "once": { 893 | "version": "1.4.0", 894 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 895 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 896 | "requires": { 897 | "wrappy": "1" 898 | } 899 | }, 900 | "os-homedir": { 901 | "version": "1.0.2", 902 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 903 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" 904 | }, 905 | "os-tmpdir": { 906 | "version": "1.0.2", 907 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 908 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 909 | }, 910 | "osenv": { 911 | "version": "0.1.5", 912 | "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", 913 | "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", 914 | "requires": { 915 | "os-homedir": "^1.0.0", 916 | "os-tmpdir": "^1.0.0" 917 | } 918 | }, 919 | "path-is-absolute": { 920 | "version": "1.0.1", 921 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 922 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 923 | }, 924 | "process-nextick-args": { 925 | "version": "2.0.1", 926 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 927 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 928 | }, 929 | "punycode": { 930 | "version": "2.1.1", 931 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 932 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 933 | }, 934 | "rc": { 935 | "version": "1.2.8", 936 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 937 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 938 | "requires": { 939 | "deep-extend": "^0.6.0", 940 | "ini": "~1.3.0", 941 | "minimist": "^1.2.0", 942 | "strip-json-comments": "~2.0.1" 943 | } 944 | }, 945 | "readable-stream": { 946 | "version": "2.3.7", 947 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 948 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 949 | "requires": { 950 | "core-util-is": "~1.0.0", 951 | "inherits": "~2.0.3", 952 | "isarray": "~1.0.0", 953 | "process-nextick-args": "~2.0.0", 954 | "safe-buffer": "~5.1.1", 955 | "string_decoder": "~1.1.1", 956 | "util-deprecate": "~1.0.1" 957 | } 958 | }, 959 | "regexp-clone": { 960 | "version": "1.0.0", 961 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", 962 | "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" 963 | }, 964 | "require_optional": { 965 | "version": "1.0.1", 966 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 967 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", 968 | "requires": { 969 | "resolve-from": "^2.0.0", 970 | "semver": "^5.1.0" 971 | } 972 | }, 973 | "resolve-from": { 974 | "version": "2.0.0", 975 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 976 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 977 | }, 978 | "rimraf": { 979 | "version": "2.7.1", 980 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 981 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 982 | "requires": { 983 | "glob": "^7.1.3" 984 | } 985 | }, 986 | "safe-buffer": { 987 | "version": "5.1.2", 988 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 989 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 990 | }, 991 | "safer-buffer": { 992 | "version": "2.1.2", 993 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 994 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 995 | }, 996 | "saslprep": { 997 | "version": "1.0.3", 998 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 999 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 1000 | "optional": true, 1001 | "requires": { 1002 | "sparse-bitfield": "^3.0.3" 1003 | } 1004 | }, 1005 | "sax": { 1006 | "version": "1.2.4", 1007 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 1008 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 1009 | }, 1010 | "semver": { 1011 | "version": "5.7.1", 1012 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1013 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 1014 | }, 1015 | "set-blocking": { 1016 | "version": "2.0.0", 1017 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1018 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 1019 | }, 1020 | "sift": { 1021 | "version": "7.0.1", 1022 | "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", 1023 | "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" 1024 | }, 1025 | "signal-exit": { 1026 | "version": "3.0.3", 1027 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 1028 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" 1029 | }, 1030 | "sliced": { 1031 | "version": "1.0.1", 1032 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 1033 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 1034 | }, 1035 | "sparse-bitfield": { 1036 | "version": "3.0.3", 1037 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 1038 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 1039 | "optional": true, 1040 | "requires": { 1041 | "memory-pager": "^1.0.2" 1042 | } 1043 | }, 1044 | "string-width": { 1045 | "version": "1.0.2", 1046 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1047 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1048 | "requires": { 1049 | "code-point-at": "^1.0.0", 1050 | "is-fullwidth-code-point": "^1.0.0", 1051 | "strip-ansi": "^3.0.0" 1052 | } 1053 | }, 1054 | "string_decoder": { 1055 | "version": "1.1.1", 1056 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1057 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1058 | "requires": { 1059 | "safe-buffer": "~5.1.0" 1060 | } 1061 | }, 1062 | "strip-ansi": { 1063 | "version": "3.0.1", 1064 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1065 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1066 | "requires": { 1067 | "ansi-regex": "^2.0.0" 1068 | } 1069 | }, 1070 | "strip-json-comments": { 1071 | "version": "2.0.1", 1072 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1073 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" 1074 | }, 1075 | "tar": { 1076 | "version": "4.4.13", 1077 | "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", 1078 | "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", 1079 | "requires": { 1080 | "chownr": "^1.1.1", 1081 | "fs-minipass": "^1.2.5", 1082 | "minipass": "^2.8.6", 1083 | "minizlib": "^1.2.1", 1084 | "mkdirp": "^0.5.0", 1085 | "safe-buffer": "^5.1.2", 1086 | "yallist": "^3.0.3" 1087 | } 1088 | }, 1089 | "topo": { 1090 | "version": "3.0.3", 1091 | "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", 1092 | "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", 1093 | "requires": { 1094 | "hoek": "6.x.x" 1095 | }, 1096 | "dependencies": { 1097 | "hoek": { 1098 | "version": "6.1.3", 1099 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", 1100 | "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" 1101 | } 1102 | } 1103 | }, 1104 | "util-deprecate": { 1105 | "version": "1.0.2", 1106 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1107 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1108 | }, 1109 | "wide-align": { 1110 | "version": "1.1.3", 1111 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 1112 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 1113 | "requires": { 1114 | "string-width": "^1.0.2 || 2" 1115 | } 1116 | }, 1117 | "wrappy": { 1118 | "version": "1.0.2", 1119 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1120 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1121 | }, 1122 | "xtend": { 1123 | "version": "4.0.2", 1124 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 1125 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" 1126 | }, 1127 | "yallist": { 1128 | "version": "3.1.1", 1129 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 1130 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 1131 | } 1132 | } 1133 | } 1134 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user-authentication-api", 3 | "version": "0.0.0", 4 | "description": "Sample API for protecting routes with JWT authentication", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "dev": "nodemon server.js" 9 | }, 10 | "keywords": [ 11 | "authentication", 12 | "jwt", 13 | "api" 14 | ], 15 | "author": "Auth0", 16 | "license": "MIT", 17 | "dependencies": { 18 | "bcrypt": "^5.0.0", 19 | "boom": "^3.2.2", 20 | "dotenv": "^8.2.0", 21 | "glob": "^7.1.6", 22 | "hapi": "^13.5.3", 23 | "hapi-auth-jwt": "^4.0.0", 24 | "joi": "^13.0.0", 25 | "joi-objectid": "^3.0.1", 26 | "jsonwebtoken": "^8.5.1", 27 | "md5": "^2.3.0", 28 | "mongoose": "^5.10.7" 29 | }, 30 | "prettier": { 31 | "singleQuote": true, 32 | "arrowParens": "avoid", 33 | "trailingComma": "none" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # HapiJS Authentication Sample 2 | 3 | This sample project demonstrates how to set up a user authentication API with Hapi.js using JSON Web Tokens. There are several endpoints exposed in the sample, including user login and signup, along with an example of a protected `instructors` resource. 4 | 5 | ## Demo API 6 | 7 | The API for the course is served at **https://user-authentication-api.now.sh/api** 8 | 9 | ## Installation and Running the App 10 | 11 | Clone the repo, then: 12 | 13 | ```bash 14 | npm install 15 | node server.js 16 | ``` 17 | 18 | The app will be served at `localhost:3001`. 19 | 20 | ## Local Setup 21 | 22 | To setup the API locally, you will need to run MongoDB or have an MLab instance. Create a `.env` file and populate it with the following values: 23 | 24 | ```bash 25 | SECRET_KEY= 26 | MLAB_USER= 27 | MLAB_PASSWORD= 28 | MLAB_DOMAIN= 29 | MLAB_DB= 30 | ``` 31 | 32 | ## Available Routes 33 | 34 | #### **POST** `/api/users` 35 | * Used for signing up a user. Accepts `username`, `email`, and `password` to create a user. Returns a JWT. 36 | 37 | #### **POST** `/api/users/authenticate` 38 | * Used for logging a user in. Accepts `user` (where you can supply a users `username` or `email`) and `password` to authenticate a user. Returns a JWT. 39 | 40 | #### **GET** `/api/users` 41 | * Returns all users in the database. Requires a valid JWT with an `admin` scope. 42 | 43 | #### **PATCH** `/api/user/{id}` 44 | * Updates a user. Requires a valid JWT with an `admin` scope. 45 | 46 | #### **GET** `/api/instructors` 47 | * Returns all `instructors` in the database. Requires a valid JWT. 48 | 49 | #### **GET** `/api/instructors/{id}` 50 | * Returns a specific `instructor` in the database. Requires a valid JWT. 51 | 52 | #### **POST** `/api/instructors` 53 | * Saves a new `instructor` in the database. Requires a valid JWT with an `admin` scope. 54 | 55 | #### **DELETE** `/api/instructors/{id}` 56 | * Deletes an instructor with a specific `id`. Requires a valid JWT with an `admin` scope. 57 | 58 | #### **POST** `/api/users/check` 59 | * Checks whether a user already exists or not. Useful for doing async form validation. 60 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('dotenv').config(); 4 | const Hapi = require('hapi'); 5 | const Boom = require('boom'); 6 | const mongoose = require('mongoose'); 7 | const glob = require('glob'); 8 | const path = require('path'); 9 | 10 | const server = new Hapi.Server(); 11 | 12 | // The connection object takes some 13 | // configuration, including the port 14 | server.connection({ port: process.env.PORT || 3001, routes: { cors: true } }); 15 | 16 | server.register(require('hapi-auth-jwt'), err => { 17 | // We are giving the strategy a name of 'jwt' 18 | server.auth.strategy('jwt', 'jwt', 'required', { 19 | key: process.env.SECRET_KEY, 20 | verifyOptions: { algorithms: ['HS256'] } 21 | }); 22 | 23 | // Look through the routes in 24 | // all the subdirectories of API 25 | // and create a new route for each 26 | glob.sync('api/**/routes/*.js', { root: __dirname }).forEach(file => { 27 | const route = require(path.join(__dirname, file)); 28 | if (route.method && route.path) { 29 | server.route(route); 30 | } 31 | }); 32 | }); 33 | 34 | // Start the server 35 | server.start(err => { 36 | if (err) { 37 | throw err; 38 | } 39 | // Once started, connect to Mongo through Mongoose 40 | mongoose.connect( 41 | process.env.MLAB_URL, 42 | { useNewUrlParser: true, useUnifiedTopology: true }, 43 | err => { 44 | if (err) { 45 | throw err; 46 | } 47 | } 48 | ); 49 | 50 | console.info(`Server started at ${server.info.uri}`); 51 | }); 52 | -------------------------------------------------------------------------------- /util/createGravatar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const md5 = require('md5'); 4 | 5 | function createGravatarUrl(email) { 6 | return `https://www.gravatar.com/avatar/${md5(email).toLowerCase().trim()}`; 7 | } 8 | 9 | module.exports = createGravatarUrl; 10 | -------------------------------------------------------------------------------- /util/token.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const secret = process.env.SECRET_KEY; 4 | const jwt = require('jsonwebtoken'); 5 | const createGravatarUrl = require('./createGravatar'); 6 | 7 | function createToken(user) { 8 | let scope; 9 | // Check if the user object passed in 10 | // has admin set to true, and if so, set 11 | // scopes to admin 12 | if (user.admin) { 13 | scope = 'admin'; 14 | } 15 | 16 | // Sign the JWT 17 | return jwt.sign( 18 | { 19 | sub: user.id, 20 | username: user.username, 21 | email: user.email, 22 | role: 'admin', 23 | gravatar: createGravatarUrl(user.email), 24 | scope 25 | }, 26 | secret, 27 | { 28 | algorithm: 'HS256', 29 | expiresIn: '1h' 30 | } 31 | ); 32 | } 33 | 34 | module.exports = createToken; 35 | -------------------------------------------------------------------------------- /util/userFunctions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Boom = require('boom'); 4 | const User = require('../api/users/model/User'); 5 | const bcrypt = require('bcrypt'); 6 | 7 | function verifyUniqueUser(req, res) { 8 | // Find an entry from the database that 9 | // matches either the email or username 10 | User.findOne( 11 | { 12 | $or: [{ email: req.payload.email }, { username: req.payload.username }] 13 | }, 14 | (err, user) => { 15 | // Check whether the username or email 16 | // is already taken and error out if so 17 | if (user) { 18 | if (user.username === req.payload.username) { 19 | res(Boom.badRequest('Username taken')); 20 | return; 21 | } 22 | if (user.email === req.payload.email) { 23 | res(Boom.badRequest('Email taken')); 24 | return; 25 | } 26 | } 27 | // If everything checks out, send the payload through 28 | // to the route handler 29 | res(req.payload); 30 | } 31 | ); 32 | } 33 | 34 | function verifyCredentials(req, res) { 35 | const password = req.payload.password; 36 | 37 | // Find an entry from the database that 38 | // matches either the email or username 39 | User.findOne( 40 | { 41 | $or: [{ email: req.payload.user }, { username: req.payload.user }] 42 | }, 43 | (err, user) => { 44 | if (user) { 45 | bcrypt.compare(password, user.password, (err, isValid) => { 46 | if (isValid) { 47 | res(user); 48 | } else { 49 | res(Boom.badRequest('Incorrect password!')); 50 | } 51 | }); 52 | } else { 53 | res(Boom.badRequest('Incorrect username or email!')); 54 | } 55 | } 56 | ); 57 | } 58 | 59 | module.exports = { 60 | verifyUniqueUser: verifyUniqueUser, 61 | verifyCredentials: verifyCredentials 62 | }; 63 | --------------------------------------------------------------------------------