-
Notifications
You must be signed in to change notification settings - Fork 26
Solutions to Assignment #18
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
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,19 @@ | ||
| const mongoose = require('mongoose') | ||
|
|
||
| const schema = new mongoose.Schema({ | ||
| username : { | ||
| type : String, | ||
| required : true | ||
| }, | ||
| password : { | ||
| type : String, | ||
| required : true | ||
| }, | ||
| admin :{ | ||
| type : Boolean, | ||
| default : false | ||
| } | ||
|
|
||
| }, { timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }) | ||
|
|
||
| module.exports = mongoose.model('User', schema) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| const router = require('express').Router() | ||
| const User = require('../models/user'); | ||
| const bcrypt = require('bcrypt'); | ||
| const jsonwebtoken = require('jsonwebtoken'); | ||
|
|
||
| const {SECRET_KEY} = process.env | ||
|
|
||
| const saltRounds = 10; | ||
|
|
||
| // Add a post route for sign up | ||
| router.post('/signup', async (req, res, next) =>{ | ||
| var status = 201; | ||
| try { | ||
| const {username, password} = req.body; | ||
| if (!username) | ||
| { | ||
| status = 400; | ||
| throw new Error('Please provide a valid username'); | ||
| } | ||
| if (!password) | ||
| { | ||
| status = 400; | ||
| throw new Error('Please provide a valid password'); | ||
| } | ||
| if(password.length < 8) | ||
| { | ||
| status = 400; | ||
| throw new Error('Your password should be atleast 8 characters'); | ||
| } | ||
| var user = await User.findOne({username}); | ||
| if (user) | ||
| { | ||
| status = 400; | ||
| throw new Error('User already exists'); | ||
| } | ||
| var hashedPassword = await bcrypt.hash(req.body.password, saltRounds); | ||
| const response = await User.create({username : username, password: hashedPassword}); | ||
| res.status(status).json({status, response}) | ||
| } | ||
| catch(err){ | ||
| console.error(err); | ||
| const error = new Error(err.message) | ||
| error.status = 400 | ||
| next(error) | ||
| } | ||
| }) | ||
|
|
||
| // Add a post route for login | ||
| router.post('/login', async (req, res, next) => { | ||
| var status = 201; | ||
|
|
||
| try{ | ||
| const {username, password} = req.body; | ||
| var guest = await User.findOne({username}) | ||
| if (!guest) | ||
| { | ||
| throw new Error('User not found') | ||
| } | ||
|
|
||
| const isValid = await bcrypt.compare(password, guest.password) | ||
| if (!isValid) throw new Error('Password is invalid'); | ||
|
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 use the same error message for both of the above errors. Otherwise, it's easier for someone to "guess" another user's username and is more insecure. |
||
|
|
||
| const payload = { id: guest._id } // Set up payload | ||
| const options = { expiresIn: '1 day' } // Set up expiration | ||
| const token = jsonwebtoken.sign(payload, SECRET_KEY , options) | ||
| res.status(status).json({status, token}) | ||
| } | ||
| catch(err){ | ||
| console.error(err); | ||
| const error = new Error('Login creds incorrect.') | ||
| error.status = 400 | ||
| next(error) | ||
| } | ||
| }) | ||
|
|
||
| // Add a patch route for an admin to update another users admin privileges | ||
| // I am assuming here that the param that is passed in for :id is the username | ||
| router.patch('/users/:id/permissions', async (req, res, next) => { | ||
| const status = 200 | ||
| try { | ||
| // Check if A valid JWT token is not provided (status 401) | ||
| const token = req.headers.authorization.split('Bearer ')[1] | ||
| // Check if a token is provided | ||
| if (!token) | ||
| { | ||
| status = 401; | ||
| throw new Error(' No token present') | ||
| } | ||
| // Check if the token is valid | ||
| const payload = jsonwebtoken.verify(token, SECRET_KEY) | ||
| if (!payload) | ||
| { | ||
| status = 401; | ||
| throw new Error('Invalid token') | ||
| } | ||
|
|
||
| // Check if request body has admin | ||
| if(!req.body.admin) | ||
| { | ||
| status = 400; | ||
| throw new Error('Body does not contain admin') | ||
| } | ||
|
|
||
| // Authentication | ||
| const adminUser = await User.findOne({ _id: payload.id }) | ||
| if(!adminUser) | ||
| { | ||
| status = 401; | ||
| throw new Error(' No admin user found') | ||
| } | ||
|
|
||
| if(!(adminUser.admin)) | ||
| { | ||
| status = 401; | ||
| throw new Error('User is not an admin') | ||
| } | ||
|
|
||
| const user = await User.findOneAndUpdate({ username : req.params.id}, { admin: req.body.admin }, { new: true }) | ||
|
|
||
| if(!user) | ||
| { | ||
| status = 404; | ||
| throw new Error(' The requested user cannot be found'); | ||
| } | ||
|
|
||
| res.json({ status, message:"Successfully updated admin privileges" }); | ||
| } catch (e) { | ||
| console.error(e) | ||
| const error = new Error(`Unable to update user permissions`) | ||
| error.status = 404 | ||
| next(error) | ||
| } | ||
| }) | ||
|
|
||
| module.exports = router | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,14 @@ | ||
| const router = require('express').Router() | ||
| const Book = require('../models/book') | ||
| const User = require('../models/user') | ||
| const jsonwebtoken = require('jsonwebtoken'); | ||
|
|
||
| const {SECRET_KEY} = process.env | ||
|
|
||
| router.get('/', async (req, res, next) => { | ||
| const status = 200 | ||
| const response = await Book.find().select('-__v') | ||
|
|
||
| res.json({ status, response }) | ||
| }) | ||
|
|
||
|
|
@@ -14,8 +18,8 @@ router.get('/:id', async (req, res, next) => { | |
| try { | ||
| const response = await Book.findById(id).select('-__v') | ||
| if (!response) throw new Error(`Invalid Book _id: ${id}`) | ||
| res.json({ status, response }) | ||
|
|
||
| res.json({ status, response }) | ||
| } catch (e) { | ||
| console.error(e) | ||
| const error = new Error(`Cannot find book with id ${id}.`) | ||
|
|
@@ -28,9 +32,27 @@ router.get('/:id', async (req, res, next) => { | |
| router.post('/', async (req, res, next) => { | ||
| const status = 200 | ||
| try { | ||
|
|
||
| // Check if the user is an admin | ||
| const token = req.headers.authorization.split('Bearer ')[1] | ||
| const payload = jsonwebtoken.verify(token, SECRET_KEY) | ||
| const user = await User.findOne({ _id: payload.id }).select('-__v -password') | ||
| if(!user) | ||
| { | ||
| const error = new Error('Cannot find user') | ||
| error.status = 404 | ||
| return next(error) | ||
| } | ||
| if(!(user.admin)) | ||
|
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 shouldn't need the |
||
| { | ||
| const error = new Error('User not an admin') | ||
| error.status = 404 | ||
| return next(error) | ||
| } | ||
|
|
||
| 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) { | ||
|
|
@@ -45,34 +67,104 @@ 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 | ||
| var status; | ||
|
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 could use |
||
| try { | ||
| // Check if its valid user/token | ||
| const token = req.headers.authorization.split('Bearer ')[1] | ||
| const payload = jsonwebtoken.verify(token, SECRET_KEY) | ||
| const user = await User.findOne({ _id: payload.id }).select('-__v -password') | ||
| if(!user) | ||
| { | ||
| const error = new Error('Cannot find user') | ||
| error.status = 404 | ||
| return next(error) | ||
| } | ||
|
|
||
| const book = await Book.findById(id) | ||
| if (!book) { | ||
| const error = new Error(`Invalid Book _id: ${id}`) | ||
| error.message = 404 | ||
| return next(error) | ||
| } | ||
|
|
||
| if (book.reserved.status === true) | ||
| { | ||
| const error = new Error('Book already reserved') | ||
| error.message = 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) { | ||
| console.error(e) | ||
| const message = 'Failed to set the reservation' | ||
| const error = new Error(message) | ||
| error.status = status | ||
| next(error) | ||
| } | ||
| }) | ||
|
|
||
| // 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) => { | ||
|
|
||
| try{ | ||
| // Check if its valid user/token | ||
| const token = req.headers.authorization.split('Bearer ')[1] | ||
| const payload = jsonwebtoken.verify(token, SECRET_KEY) | ||
| const user = await User.findOne({ _id: payload.id }).select('-__v -password') | ||
| if(!user) | ||
| { | ||
| const error = new Error('Cannot find user') | ||
| error.status = 404 | ||
| return next(error) | ||
| } | ||
|
|
||
| // Find the book | ||
| const book = await Book.findById(id) | ||
| if (!book) { | ||
| const error = new Error(`Invalid Book _id: ${id}`) | ||
| error.status = 404 | ||
| return next(error) | ||
| } | ||
| if(book.reserved.status === false) | ||
| { | ||
| const error = new Error('You cannot return an unreserved book') | ||
| error.status = 400 | ||
| return next(error) | ||
| } | ||
| if(book.reserved.memberId != user._id) | ||
| { | ||
| const error = new Error('The user id does not match') | ||
| error.status = 401 | ||
| return next(error) | ||
| } | ||
|
|
||
| /// If able to return successfully, set reserved status to false and clear the memberId | ||
| book.reserved.status = false | ||
| book.reserved.memberId = "" | ||
|
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.
|
||
| await book.save() | ||
|
|
||
| const response = await Book.findById(book._id).select('-__v') | ||
| const status = 200 | ||
| const message = 'You must implement this route!' | ||
|
|
||
| console.log(message) | ||
| res.status(status).json({ status, message }) | ||
| res.json({ status, response }) | ||
|
|
||
| res.status(status).json({ status, response }) | ||
| } | ||
| catch (e) { | ||
| console.error(e) | ||
| const message = 'Failed to set the reservation' | ||
| const error = new Error(message) | ||
| error.status = status | ||
| next(error) | ||
| } | ||
| }) | ||
|
|
||
| module.exports = router | ||
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.
Since these are all the same, you could probably just define this at the top!