From f914016bfaba3b23df837e6e0825e575e9ce89e0 Mon Sep 17 00:00:00 2001 From: Tim Willis Date: Fri, 26 Jul 2019 11:42:43 -0700 Subject: [PATCH 1/4] Completing first few items --- api/models/user.js | 18 ++ api/routes/auth.js | 66 ++++++ app.js | 1 + db/seeds.js | 23 +- nodemon.sample.json | 3 +- package-lock.json | 500 ++++++++++++++++++++++++++++++++++++++++---- package.json | 2 + readme.md | 8 +- 8 files changed, 576 insertions(+), 45 deletions(-) create mode 100644 api/models/user.js create mode 100644 api/routes/auth.js diff --git a/api/models/user.js b/api/models/user.js new file mode 100644 index 0000000..7a366fe --- /dev/null +++ b/api/models/user.js @@ -0,0 +1,18 @@ +const mongoose = require('mongoose') + +const schema = mongoose.Schema({ + username: { + type: String, + required: true + }, + password : { + type: String, + requried: true + }, + admin: { + type: Boolean, + default: false + } +}) + +module.exports = mongoose.model('User', schema) \ No newline at end of file diff --git a/api/routes/auth.js b/api/routes/auth.js new file mode 100644 index 0000000..d1221e8 --- /dev/null +++ b/api/routes/auth.js @@ -0,0 +1,66 @@ +const { SECRET_KEY } = process.env +const router = require('express').Router() +const User = require('../models/user') +const bcrypt = require('bcrypt') +const jsonwebtoken = require('jsonwebtoken') + +router.post('/signup', async (req,res, next) => { + const status = 201 + + try { + const { username, password } = req.body + + //throw errors if malformed input + if (!(username && password)) throw new Error(`Username & password are required!`) + if (password.length < 8) throw new Error(`Please choose a longer password`) + + const guest = await User.findOne({username}) + //if user already exists, throw error + if (guest) throw new Error(`User: ${username} already exists!`) + + //store user in database + const saltRounds = 10 + const hashed = await bcrypt.hash(password, saltRounds) + await User.create({ + username, + password: hashed + }) + console.log(`User ${username} created!`) + + //return success + const payload = { id: guest._id } //setup payload + const options = { expiresIn: '1 day' } //add expiration + const token = jsonwebtoken.sign(payload, SECRET_KEY, options) //create token + + res.status(status).json({status, token}) + + } catch (e) { + e.status = 400 + next(e) + } +}) + +router.post('/login', async (req, res, next)=> { + const status = 201 + try{ + const { username, password } = req.body + //throw error if no username + if (!username) throw new Error(`Username required to login`) + + const guest = await User.findOne({username}) + const isValid = bcrypt.compare(password, guest.password) + //throw error if issue with username/password + if (!isValid) throw new Error(`Username and password do not match`) + + const payload = { id: guest._id } + const options = { expiresIn: '1 day' } + const token = jsonwebtoken.sign(paylod, SECRET_KEY, options) + res.status(status).json({status, token}) + } catch (e) { + e.status = 401 + next(e) + } +}) + + +module.exports = router \ No newline at end of file diff --git a/app.js b/app.js index 075ce75..707a2c8 100644 --- a/app.js +++ b/app.js @@ -17,6 +17,7 @@ app.use(require('body-parser').json()) // Routes app.use('/api/books', require('./api/routes/books')) +app.use('/api', require('./api/routes/auth')) // Not Found Handler app.use((req, res, next) => { diff --git a/db/seeds.js b/db/seeds.js index 3ea4a6a..2d526f7 100644 --- a/db/seeds.js +++ b/db/seeds.js @@ -1,11 +1,26 @@ const mongoose = require('mongoose') const Book = require('../api/models/book') +const User = require('../api/models/user') const config = require('../nodemon.json') +const bcrypt = require('bcrypt') +const saltrounds = 10 const reset = async () => { mongoose.connect(config.env.MONGO_DB_CONNECTION, { useNewUrlParser: true }) - await Book.deleteMany() // Deletes all records - return await Book.create([ + await Book.deleteMany() // Deletes book all records + await User.deleteMany() // Deletes user all records + const users = await User.create([ + { + username: 'Admin', + password: await bcrypt.hash('strongpassword', saltrounds), + admin: true + }, + { + username: 'User', + password: await bcrypt.hash('weakpassword', saltrounds), + } + ]) + const books = await Book.create([ { title: 'The Colour of Magic', published: 1983, @@ -41,9 +56,11 @@ const reset = async () => { ] } ]) + + return { users, books } } reset().catch(console.error).then((response) => { - console.log(`Seeds successful! ${response.length} records created.`) + console.log(`Seeds successful! ${response.users.length} users created, ${response.books.length} book records created.`) return mongoose.disconnect() }) \ No newline at end of file diff --git a/nodemon.sample.json b/nodemon.sample.json index e388adc..2455320 100644 --- a/nodemon.sample.json +++ b/nodemon.sample.json @@ -2,6 +2,7 @@ "env": { "MONGO_DB_CONNECTION": "", "NODE_ENV": "development", - "PORT": 5000 + "PORT": 5000, + "SECRET_KEY": "SUPERSECRETKEY" } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c94533b..c44b81d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,8 +7,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { "version": "1.3.7", @@ -31,8 +30,7 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "ansi-styles": { "version": "3.2.1", @@ -64,6 +62,20 @@ } } }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -122,8 +134,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -189,6 +200,22 @@ "safe-buffer": "5.1.2" } }, + "bcrypt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.6.tgz", + "integrity": "sha512-taA5bCTfXe7FUjKroKky9EXpdhkVvhE5owfxfLYodbrAR1Ul3juLmIQmIQBK4L9a5BuUcE6cqmwT+Da20lF9tg==", + "requires": { + "nan": "2.13.2", + "node-pre-gyp": "0.12.0" + }, + "dependencies": { + "nan": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==" + } + } + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -236,7 +263,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -276,6 +302,11 @@ "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.1.tgz", "integrity": "sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg==" }, + "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", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -341,6 +372,11 @@ "upath": "^1.1.1" } }, + "chownr": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" + }, "ci-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", @@ -376,6 +412,11 @@ "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", "dev": true }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -410,8 +451,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "configstore": { "version": "3.1.2", @@ -427,6 +467,11 @@ "xdg-basedir": "^3.0.0" } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -459,8 +504,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "create-error-class": { "version": "3.0.2", @@ -505,8 +549,7 @@ "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "define-property": { "version": "2.0.2", @@ -549,6 +592,11 @@ } } }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -559,6 +607,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -574,6 +627,14 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -835,6 +896,19 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-minipass": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", + "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, "fsevents": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", @@ -1383,6 +1457,54 @@ } } }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", @@ -1395,6 +1517,19 @@ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -1456,6 +1591,11 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -1514,6 +1654,14 @@ "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", "dev": true }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -1526,6 +1674,15 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", @@ -1534,8 +1691,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "ipaddr.js": { "version": "1.9.0", @@ -1640,8 +1796,7 @@ "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 + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-glob": { "version": "4.0.1", @@ -1739,8 +1894,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -1754,6 +1908,49 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, + "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" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kareem": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.0.tgz", @@ -1779,6 +1976,41 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, + "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=" + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -1883,7 +2115,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1891,8 +2122,31 @@ "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + } + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "requires": { + "minipass": "^2.2.1" + } }, "mixin-deep": { "version": "1.3.2", @@ -1915,6 +2169,21 @@ } } }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, "mongodb": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.2.7.tgz", @@ -2038,11 +2307,64 @@ "to-regex": "^3.0.1" } }, + "needle": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "node-pre-gyp": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + }, + "dependencies": { + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + } + } + }, "nodemon": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.1.tgz", @@ -2093,6 +2415,20 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "npm-bundled": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" + }, + "npm-packlist": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz", + "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -2102,6 +2438,27 @@ "path-key": "^2.0.0" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -2165,6 +2522,33 @@ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", "dev": true }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -2203,8 +2587,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -2244,8 +2627,7 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "proxy-addr": { "version": "2.0.5", @@ -2293,7 +2675,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -2305,7 +2686,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2405,6 +2785,14 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -2433,6 +2821,11 @@ "sparse-bitfield": "^3.0.3" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "semver": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", @@ -2485,6 +2878,11 @@ "send": "0.17.1" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -2536,8 +2934,7 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "sliced": { "version": "1.0.1", @@ -2724,7 +3121,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -2734,7 +3130,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -2743,7 +3138,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -2757,8 +3151,7 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "supports-color": { "version": "5.5.0", @@ -2769,6 +3162,27 @@ "has-flag": "^3.0.0" } }, + "tar": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + } + } + }, "term-size": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", @@ -2978,8 +3392,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", @@ -3000,6 +3413,14 @@ "isexe": "^2.0.0" } }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "widest-line": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", @@ -3009,6 +3430,11 @@ "string-width": "^2.1.1" } }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, "write-file-atomic": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", diff --git a/package.json b/package.json index 869adc3..6ce2b0b 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ "author": "", "license": "ISC", "dependencies": { + "bcrypt": "^3.0.6", "body-parser": "^1.19.0", "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", "mongoose": "^5.6.0" }, "devDependencies": { diff --git a/readme.md b/readme.md index e3eed50..63f4965 100644 --- a/readme.md +++ b/readme.md @@ -42,19 +42,19 @@ Reserve a book. To complete this exercise, you will need to do the following: -- [ ] **Create a User Model:** Users have a `username`, a `password`, and an `admin` property which is set to `false` by default. +- [x] **Create a User Model:** Users have a `username`, a `password`, and an `admin` property which is set to `false` by default. -- [ ] **Create a `POST /api/signup` route:** Create a new route that allows someone to create an account. Securely store the password using the `bcrypt` package. On successful creation, return a JWT token. You should return an error in the following cases: +- [x] **Create a `POST /api/signup` route:** Create a new route that allows someone to create an account. Securely store the password using the `bcrypt` package. On successful creation, return a JWT token. You should return an error in the following cases: * Username is not provided * Username is already taken * Password is not provided * Password is less than 8 characters -- [ ] **Create a `POST /api/login` route:** Create a new route that allows someone to login. On successful creation, return a JWT token. You should return an error in the following cases: +- [x] **Create a `POST /api/login` route:** Create a new route that allows someone to login. On successful creation, return a JWT token. You should return an error in the following cases: * Username is not found * Username and password do not match -- [ ] **Add an admin User and a regular user to the `./db/seeds.js` file:** In the `seeds.js` file, when the `reset()` function is run, create a new User who has admin permissions and another User without admin permissions. Make sure that both users will be deleted and then recreated whenever the function is run. +- [x] **Add an admin User and a regular user to the `./db/seeds.js` file:** In the `seeds.js` file, when the `reset()` function is run, create a new User who has admin permissions and another User without admin permissions. Make sure that both users will be deleted and then recreated whenever the function is run. - [ ] **Create a `PATCH /api/users/:id/permissions` route:** Create a new route that allows for an admin to change permissions of another user. The route should only be looking for the `admin: ` key in the request body and setting the value appropriately. On success, return a status 204. You should return an error in the following cases: * A valid JWT token is not provided (status 401) From fca3f0dcbd28b11ada920cd3f9c8da49d69a2bfd Mon Sep 17 00:00:00 2001 From: Tim Willis Date: Tue, 30 Jul 2019 08:06:56 -0700 Subject: [PATCH 2/4] Finishing exercises --- api/routes/auth.js | 41 ++++++++++++++++- api/routes/books.js | 105 +++++++++++++++++++++++++++++++++++++------- readme.md | 8 ++-- 3 files changed, 133 insertions(+), 21 deletions(-) diff --git a/api/routes/auth.js b/api/routes/auth.js index d1221e8..d28b31b 100644 --- a/api/routes/auth.js +++ b/api/routes/auth.js @@ -54,7 +54,7 @@ router.post('/login', async (req, res, next)=> { const payload = { id: guest._id } const options = { expiresIn: '1 day' } - const token = jsonwebtoken.sign(paylod, SECRET_KEY, options) + const token = jsonwebtoken.sign(payload, SECRET_KEY, options) res.status(status).json({status, token}) } catch (e) { e.status = 401 @@ -62,5 +62,44 @@ router.post('/login', async (req, res, next)=> { } }) +router.patch('/users/:id/permissions', async (req, res, next) => { + const status = 204 + try { + //make sure request body is OK - 400 + const { permissions } = req.body + const { id } = req.params + if ( permissions !== (true || false)) throw new Error (`There was a problem with your request body`) + + //make sure headers contain auth - 401 + const token = req.headers.authorization.split('Bearer ')[1] + if (!token) throw new Error(`There was a problem with your request`) + const payload = jsonwebtoken.verify(token, SECRET_KEY) + + const user = await User.findOne({ _id: payload.id }).select('-__v -password') + //make sure request maker is an admin - 401 + const { admin } = user + if (admin !== true) throw new Error(`There was a problem with your request`) + + //make sure user exists - 404 + const updatedUser = await User.findOne({_id: id}) + if (!updatedUser) throw new Error(`User ID: ${id} does not exist!`) + + //need to verify this and make sure it is correct way to do.. + updatedUser.admin = permissions + await updatedUser.save() + + res.status(status).json({status}) + + } catch (e) { + if (e.message == 'There was a problem with your request body') { + e.status = 400 + } else if (e.message == 'There was a problem with your request') { + e.status = 401 + } else { + e.status = 404 + } + next(e) + } +}) module.exports = router \ No newline at end of file diff --git a/api/routes/books.js b/api/routes/books.js index 4ad32c8..ad4302c 100644 --- a/api/routes/books.js +++ b/api/routes/books.js @@ -1,5 +1,7 @@ +const SECRET_KEY = process.env const router = require('express').Router() const Book = require('../models/book') +const jsonwebtoken = require('jsonwebtoken') router.get('/', async (req, res, next) => { const status = 200 @@ -25,20 +27,35 @@ router.get('/:id', async (req, res, next) => { }) // You should only be able to create a book if the user is an admin +// Nested try/catch here router.post('/', async (req, res, next) => { const status = 200 try { - const book = await Book.create(req.body) - if (!book) throw new Error(`Request body failed: ${JSON.stringify(req.body)}`) - - const response = await Book.findById(book._id).select('-__v') - res.json({ status, response }) + const token = req.headers.authorization.split('Bearer ')[1] + //make sure token exists + if (!token) throw new Error (`You are not authorized`) + const payload = jsonwebtoken.verify(token, SECRET_KEY) + const user = await User.findOne({ _id: payload.id }).select('-__v -password') + if (!user || user.admin !== true) throw new Error (`You are not authorized`) + + try { + //need to insert stuff here + const book = await Book.create(req.body) + if (!book) throw new Error(`Request body failed: ${JSON.stringify(req.body)}`) + + const response = await Book.findById(book._id).select('-__v') + res.json({ status, response }) + } catch (e) { + console.error(e) + const message = 'Failure to create. Please check request body and try again.' + const error = new Error(message) + error.status = 400 + next(error) + } + } catch (e) { - console.error(e) - const message = 'Failure to create. Please check request body and try again.' - const error = new Error(message) - error.status = 400 - next(error) + e.status = 401 + next(e) } }) @@ -46,33 +63,89 @@ router.post('/', async (req, res, next) => { router.patch('/:id/reserve', async (req, res, next) => { const { id } = req.params try { + const token = req.headers.authorization.split('Bearer ')[1] + //make sure token exists + if (!token) { + const error = new Error (`You are not authorized`) + error.status = 401 + return next(error) + } + //try/catch this? + const payload = jsonwebtoken.verify(token, SECRET_KEY) + + const user = await User.findById(payload.id) + const book = await Book.findById(id) if (!book) { const error = new Error(`Invalid Book _id: ${id}`) - error.message = 404 + error.status = 404 + return next(error) + } else if (book.reserved.status === true) { + const error = new Error(`Book _id: ${id} is already reserved`) + error.status = 400 return next(error) } book.reserved.status = true // Set the reserved memberId to the current user + book.reserved.memberId = user._id await book.save() const response = await Book.findById(book._id).select('-__v') const status = 200 res.json({ status, response }) } catch (e) { + //what to put here? console.error(e) + //next (e)? } }) // You should only be able to return a book if the user is logged in // and that user is the one who reserved the book router.patch('/:id/return', async (req, res, next) => { - const status = 200 - const message = 'You must implement this route!' - - console.log(message) - res.status(status).json({ status, message }) + const status = 204 + const { id } = req.params + + try { + const token = req.headers.authorization.split('Bearer ')[1] + if (!token) { + const error = new Error (`You are not authorized`) + error.status = 401 + return next(error) + } + //try/catch this? + const payload = jsonwebtoken.verify(token, SECRET_KEY) + + const book = await Book.findById(id) + if (!book) { + const error = new Error (`Book _id: ${id} not found`) + error.status = 404 + return next(error) + } else if (book.reserved.memberId !== payload.id) { + const error = new Error (`A different user reserved this book`) + error.status = 401 + return next(error) + } else if (book.reserved !== true) { + const error = new Error(`This book is not reserved!`) + error.status = 400 + return next(error) + } + + book.reserved.status = false + book.reserved.memberId = null + + await book.save() + + const response = await Book.findById(book._id).select('-__v') + + res.status(status).json({status,response}) + + } catch (e) { + //what to put here? + console.error(e) + //next (e)? + } }) module.exports = router \ No newline at end of file diff --git a/readme.md b/readme.md index 63f4965..dc9c3ca 100644 --- a/readme.md +++ b/readme.md @@ -56,20 +56,20 @@ To complete this exercise, you will need to do the following: - [x] **Add an admin User and a regular user to the `./db/seeds.js` file:** In the `seeds.js` file, when the `reset()` function is run, create a new User who has admin permissions and another User without admin permissions. Make sure that both users will be deleted and then recreated whenever the function is run. -- [ ] **Create a `PATCH /api/users/:id/permissions` route:** Create a new route that allows for an admin to change permissions of another user. The route should only be looking for the `admin: ` key in the request body and setting the value appropriately. On success, return a status 204. You should return an error in the following cases: +- [x] **Create a `PATCH /api/users/:id/permissions` route:** Create a new route that allows for an admin to change permissions of another user. The route should only be looking for the `admin: ` key in the request body and setting the value appropriately. On success, return a status 204. You should return an error in the following cases: * A valid JWT token is not provided (status 401) * The JWT token is for a user who is not an admin (status 401) * User cannot be found (status 404) * The request body does not include an `admin` key with a boolean value (status 400) -- [ ] **Update the `POST /api/books` route:** This route should only be available to users who are admins. If the user is an admin, proceed as normal. If they are not an admin, return an error message with a status code of 401. +- [x] **Update the `POST /api/books` route:** This route should only be available to users who are admins. If the user is an admin, proceed as normal. If they are not an admin, return an error message with a status code of 401. -- [ ] **Update the `POST /api/books/:id/reserve` route:** This route allows for someone to reserve a book. If the user is logged in, proceed as normal. You should return an error in the following cases: +- [x] **Update the `POST /api/books/:id/reserve` route:** This route allows for someone to reserve a book. If the user is logged in, proceed as normal. You should return an error in the following cases: * A valid JWT token is not provided (status 401) * The book is already reserved (status 400) * Book cannot be found (status 404) -- [ ] **Create a `PATCH /api/books/:id/return` route:** This route should return a book if the user has already reserved it. If the appropriate user is returning the book, set the `reserved.status` to `false` and update the `reserved.memberId` to be `null`. You should return an error in the following cases: +- [x] **Create a `PATCH /api/books/:id/return` route:** This route should return a book if the user has already reserved it. If the appropriate user is returning the book, set the `reserved.status` to `false` and update the `reserved.memberId` to be `null`. You should return an error in the following cases: * A valid JWT token is not provided (status 401) * The book is reserved by a different user (status 401) * The book is not reserved (status 400) From 293c7ed73de0e45ab35b25df035ad3160d0a1763 Mon Sep 17 00:00:00 2001 From: Tim Willis Date: Tue, 30 Jul 2019 22:22:14 -0700 Subject: [PATCH 3/4] Minor tweaks --- api/routes/auth.js | 5 +++-- api/routes/books.js | 26 +++++++++++++------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/api/routes/auth.js b/api/routes/auth.js index d28b31b..4febd6f 100644 --- a/api/routes/auth.js +++ b/api/routes/auth.js @@ -15,20 +15,21 @@ router.post('/signup', async (req,res, next) => { if (password.length < 8) throw new Error(`Please choose a longer password`) const guest = await User.findOne({username}) + //if user already exists, throw error if (guest) throw new Error(`User: ${username} already exists!`) //store user in database const saltRounds = 10 const hashed = await bcrypt.hash(password, saltRounds) - await User.create({ + const user = await User.create({ username, password: hashed }) console.log(`User ${username} created!`) //return success - const payload = { id: guest._id } //setup payload + const payload = { id: user._id } //setup payload const options = { expiresIn: '1 day' } //add expiration const token = jsonwebtoken.sign(payload, SECRET_KEY, options) //create token diff --git a/api/routes/books.js b/api/routes/books.js index ad4302c..b58c86a 100644 --- a/api/routes/books.js +++ b/api/routes/books.js @@ -62,17 +62,17 @@ router.post('/', async (req, res, next) => { // You should only be able to reserve a book if a user is logged in router.patch('/:id/reserve', async (req, res, next) => { const { id } = req.params + const status = 200 + const token = req.headers.authorization.split('Bearer ')[1] + //make sure token exists + if (!token) { + const error = new Error (`You are not authorized`) + error.status = 401 + return next(error) + } + //try/catch try { - const token = req.headers.authorization.split('Bearer ')[1] - //make sure token exists - if (!token) { - const error = new Error (`You are not authorized`) - error.status = 401 - return next(error) - } - //try/catch this? const payload = jsonwebtoken.verify(token, SECRET_KEY) - const user = await User.findById(payload.id) const book = await Book.findById(id) @@ -92,13 +92,13 @@ router.patch('/:id/reserve', async (req, res, next) => { await book.save() const response = await Book.findById(book._id).select('-__v') - const status = 200 - res.json({ status, response }) } catch (e) { - //what to put here? console.error(e) - //next (e)? + e.status = 401 + next(e) } + + res.json({ status, response }) }) // You should only be able to return a book if the user is logged in From c06f6c38963d8313523e4bf7f37e9959506d4f24 Mon Sep 17 00:00:00 2001 From: Tim Willis Date: Sun, 11 Aug 2019 17:47:20 -0700 Subject: [PATCH 4/4] Polishing work --- api/routes/auth.js | 39 ++++++++++++++++++++++++--------------- api/routes/books.js | 30 +++++++++++++++--------------- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/api/routes/auth.js b/api/routes/auth.js index 4febd6f..230b3f7 100644 --- a/api/routes/auth.js +++ b/api/routes/auth.js @@ -47,9 +47,8 @@ router.post('/login', async (req, res, next)=> { const { username, password } = req.body //throw error if no username if (!username) throw new Error(`Username required to login`) - const guest = await User.findOne({username}) - const isValid = bcrypt.compare(password, guest.password) + const isValid = await bcrypt.compare(password, guest.password) //throw error if issue with username/password if (!isValid) throw new Error(`Username and password do not match`) @@ -67,38 +66,48 @@ router.patch('/users/:id/permissions', async (req, res, next) => { const status = 204 try { //make sure request body is OK - 400 - const { permissions } = req.body + const permissions = req.body.admin const { id } = req.params - if ( permissions !== (true || false)) throw new Error (`There was a problem with your request body`) + if ( !(permissions === "true" || permissions === "false")) { + const error = new Error(`There was a problem with your request body`) + error.status = 400 + next(error) + } //make sure headers contain auth - 401 const token = req.headers.authorization.split('Bearer ')[1] - if (!token) throw new Error(`There was a problem with your request`) + if (!token) { + const error = new Error(`There was a problem with your request`) + error.status = 401 + next(error) + } + const payload = jsonwebtoken.verify(token, SECRET_KEY) const user = await User.findOne({ _id: payload.id }).select('-__v -password') //make sure request maker is an admin - 401 const { admin } = user - if (admin !== true) throw new Error(`There was a problem with your request`) + if (admin !== true) { + const error = new Error(`There was a problem with your request`) + error.status = 401 + next(error) + } //make sure user exists - 404 const updatedUser = await User.findOne({_id: id}) - if (!updatedUser) throw new Error(`User ID: ${id} does not exist!`) + if (!updatedUser) { + const error = new Error(`User ID: ${id} does not exist!`) + error.status = 404 + next(error) + } //need to verify this and make sure it is correct way to do.. updatedUser.admin = permissions await updatedUser.save() - res.status(status).json({status}) + res.status(status).send() } catch (e) { - if (e.message == 'There was a problem with your request body') { - e.status = 400 - } else if (e.message == 'There was a problem with your request') { - e.status = 401 - } else { - e.status = 404 - } next(e) } }) diff --git a/api/routes/books.js b/api/routes/books.js index b58c86a..5be4b8d 100644 --- a/api/routes/books.js +++ b/api/routes/books.js @@ -1,6 +1,7 @@ -const SECRET_KEY = process.env +const { SECRET_KEY } = process.env const router = require('express').Router() const Book = require('../models/book') +const User = require('../models/user') const jsonwebtoken = require('jsonwebtoken') router.get('/', async (req, res, next) => { @@ -30,8 +31,10 @@ router.get('/:id', async (req, res, next) => { // Nested try/catch here router.post('/', async (req, res, next) => { const status = 200 + const { authorization } = req.headers try { - const token = req.headers.authorization.split('Bearer ')[1] + if (!authorization) throw new Error (`You are not authorized!`) + const token = authorization.split('Bearer ')[1] //make sure token exists if (!token) throw new Error (`You are not authorized`) const payload = jsonwebtoken.verify(token, SECRET_KEY) @@ -39,7 +42,6 @@ router.post('/', async (req, res, next) => { if (!user || user.admin !== true) throw new Error (`You are not authorized`) try { - //need to insert stuff here const book = await Book.create(req.body) if (!book) throw new Error(`Request body failed: ${JSON.stringify(req.body)}`) @@ -60,18 +62,18 @@ router.post('/', async (req, res, next) => { }) // You should only be able to reserve a book if a user is logged in -router.patch('/:id/reserve', async (req, res, next) => { +router.post('/:id/reserve', async (req, res, next) => { const { id } = req.params const status = 200 - const token = req.headers.authorization.split('Bearer ')[1] + const { authorization } = req.headers //make sure token exists - if (!token) { + if (! authorization) { const error = new Error (`You are not authorized`) error.status = 401 return next(error) } - //try/catch try { + const token = authorization.split('Bearer ')[1] const payload = jsonwebtoken.verify(token, SECRET_KEY) const user = await User.findById(payload.id) @@ -92,13 +94,12 @@ router.patch('/:id/reserve', async (req, res, next) => { await book.save() const response = await Book.findById(book._id).select('-__v') + res.json({ status, response }) } catch (e) { console.error(e) e.status = 401 next(e) } - - res.json({ status, response }) }) // You should only be able to return a book if the user is logged in @@ -115,18 +116,18 @@ router.patch('/:id/return', async (req, res, next) => { return next(error) } //try/catch this? - const payload = jsonwebtoken.verify(token, SECRET_KEY) - + const payload = await jsonwebtoken.verify(token, SECRET_KEY) const book = await Book.findById(id) if (!book) { const error = new Error (`Book _id: ${id} not found`) error.status = 404 return next(error) - } else if (book.reserved.memberId !== payload.id) { + } else if (book.reserved.memberId != payload.id) { + //not deep equal because string/object comparison const error = new Error (`A different user reserved this book`) error.status = 401 return next(error) - } else if (book.reserved !== true) { + } else if (book.reserved.status !== true) { const error = new Error(`This book is not reserved!`) error.status = 400 return next(error) @@ -142,9 +143,8 @@ router.patch('/:id/return', async (req, res, next) => { res.status(status).json({status,response}) } catch (e) { - //what to put here? console.error(e) - //next (e)? + next (e) } })