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
23 changes: 23 additions & 0 deletions api/models/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const mongoose = require('mongoose')

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

router.post('/signup', async (req, res, next) => {
const status = 201
try {
const { username, password } = req.body
// Username is not provided
if (!username) throw new Error (`Username not provided.`)
// Username is already taken
const alreadyUsername = await User.findOne({username})
.select('-__v -password')
if (alreadyUsername) throw new Error (`Username already taken.`)

// Password is not provided
if (!password) throw new Error (`Password not provided.`)
// password is less than 8 characters
if (password.length < 8) throw new Error (`Password is less than 8 characters.`)

const hashedPassword = await bcrypt.hash(password, saltRounds)
const user = await User.create({
username,
password: hashedPassword
})

const payload = { id: user._id }
const options = { expiresIn: '1 day' }
const token = jwt.sign(payload, SECRET_KEY, options)

res.json({ status, token })
} catch (e) {
e.status = 400
console.error(e)
next(e)
}
})


router.post('/login', async (req, res, next) => {
const status = 201
try {
const { username, password } = req.body
const user = await User.findOne({ username})
.select('-__v -password')
// Username is not found
if (!user) throw new Error (`Incorrect login credentials.`)

// If username does exist, compare the plain text password the the hashed version
const isValid = await bcrypt.compare(password, user.password)
if (!isValid) throw new Error(`Incorrect login credentials.`)

const payload = { id: user._id }
const options = { expiresIn: '1 day' }
const token = jwt.sign(payload, SECRET_KEY, options)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Because you're performing this operation multiple times, this is a great thing to put into a function.


res.json({ status, token })
} catch (e) {
console.error(e)
const error = new Error ('Incorrect login credentials')
error.status = 401
next(error)
}
})

// PATCH /api/users/:id/permissions to change admin permissions of another user
// Finds admin: <boolean> key in req.body
// Returns 204
router.patch('/api/users/:id/permissions', async (req, res, next) => {
const status = 204
try {
const { admin } = req.body
// Request body does not include an admin key with a boolean value 400
if (!( admin === true || admin === false)) {
const error = new Error (`Invalid request.`)
error.status = 400
next(error)
}

// Valid JWT is not provided 401
const token = req.headers.authorization.split('Bearer ')[1]
if (!token) {
const error = new Error (`Invalid token.`)
error.status = 401
next(error)
}
const payload = jsonwebtoken.verify(token, SECRET_KEY)

// JWT token is for a user who is not an admin 401
const clientUser = await User.findOne({ _id: payload.id })
.select('-__v -password')
if (!clientUser.admin === true) {
const error = new Error (`Insufficient privelages.`)
error.status = 401
next(error)
}

// User cannot be found 404
const userToUpdate = await User.findOne({_id: req.params.id})
.select('-__v -password')
if (!userToUpdate) {
const error = new Error (`User cannot be found.`)
error.status = 404
next(error)
}

Object.assign(userToUpdate, admin)
await user.save()
Copy link
Collaborator

Choose a reason for hiding this comment

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

This shouldn't work, as there's no user variable.


res.json({ status, user })
} catch (e) {
console.error(e)
next(e)
}
})

module.exports = router
113 changes: 97 additions & 16 deletions api/routes/books.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
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
Expand All @@ -26,53 +29,131 @@ 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
const status = 201
try {
// Valid JWT is not provided 401
const token = req.headers.authorization.split('Bearer ')[1]
if (!token) {
const error = new Error (`Invalid token.`)
error.status = 401
next(error)
}
const payload = jsonwebtoken.verify(token, SECRET_KEY)

// JWT token is for a user who is not an admin 401
const clientUser = await User.findOne({ _id: payload.id })
.select('-__v -password')
if (!clientUser.admin === true) {
const error = new Error (`Insufficient privelages.`)
error.status = 401
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) {
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)
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) => {
const { id } = req.params
try {
const token = req.headers.authorization.split('Bearer ')[1]
// A valid JWT token is not provided (status 401)
if (!token) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You don't want to just check whether or not there is a token, you want to check whether or not the token is valid. In this case, you should use payload from below.

const error = new Error (`Invalid token.`)
error.status = 401
next(error)
}
const payload = jsonwebtoken.verify(token, SECRET_KEY)

const book = await Book.findById(id)
// Book cannot be found (status 404)
if (!book) {
const error = new Error(`Invalid Book _id: ${id}`)
error.message = 404
return next(error)
}

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 })
// The book is already reserved (status 400)
if (book.reserved.status === true) {
const error = new Error(`Book is already reserved.`)
error.message = 400
return next(error)

} else if (book.reserved.status === false){
book.reserved.status = true
// Set the reserved memberId to the current user
book.reserved.memberId = payload.id
await book.save()

const response = book
const status = 200
res.json({ status, response })
}
} catch (e) {
console.error(e)
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 { id } = req.params
try {
const token = req.headers.authorization.split('Bearer ')[1]
// A valid JWT token is not provided (status 401)
if (!token) {
const error = new Error (`Invalid token.`)
error.status = 401
next(error)
}
const payload = jsonwebtoken.verify(token, SECRET_KEY)

const book = await Book.findById(id)
// Book cannot be found (status 404)
if (!book) {
const error = new Error(`Invalid Book _id: ${id}`)
error.message = 404
return next(error)
}

// The book is not reserved (status 400)
if (book.reserved.status === false) {
const error = new Error(`Book is not reserved.`)
error.message = 400
return next(error)
}

// The book is reserved by a different user (status 401)
if (book.reserved.memberId != payload.id) {
const error = new Error(`Book is reserved by a different user.`)
error.message = 401
return next(error)
}

if (book.reserved.status === true){
// Set the reserved memberId to the current user
book.reserved.status = false
book.reserved.memberId = null
await book.save()

const response = book
const status = 200
res.json({ status, response })
}
} catch (e) {
console.error(e)
next(e)
}
})

module.exports = router
2 changes: 2 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ 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: 21 additions & 2 deletions db/seeds.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
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 User.deleteMany() // Deletes all records

const book = await Book.create([
{
title: 'The Colour of Magic',
published: 1983,
Expand Down Expand Up @@ -41,9 +47,22 @@ const reset = async () => {
]
}
])
const user = await User.create([
{
username: 'user',
password: await bcrypt.hash('password', saltRounds),
admin: true
},
{
username: 'admin',
password: await bcrypt.hash('password', saltRounds)
}
])
return { book, user }
}

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