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
9 changes: 9 additions & 0 deletions api/models/user.js
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)
111 changes: 111 additions & 0 deletions api/routes/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const { SECRET_KEY } = process.env
const router = require('express').Router()
const User = require('../models/user')
const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')

router.post('/signup', async (req, res, next) => {
const status = 201
const { username, password, admin } = req.body
try {
if(!username) {
return next({status: 400, message: 'Username was not provided.'})
}
if(!password) {
return next({status: 400, message: 'Password was not provided.'})
}
if(password.length < 8) {
return next({status: 400, message: 'Password is less than 8 characters.'})
}

// check to see if a user already exists with that name
const user = await User.findOne({ username })
// return an error if the user already exists
if(user !== null) {
return next({status: 400, message: 'Username is already taken.'})
}

// if the user does not exist, hash the given password
const hash = await bcrypt.hash(password, 12)
// create a new user with the hashed password
const response = await User.create({ username: username, password: hash })
const payload = { id: response._id, username: response.username, admin: response.admin }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Destructuring the response above can be an easy way to make this code a bit more readable.

const options = { expiresIn: '1 day' }
const token = jwt.sign(payload, SECRET_KEY, options)
res.status(status).json({ status, token })
} catch (e) {
console.error(e)
next({ status: 500, message: e })
}
})

router.post('/login', async (req, res, next) => {
const status = 200
const { username, password } = req.body
const failStatus = 401
const failMessage = 'Username/Password is incorrect.'

try {
// find the user trying to log in
const user = await User.findOne({ username })

// if the user is not found, return an error
if(user === null) {
return next({status: failStatus, message: failMessage})
}

// if a user is found, compare the entered password with the stored password
const isValid = await bcrypt.compare(password, user.password)
// if the passwords match, allow the login
if(isValid) {
const payload = { id: user._id, username: user.username, admin: user.admin }
const options = { expiresIn: '1 day' }
const token = jwt.sign(payload, SECRET_KEY, options)
res.status(status).json({
status: status,
token: token
})
} else {
return next({status: failStatus, message: failMessage})
}
} catch (e) {
console.error(e)
next({ status: 500, message: e })
}
})

router.patch('/users/:id/permissions', async (req, res, next) => {
const status = 204
const { admin } = req.body
const { id } = req.params

let payload;
try {
const token = req.headers.authorization.split('Bearer ')[1]
payload = jwt.verify(token, SECRET_KEY)
} catch (e) {
return next({ status: 401, message: 'Invalid token.' })
}

try {
if(payload.admin === false) {
return next({ status: 401, message: 'Unauthorized.'})
}
if(!('admin' in req.body)) {
return next({ status: 400, message: 'Body must include admin value.'})
}

const user = await User.findById(id)
if(!user) {
return next({ status: 404, message: 'User not found.'})
}

user.admin = admin
await user.save()
res.status(status).send()
} catch (e) {
next({ status: 500, message: e })
}
})

module.exports = router
70 changes: 64 additions & 6 deletions api/routes/books.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const { SECRET_KEY } = process.env
const router = require('express').Router()
const Book = require('../models/book')
const jwt = require('jsonwebtoken')

router.get('/', async (req, res, next) => {
const status = 200
Expand Down Expand Up @@ -27,7 +29,14 @@ router.get('/:id', async (req, res, next) => {
// You should only be able to create a book if the user is an admin
router.post('/', async (req, res, next) => {
const status = 200

try {
const token = req.headers.authorization.split('Bearer ')[1]
const payload = jwt.verify(token, SECRET_KEY)
if(payload.admin === false) {
return next({ status: 401, message: 'Unauthorized.'})
}

const book = await Book.create(req.body)
if (!book) throw new Error(`Request body failed: ${JSON.stringify(req.body)}`)

Expand All @@ -45,16 +54,31 @@ 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
let payload;
try {
const token = req.headers.authorization.split('Bearer ')[1]
payload = jwt.verify(token, SECRET_KEY)
} catch (e) {
return next({ status: 401, message: 'Invalid token.' })
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would encourage you to move this into middleware.


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

if(book.reserved.status === true) {
const error = new Error(`Book is already reserved.`)
error.status = 400
return next(error)
}

book.reserved.status = true
// Set the reserved memberId to the current user
book.reserved.memberId = payload.id
await book.save()

const response = await Book.findById(book._id).select('-__v')
Expand All @@ -68,11 +92,45 @@ router.patch('/:id/reserve', async (req, res, next) => {
// 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 { id } = req.params
let payload;
try {
const token = req.headers.authorization.split('Bearer ')[1]
payload = jwt.verify(token, SECRET_KEY)
} catch (e) {
return next({ status: 401, message: 'Invalid token.' })
}

try {
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(`Book is not reserved.`)
error.status = 400
return next(error)
}

if(!book.reserved.memberId.equals(payload.id)) {
const error = new Error(`Book is reserved by another user.`)
error.status = 401
return next(error)
}

book.reserved.status = false
book.reserved.memberId = null
await book.save()

const response = await Book.findById(book._id).select('-__v')
const status = 200
res.json({ status, response })
} catch (e) {
console.error(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
31 changes: 28 additions & 3 deletions db/seeds.js
Original file line number Diff line number Diff line change
@@ -1,9 +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 reset = async () => {
mongoose.connect(config.env.MONGO_DB_CONNECTION, { useNewUrlParser: true })
const resetUsers = async () => {
await User.deleteMany() // Deletes all records
return await User.create([
{
username: 'admin',
password: await bcrypt.hash('admin', 12),
admin: true
},
{
username: 'user',
password: await bcrypt.hash('user', 12),
admin: false
}
])
}

const resetBooks = async () => {
await Book.deleteMany() // Deletes all records
return await Book.create([
{
Expand Down Expand Up @@ -43,7 +60,15 @@ const reset = async () => {
])
}

const reset = async () => {
mongoose.connect(config.env.MONGO_DB_CONNECTION, { useNewUrlParser: true })
const books = await resetBooks()
const users = await resetUsers()
return { books, users }
}

reset().catch(console.error).then((response) => {
console.log(`Seeds successful! ${response.length} records created.`)
console.log(`Seeds successful! ${response.books.length} book records created.`)
console.log(`Seeds successful! ${response.users.length} user records created.`)
return mongoose.disconnect()
})
Loading