-
Notifications
You must be signed in to change notification settings - Fork 26
Tom Workman | WIP exercise-authentication-authorization Assignment #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
284316d
3d1c1df
6a70c84
a3e3ba3
4639bf5
9a78207
95f7c57
0a47f3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| const mongoose = require("mongoose"); | ||
|
|
||
| const schema = mongoose.Schema({ | ||
| username: String, | ||
| password: String, | ||
| admin: { type: Boolean, default: false } | ||
| }); | ||
|
|
||
| module.exports = mongoose.model("User", schema); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| const router = require("express").Router(); | ||
| const jsonwebtoken = require("jsonwebtoken"); | ||
| const bcrypt = require("bcrypt"); | ||
| const saltRounds = 10; | ||
| const User = require("../models/user"); | ||
| const { SECRET_PW } = process.env; | ||
|
|
||
| router.post("/signup", async (req, res, next) => { | ||
| const status = 201; | ||
| try { | ||
| const { username, password, admin } = req.body; | ||
| const user = await User.findOne({ username }); | ||
| if (user) throw new Error(`User ${username} already exists.`); | ||
|
|
||
| const hashedPassword = await bcrypt.hash(password, saltRounds); | ||
| const response = await User.create({ | ||
| username, | ||
| password: hashedPassword, | ||
| admin | ||
| }); | ||
| res.status(status).json({ status, response }); | ||
| } catch (error) { | ||
| console.log(error); | ||
| const e = new Error(`User ${username} already exists.`); | ||
| e.status = 400; | ||
| next(e); | ||
| } | ||
| }); | ||
|
|
||
| router.post("/login", async (req, res, next) => { | ||
| const status = 201; | ||
| try { | ||
| const { username, password } = req.body; | ||
| const checkForUser = await User.findOne({ | ||
| username | ||
| }); | ||
| if (!checkForUser) throw new Error(`User ${username} does not exist.`); | ||
|
|
||
| const user = await bcrypt.compare(password, checkForUser.password); | ||
| if (!user) throw new Error(`Password is invalid.`); | ||
|
|
||
| const payload = { id: user._id }; | ||
| const options = { expiresIn: "1 day" }; | ||
| const token = jsonwebtoken.sign(payload, "SECRET_PW", options); | ||
|
|
||
| // res.status(status).json({ status, response: `You have been logged in.` }); | ||
| res.status(status).json({ status, token }); | ||
| } catch (error) { | ||
| console.log(error); | ||
| const e = new Error("Login credentials incorrect."); | ||
| e.status = 401; | ||
| next(e); | ||
| } | ||
| }); | ||
|
|
||
| // Admin should be able to change permissions for other users | ||
| // NOTE: Can't get this working...come back to it. | ||
| // router.patch("/users/:id/permissions", async (req, res, next) => { | ||
| // const { id } = req.params; | ||
|
|
||
| // try { | ||
| // const token = req.headers.authorization.split("Bearer ")[1]; | ||
| // const payload = jsonwebtoken.verify(token, SECRET_PW); | ||
| // const user = await User.findOne({ _id: payload.id }).select( | ||
| // "-__v -password" | ||
| // ); | ||
| // const isAdminUser = user.admin; | ||
|
|
||
| // if (!isAdminUser) { | ||
| // const error = new Error( | ||
| // "The JWT token is for a user who is not an admin." | ||
| // ); | ||
| // error.message = 401; | ||
| // return next(error); | ||
| // } | ||
|
|
||
| // if (!user) { | ||
| // const error = new Error("User cannot be found."); | ||
| // error.message = 404; | ||
| // return next(error); | ||
| // } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the order you have this in is a bit wrong. First you'll want to check for a user, then you'll want to check to see whether or not that user is an admin. |
||
|
|
||
| // const response = await User.findByIdAndUpdate( | ||
| // id, | ||
| // { admin: req.body.admin }, | ||
| // { new: true } | ||
| // ); | ||
| // const status = 204; | ||
| // res.json({ status, response }); | ||
| // } catch (e) { | ||
| // console.error(e); | ||
| // } | ||
| // }); | ||
|
|
||
| router.patch("/users/:id/permissions", async (req, res, next) => { | ||
| try { | ||
| const token = req.headers.authorization.split("Bearer ")[1]; | ||
| const payload = jsonwebtoken.verify(token, SECRET_PW); | ||
| const user = await User.findOne({ _id: payload.id }).select( | ||
| "-__v -password" | ||
| ); | ||
| const isAdminUser = user.admin; | ||
|
|
||
| if (!isAdminUser) { | ||
| const error = new Error( | ||
| "The JWT token is for a user who is not an admin." | ||
| ); | ||
| error.message = 401; | ||
| return next(error); | ||
| } | ||
|
|
||
| if (!user) { | ||
| const error = new Error("User cannot be found."); | ||
| error.message = 404; | ||
| return next(error); | ||
| } | ||
|
|
||
| const AdminStatus = user.admin; | ||
| if (AdminStatus === true) { | ||
| user.admin = false; | ||
| } else { | ||
| user.admin = true; | ||
| } | ||
|
|
||
| // Update the user permissions for the current user | ||
| await user.save(); | ||
|
|
||
| const response = await User.findById(user._id).select("-__v"); | ||
| const status = 204; | ||
| res.json({ status, response }); | ||
| } catch (e) { | ||
| console.error(e); | ||
| } | ||
| }); | ||
|
|
||
| module.exports = router; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,78 +1,100 @@ | ||
| const router = require('express').Router() | ||
| const Book = require('../models/book') | ||
| const router = require("express").Router(); | ||
| const Book = require("../models/book"); | ||
| const User = require("../models/user"); | ||
| const jsonwebtoken = require("jsonwebtoken"); | ||
| const bcrypt = require("bcrypt"); | ||
| const saltRounds = 10; | ||
| const { SECRET_PW } = process.env; | ||
|
|
||
| router.get('/', async (req, res, next) => { | ||
| const status = 200 | ||
| const response = await Book.find().select('-__v') | ||
|
|
||
| res.json({ status, response }) | ||
| }) | ||
| router.get("/", async (req, res, next) => { | ||
| const status = 200; | ||
| const response = await Book.find().select("-__v"); | ||
|
|
||
| router.get('/:id', async (req, res, next) => { | ||
| const { id } = req.params | ||
| const status = 200 | ||
| res.json({ status, response }); | ||
| }); | ||
|
|
||
| router.get("/:id", async (req, res, next) => { | ||
| const { id } = req.params; | ||
| const status = 200; | ||
| try { | ||
| const response = await Book.findById(id).select('-__v') | ||
| if (!response) throw new Error(`Invalid Book _id: ${id}`) | ||
| res.json({ status, response }) | ||
| const response = await Book.findById(id).select("-__v"); | ||
| if (!response) throw new Error(`Invalid Book _id: ${id}`); | ||
|
|
||
| res.json({ status, response }); | ||
| } catch (e) { | ||
| console.error(e) | ||
| const error = new Error(`Cannot find book with id ${id}.`) | ||
| error.status = 404 | ||
| next(error) | ||
| console.error(e); | ||
| const error = new Error(`Cannot find book with id ${id}.`); | ||
| error.status = 404; | ||
| next(error); | ||
| } | ||
| }) | ||
| }); | ||
|
|
||
| // You should only be able to create a book if the user is an admin | ||
| router.post('/', async (req, res, next) => { | ||
| const status = 200 | ||
| 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]; | ||
| const payload = jsonwebtoken.verify(token, SECRET_PW); | ||
| const user = await User.findOne({ _id: payload.id }).select( | ||
| "-__v -password" | ||
| ); | ||
| const isAdminUser = user.admin; | ||
| // Check for admin user | ||
| if (!isAdminUser) { | ||
| const error = new Error( | ||
| "Sorry, please contact your admin to create a new book." | ||
| ); | ||
| error.message = 401; | ||
| return next(error); | ||
| } | ||
| // If user is an admin, then create a book. | ||
| 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) | ||
| console.error(e); | ||
| const message = | ||
| "Failure to create. Please check request body and try again."; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having the right-hand assignment be on a different line works fine but looks a bit dicey. |
||
| const error = new Error(message); | ||
| error.status = 400; | ||
| next(error); | ||
| } | ||
| }) | ||
| }); | ||
|
|
||
| // 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 | ||
| router.patch("/:id/reserve", async (req, res, next) => { | ||
| const { id } = req.params; | ||
| try { | ||
| const book = await Book.findById(id) | ||
| const book = await Book.findById(id); | ||
| if (!book) { | ||
| const error = new Error(`Invalid Book _id: ${id}`) | ||
| error.message = 404 | ||
| return next(error) | ||
| const error = new Error(`Invalid Book _id: ${id}`); | ||
| error.message = 404; | ||
| return next(error); | ||
| } | ||
|
|
||
| book.reserved.status = true | ||
| book.reserved.status = true; | ||
| // Set the reserved memberId to the current user | ||
| await book.save() | ||
| const response = await Book.findById(book._id).select('-__v') | ||
| const status = 200 | ||
| res.json({ status, response }) | ||
| await book.save(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You'll want to include a user id as well when reserving a book. |
||
|
|
||
| const response = await Book.findById(book._id).select("-__v"); | ||
| const status = 200; | ||
| res.json({ status, response }); | ||
| } catch (e) { | ||
| console.error(e) | ||
| console.error(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 }) | ||
| }) | ||
| 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 }); | ||
| }); | ||
|
|
||
| module.exports = router | ||
| module.exports = router; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,37 +1,38 @@ | ||
| const { MONGO_DB_CONNECTION, NODE_ENV, PORT } = process.env | ||
| const express = require('express') | ||
| const mongoose = require('mongoose') | ||
| const app = express() | ||
| const { MONGO_DB_CONNECTION, NODE_ENV, PORT } = process.env; | ||
| const express = require("express"); | ||
| const mongoose = require("mongoose"); | ||
| const app = express(); | ||
|
|
||
| // Database Connection | ||
| if (MONGO_DB_CONNECTION) { | ||
| mongoose.connect(MONGO_DB_CONNECTION, { useNewUrlParser: true }) | ||
| console.log('Connected to database...') | ||
| mongoose.connect(MONGO_DB_CONNECTION, { useNewUrlParser: true }); | ||
| console.log("Connected to database..."); | ||
| } else { | ||
| console.log('Could not connect to database!') | ||
| console.log("Could not connect to database!"); | ||
| } | ||
|
|
||
| // Application-level Middleware | ||
| if (NODE_ENV === 'development') app.use(require('morgan')('dev')) | ||
| app.use(require('body-parser').json()) | ||
| if (NODE_ENV === "development") app.use(require("morgan")("dev")); | ||
| app.use(require("body-parser").json()); | ||
|
|
||
| // Routes | ||
| app.use('/api/books', require('./api/routes/books')) | ||
| app.use("/api/", require("./api/routes/auth")); | ||
| app.use("/api/books", require("./api/routes/books")); | ||
|
|
||
| // Not Found Handler | ||
| app.use((req, res, next) => { | ||
| const error = new Error(`Could not ${req.method} ${req.path}`) | ||
| error.status = 404 | ||
| next(error) | ||
| }) | ||
| const error = new Error(`Could not ${req.method} ${req.path}`); | ||
| error.status = 404; | ||
| next(error); | ||
| }); | ||
|
|
||
| // Error Handler | ||
| app.use((err, req, res, next) => { | ||
| if (NODE_ENV === 'development') console.error(err) | ||
| const { message, status } = err | ||
| res.status(status).json({ status, message }) | ||
| }) | ||
| if (NODE_ENV === "development") console.error(err); | ||
| const { message, status } = err; | ||
| res.status(status).json({ status, message }); | ||
| }); | ||
|
|
||
| // Open Connection | ||
| const listener = () => console.log(`Listening on Port ${PORT}!`) | ||
| app.listen(PORT, listener) | ||
| const listener = () => console.log(`Listening on Port ${PORT}!`); | ||
| app.listen(PORT, listener); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll want to keep these error messages the same, otherwise it's easier for someone to "guess" at a username. Having the same error message is more secure.