From 613a9937018acaa837894ba720baff8afaaeb79c Mon Sep 17 00:00:00 2001 From: Cynthia Coronado Date: Sat, 12 Feb 2022 13:01:50 -0800 Subject: [PATCH 1/4] server running, user model completed --- api/users/users-model.js | 12 +++ index.js | 2 + package-lock.json | 203 ++++++++++++++++++++++++++++++++++++++- package.json | 2 + 4 files changed, 218 insertions(+), 1 deletion(-) diff --git a/api/users/users-model.js b/api/users/users-model.js index 7a2064834..d92417e92 100644 --- a/api/users/users-model.js +++ b/api/users/users-model.js @@ -18,6 +18,9 @@ function find() { } ] */ + return db('users') + .join('roles', 'users.role_id', 'roles.role_id') + .select('user_id', 'username', 'role_name') } function findBy(filter) { @@ -34,6 +37,10 @@ function findBy(filter) { } ] */ + return db('users') + .join('roles', 'users.role_id', 'roles.role_id') + .select('user_id', 'username', 'password', 'role_name') + .where(filter) } function findById(user_id) { @@ -47,6 +54,11 @@ function findById(user_id) { "role_name": "instructor" } */ + return db('users') + .join('roles', 'users.role_id', 'roles.role_id') + .select('user_id', 'username', 'role_name') + .where('users.user_id', user_id) + .first() } /** diff --git a/index.js b/index.js index 71f14885b..492676dc7 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,5 @@ +require('dotenv').config + const server = require('./api/server.js'); const PORT = process.env.PORT || 9000; diff --git a/package-lock.json b/package-lock.json index 0736ef5e2..162608157 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,13 +5,14 @@ "requires": true, "packages": { "": { - "name": "node-auth2-project", "version": "1.0.0", "dependencies": { "bcryptjs": "^2.4.3", "cors": "^2.8.5", + "dotenv": "^16.0.0", "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", @@ -2376,6 +2382,14 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", + "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==", + "engines": { + "node": ">=12" + } + }, "node_modules/duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -2392,6 +2406,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 +4631,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 +4675,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 +4839,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 +8673,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", @@ -9078,6 +9188,11 @@ "is-obj": "^2.0.0" } }, + "dotenv": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", + "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==" + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -9094,6 +9209,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 +10934,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 +10970,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 +11084,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..6afda95f5 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,10 @@ "dependencies": { "bcryptjs": "^2.4.3", "cors": "^2.8.5", + "dotenv": "^16.0.0", "express": "^4.17.1", "helmet": "^4.6.0", + "jsonwebtoken": "^8.5.1", "knex": "^0.95.14", "sqlite3": "^5.0.2" }, From a99c2bf688c3cfc2811e5c1efcb219e202f712c1 Mon Sep 17 00:00:00 2001 From: Cynthia Coronado Date: Sat, 12 Feb 2022 13:13:48 -0800 Subject: [PATCH 2/4] auth middleward completed --- README.md | 6 ++-- api/auth/auth-middleware.js | 62 ++++++++++++++++++++++++++++++++++++- api/secrets/index.js | 3 +- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d16a46cac..1c607145f 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ You will complete the following tasks and do any extra wiring and package instal Write the following user access functions inside `api/users/users-model.js`: -- [ ] `find` -- [ ] `findBy` -- [ ] `findById` +- [x] `find` +- [x] `findBy` +- [x] `findById` #### 2B - Middleware Functions diff --git a/api/auth/auth-middleware.js b/api/auth/auth-middleware.js index c603d37c7..42ad6b385 100644 --- a/api/auth/auth-middleware.js +++ b/api/auth/auth-middleware.js @@ -1,4 +1,6 @@ const { JWT_SECRET } = require("../secrets"); // use this secret! +const { findBy } = require('../users/users-model') +const jwt = require('jsonwebtoken') const restricted = (req, res, next) => { /* @@ -16,6 +18,24 @@ const restricted = (req, res, next) => { Put the decoded token in the req object, to make life easier for middlewares downstream! */ + const token = req.headers.authorization + if(!token){ + return next ({ + status: 401, + message: 'Token reuired' + }) + } + jwt.verify(token, JWT_SECRET, (err, decodedToken) => { + if(err){ + next({ + status: 401, + message: 'Token valid' + }) + }else{ + req.decodedToken = decodedToken + next() + } + }) } const only = role_name => (req, res, next) => { @@ -29,10 +49,19 @@ const only = role_name => (req, res, next) => { Pull the decoded token from the req object, to avoid verifying it again! */ + const roleName = req.decodedToken.roleName + if(role_name === req.decodedToken.role_name){ + next() + }else { + next({ + status: 403, + message: 'This is not for you' + }) + } } -const checkUsernameExists = (req, res, next) => { +const checkUsernameExists = async (req, res, next) => { /* If the username in req.body does NOT exist in the database status 401 @@ -40,6 +69,20 @@ const checkUsernameExists = (req, res, next) => { "message": "Invalid credentials" } */ + try{ + const [ user ] = await findBy({ username: req.body.username }) + if(!user){ + next({ + status: 422, + message: 'Invalid credentials' + }) + }else { + req.user = user + next() + } + }catch(err){ + next(err) + } } @@ -62,6 +105,23 @@ const validateRoleName = (req, res, next) => { "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'){ + next({ + status: 422, + message: 'Role name can not be admin' + }) + }else if (req.body.role_name.trim().length > 32){ + next({ + status: 422, + message: 'Role name can not be longer than 32 characters' + }) + }else { + req.role_name = req.body.role_name.trim() + next() + } } module.exports = { diff --git a/api/secrets/index.js b/api/secrets/index.js index 1a125b81e..832430789 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 JWT_SECRET = process.env.JWT_SECRET ||'shh' module.exports = { - + JWT_SECRET } From f27e5855fd6abf492f5c4fe9304a5ff756c111a1 Mon Sep 17 00:00:00 2001 From: Cynthia Coronado Date: Sat, 12 Feb 2022 13:20:44 -0800 Subject: [PATCH 3/4] auth router completed --- api/auth/auth-router.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/api/auth/auth-router.js b/api/auth/auth-router.js index c723c2da8..f320d83a5 100644 --- a/api/auth/auth-router.js +++ b/api/auth/auth-router.js @@ -1,6 +1,9 @@ const router = require("express").Router(); const { checkUsernameExists, validateRoleName } = require('./auth-middleware'); const { JWT_SECRET } = require("../secrets"); // use this secret! +const bcrypt = require('bcryptjs') +const User = require('../users/users-model') +const jwt = require('jsonwebtoken') router.post("/register", validateRoleName, (req, res, next) => { /** @@ -14,6 +17,14 @@ router.post("/register", validateRoleName, (req, res, next) => { "role_name": "angel" } */ + const { username, password } = req.body + const { role_name } = req + const hash = bcrypt.hashSync(passord, 8) + User.add({ username, password: hash, role_name }) + .then(newUser => { + res.status(201).json(newUser) + }) + .catch(next) }); @@ -37,6 +48,30 @@ router.post("/login", checkUsernameExists, (req, res, next) => { "role_name": "admin" // the role of the authenticated user } */ + if(bcrypt.compareSync(req.body.passord, req.user.passord)){ + const token = buildToken(req.user) + res.json({ + message: `${req.user.username} is back`, + token + }) + }else { + next({ + status: 401, + message: 'Invalid credentials' + }) + } }); +function buildToken(user){ + const payload = { + subject: user.user_id, + role_name: user.role_name, + username: user.username, + } + const options = { + expiresIn: '1d', + } + return jwt.sign(payload, JWT_SECRET, options) +} + module.exports = router; From 4d1a5358bef3cde38bbadf58c488ff4ea22e3066 Mon Sep 17 00:00:00 2001 From: Cynthia Coronado Date: Sat, 12 Feb 2022 13:23:25 -0800 Subject: [PATCH 4/4] completed --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1c607145f..1540598a7 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ Write the following user access functions inside `api/users/users-model.js`: Write the following auth middlewares inside `api/auth/auth-middleware.js`: -- [ ] `restricted` -- [ ] `only` -- [ ] `checkUsernameExists` -- [ ] `validateRoleName` +- [x] `restricted` +- [x] `only` +- [x] `checkUsernameExists` +- [x] `validateRoleName` #### 2C - Endpoints @@ -37,13 +37,13 @@ Authentication will be implemented using JSON Web Tokens. Write the following endpoints inside `api/auth/auth-router.js`: -- [ ] `[POST] /api/auth/register` -- [ ] `[POST] /api/auth/login` +- [x] `[POST] /api/auth/register` +- [x] `[POST] /api/auth/login` The endpoints inside `api/users/users-router.js` are built already but check them out: -- [ ] `[GET] /api/users` - only users with a valid token can access -- [ ] `[GET] /api/users/:user_id` - only users with a valid token AND a role of 'admin' can access +- [x] `[GET] /api/users` - only users with a valid token can access +- [x] `[GET] /api/users/:user_id` - only users with a valid token AND a role of 'admin' can access #### 2D - Secrets File