Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions api/models/user.js
Original file line number Diff line number Diff line change
@@ -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)
115 changes: 115 additions & 0 deletions api/routes/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
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)
const user = await User.create({
username,
password: hashed
})
console.log(`User ${username} created!`)

//return success
const payload = { id: user._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 = await 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(payload, SECRET_KEY, options)
res.status(status).json({status, token})
} catch (e) {
e.status = 401
next(e)
}
})

router.patch('/users/:id/permissions', async (req, res, next) => {
const status = 204
try {
//make sure request body is OK - 400
const permissions = req.body.admin
const { id } = req.params
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) {
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) {
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) {
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).send()

} catch (e) {
next(e)
}
})

module.exports = router
109 changes: 91 additions & 18 deletions api/routes/books.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
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) => {
const status = 200
Expand All @@ -25,54 +28,124 @@ 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
const { authorization } = req.headers
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 })
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)
const user = await User.findOne({ _id: payload.id }).select('-__v -password')
if (!user || user.admin !== true) throw new Error (`You are not authorized`)

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 })
} 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)
}
})

// 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 { authorization } = req.headers
//make sure token exists
if (! authorization) {
const error = new Error (`You are not authorized`)
error.status = 401
return next(error)
}
try {
const token = authorization.split('Bearer ')[1]
const payload = jsonwebtoken.verify(token, SECRET_KEY)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what you want to use for whether or not the person is authorized. Just having a token is not enough.

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) {
console.error(e)
e.status = 401
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 = 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) {
//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.status !== 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) {
console.error(e)
next (e)
}
})

module.exports = router
1 change: 1 addition & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
23 changes: 20 additions & 3 deletions db/seeds.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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()
})
3 changes: 2 additions & 1 deletion nodemon.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"env": {
"MONGO_DB_CONNECTION": "",
"NODE_ENV": "development",
"PORT": 5000
"PORT": 5000,
"SECRET_KEY": "SUPERSECRETKEY"
}
}
Loading