diff --git a/api/auth/auth-middleware.js b/api/auth/auth-middleware.js index c603d37c7..a3f2d0441 100644 --- a/api/auth/auth-middleware.js +++ b/api/auth/auth-middleware.js @@ -1,67 +1,61 @@ const { JWT_SECRET } = require("../secrets"); // use this secret! +const User = require('../users/users-model') +const jwt = require('jsonwebtoken') const restricted = (req, res, next) => { - /* - If the user does not provide a token in the Authorization header: - status 401 - { - "message": "Token required" - } - - If the provided token does not verify: - status 401 - { - "message": "Token invalid" - } - - Put the decoded token in the req object, to make life easier for middlewares downstream! - */ + const token = req.body.authorization + + if (!token) { + res.status(401).json({message: 'Token required'}) + } else { + jwt.verify(token, JWT_SECRET, (err, decoded) => { + if (err) { + res.status(401).json({message: 'Token invalid'}) + } else { + req.decodedToken = decoded + next() + } + }) + } } const only = role_name => (req, res, next) => { - /* - If the user does not provide a token in the Authorization header with a role_name - inside its payload matching the role_name passed to this function as its argument: - status 403 - { - "message": "This is not for you" - } - - Pull the decoded token from the req object, to avoid verifying it again! - */ + if (!req.decodedToken.role_name === role_name) { + res.status(403).json({message: 'This is not for you'}) + } else { + next() + } } -const checkUsernameExists = (req, res, next) => { - /* - If the username in req.body does NOT exist in the database - status 401 - { - "message": "Invalid credentials" +const checkUsernameExists = async (req, res, next) => { + try { + const user = await User.findBy({ username: req.body.username }) + if (!user) { + res.status(401).json({message: 'Invalid credentials'}) + } else { + req.user = user + next() } - */ + } catch (err) { + next(err) + } } const validateRoleName = (req, res, next) => { - /* - If the role_name in the body is valid, set req.role_name to be the trimmed string and proceed. - - If role_name is missing from req.body, or if after trimming it is just an empty string, - set req.role_name to be 'student' and allow the request to proceed. - - If role_name is 'admin' after trimming the string: - status 422 - { - "message": "Role name can not be admin" - } - - If role_name is over 32 characters after trimming the string: - status 422 - { - "message": "Role name can not be longer than 32 chars" - } - */ + + if (req.body.role_name || req.body.role_name.trim()) { + req.role_name = 'student' + next() + } else if (req.body.role_name.trim === 'admin') { + res.status(422).json({message: 'Role name can not be admin'}) + } else if (req.body.role_name.trim.length > 32) { + res.status(422).json({message: 'Role name can not be longer than 32 chars'}) + } else { + req.role_name = req.body.role_name.trim() + next() + } } module.exports = { diff --git a/api/auth/auth-router.js b/api/auth/auth-router.js index c723c2da8..b6a9e21ad 100644 --- a/api/auth/auth-router.js +++ b/api/auth/auth-router.js @@ -1,42 +1,53 @@ const router = require("express").Router(); const { checkUsernameExists, validateRoleName } = require('./auth-middleware'); const { JWT_SECRET } = require("../secrets"); // use this secret! +const bcrypt = require('bcryptjs') +const jwt = require('jsonwebtoken') +const User = require('../users/users-model') router.post("/register", validateRoleName, (req, res, next) => { - /** - [POST] /api/auth/register { "username": "anna", "password": "1234", "role_name": "angel" } - - response: - status 201 - { - "user"_id: 3, - "username": "anna", - "role_name": "angel" - } - */ + + let user = req.body + const hash = bcrypt.hashSync(user.password, 8) + user.password = hash + User.add(user) + .then(newUser => { + res.status(201).json(newUser) + next() + }) + .catch(err => { + res.status(500).json({message: err.message}) + }) }); - router.post("/login", checkUsernameExists, (req, res, next) => { - /** - [POST] /api/auth/login { "username": "sue", "password": "1234" } - - response: - status 200 - { - "message": "sue is back!", - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ETC.ETC" + + let { username, password } = req.body + + User.findBy({ username }) + .then(([user]) => { + if (user && bcrypt.compareSync(password, user.password)) { + const token = makeToken(user) + + res.status(200).json({message: `${user.username} is back!`, token}) + } else { + next({status: 401, message: 'Invalid credential'}) } - - The token must expire in one day, and must provide the following information - in its payload: - - { - "subject" : 1 // the user_id of the authenticated user - "username" : "bob" // the username of the authenticated user - "role_name": "admin" // the role of the authenticated user - } - */ + }).catch(err=>{ + res.status(500).json({message: err.message}) + }) }); +function makeToken(user) { + const payload = { + subject: user.id, + username: user.username, + role: user.role + } + const options = { + expiresIn: "24h" + } + return jwt.sign(payload, JWT_SECRET, options) +} + module.exports = router; diff --git a/api/secrets/index.js b/api/secrets/index.js index 1a125b81e..60cfe7b8e 100644 --- a/api/secrets/index.js +++ b/api/secrets/index.js @@ -6,6 +6,7 @@ If no fallback is provided, TESTS WON'T WORK and other developers cloning this repo won't be able to run the project as is. */ +const jwtSecret = process.env.JWT_SECRET || 'keepitsecret' module.exports = { - +jwtSecret } diff --git a/api/users/users-model.js b/api/users/users-model.js index 7a2064834..e8825dcdf 100644 --- a/api/users/users-model.js +++ b/api/users/users-model.js @@ -1,52 +1,23 @@ const db = require('../../data/db-config.js'); function find() { - /** - You will need to join two tables. - Resolves to an ARRAY with all users. - - [ - { - "user_id": 1, - "username": "bob", - "role_name": "admin" - }, - { - "user_id": 2, - "username": "sue", - "role_name": "instructor" - } - ] - */ + return db('users as u') + .join('roles as r', 'r.role_id', 'u.role_id') + .select('u.user_id', 'u.username', 'r.role_name') } function findBy(filter) { - /** - You will need to join two tables. - Resolves to an ARRAY with all users that match the filter condition. - - [ - { - "user_id": 1, - "username": "bob", - "password": "$2a$10$dFwWjD8hi8K2I9/Y65MWi.WU0qn9eAVaiBoRSShTvuJVGw8XpsCiq", - "role_name": "admin", - } - ] - */ + return db('users as u') + .join('roles as r', 'r.role_id', 'u.role_id') + .select('u.user_id', 'u.username', 'u.password', 'r.role_name') + .where('r.role_name', filter) } function findById(user_id) { - /** - You will need to join two tables. - Resolves to the user with the given user_id. - - { - "user_id": 2, - "username": "sue", - "role_name": "instructor" - } - */ + return db('users as u') + .join('roles as r', 'r.role_id', 'u.role_id') + .select('u.user_id', 'u.username', 'r.role_name') + .where('u.user_id', user_id).first() } /** diff --git a/package-lock.json b/package-lock.json index 0736ef5e2..93130d2d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "cors": "^2.8.5", "express": "^4.17.1", "helmet": "^4.6.0", + "jsonwebtoken": "^8.5.1", "knex": "^0.95.14", "sqlite3": "^5.0.2" }, @@ -1717,6 +1718,11 @@ "node-int64": "^0.4.0" } }, + "node_modules/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=" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2392,6 +2398,14 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/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==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4609,6 +4623,35 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "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" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -4624,6 +4667,25 @@ "node": ">=0.6.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/jwt-decode": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", @@ -4769,12 +4831,47 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "node_modules/lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -8568,6 +8665,11 @@ "node-int64": "^0.4.0" } }, + "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=" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -9094,6 +9196,14 @@ "safer-buffer": "^2.1.0" } }, + "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", @@ -10811,6 +10921,30 @@ "minimist": "^1.2.5" } }, + "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": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -10823,6 +10957,25 @@ "verror": "1.10.0" } }, + "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" + } + }, "jwt-decode": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", @@ -10918,12 +11071,47 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "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.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "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", diff --git a/package.json b/package.json index e3c921091..fb360196b 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "cors": "^2.8.5", "express": "^4.17.1", "helmet": "^4.6.0", + "jsonwebtoken": "^8.5.1", "knex": "^0.95.14", "sqlite3": "^5.0.2" },