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 = new mongoose.Schema({
username: {
type: String,
required: true,
},
password: {
type: String,
min: 8
},
admin: {
type: Boolean,
default: false
}
}, { timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } })

module.exports = mongoose.model('User', schema)
105 changes: 105 additions & 0 deletions api/routes/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const { SECRET_KEY } = process.env
const router = require('express').Router()
const bcrypt = require('bcrypt')
const jsonwebtoken = require('jsonwebtoken')
const User = require('../models/user')

router.post('/signup', async (req, res, next) => {
const status = 200
try {
const { username, password } = req.body
const user = await User.findOne({ username })
if (user) throw new Error (`User already exists.`)

//Create User, if user does not yet exist.
const saltrounds = 12
const hashPass = await bcrypt.hash(password, saltrounds)
const response = await User.create({username, password: hashPass})

res.status(status).json({ status, response })

} catch (e) {
console.error(e)

const error = new Error('User already exists or does not meet validation requirements.')
error.status = 422
next(error)

res.status(status).json({ status, message })

}

})

router.post('/login', async (req, res, next) => {

try {
const { username, password } = req.body
const user = await User.findOne({ username })
if (!user) throw new Error('Username could not be found.')

const validPassword = await bcrypt.compare(password, user.password)
if (!validPassword) throw new Error('Password is incorrect.')
Copy link
Collaborator

Choose a reason for hiding this comment

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

You'll want to give the same errors in either case. Otherwise, it's easier for someone to "guess" at a valid username.



// JWT Create
const status = 201
const payload = { id: user._id}
const options = { expiresIn: '1 hour'}
const jwt = jsonwebtoken.sign(payload, SECRET_KEY, options)
res.status(status).json({status, jwt})

} catch (e) {
status = 400
console.error(e)
const error = new Error('Unable to log in.')
error.status = status

res.status(status).json({ status, message })

}


})

router.patch('/:id/permissions', async (req, res, next) => {
try {
validToken = tokenValidate(req);
const user = await User.findOne({ _id: validToken.id })

//Check for valid token
if (!tokenValidate) { res.status(401).json({ status, response: 'Token is not valid.' })}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should probably be validToken instead, right?


//Check if admin value is boolean
if (!typeof req.body.admin === "boolean"){ res.status(400).json({ status, response: 'Admin value is not boolean.' })}

//Check if Admin
if (!user.admin == true) { res.status(400).json({ status, response: 'User is not an Admin.' })

//Try and see if user can be found,
if (!user == null) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This statement is a bit odd. I think what you want here is if (!user).

user.admin = req.body.admin
const user = 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 should be:

await user.save()

} else { res.status(400).json({ status, response: 'User does not exist.' })}

}
} catch (e) {
status = 422
console.error(e)
const error = new Error('An error occured.')
error.status = status
res.status(status).json({ status })

}

})


function tokenValidate(req) {
const token = req.headers.authorization.split('Bearer ')[1]
const payload = jsonwebtoken.verify(token, SECRET_KEY)

return payload
}

module.exports = router
120 changes: 82 additions & 38 deletions api/routes/books.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
const router = require('express').Router()
const Book = require('../models/book')
const User = require('../models/user')

router.get('/', async (req, res, next) => {
const status = 200
const response = await Book.find().select('-__v')


validToken = tokenValidate(req);
const user = await User.findOne({ _id: validToken.id })
if (user.admin == true) {

const status = 200
const response = await Book.find().select('-__v')
} else {
status = 401
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd be surprised if this worked? For this route, it's OK to let anyone request this information. But, it also looks like response won't be defined in the case the user is not an admin.

res.json({ status, response })
})

Expand All @@ -26,53 +34,89 @@ 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 book = await Book.create(req.body)
if (!book) throw new Error(`Request body failed: ${JSON.stringify(req.body)}`)
validToken = tokenValidate(req);
const user = await User.findOne({ _id: validToken.id })

if (!user.admin == true) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is the opposite of what you want. We only want the user to be able to create a book if they are an admin. So, I think we just need if (user.admin) here.

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 })
} 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)
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)
}
} else { res.status(401).json({ status, response: 'User is not admin.' })}
})

// 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 book = await Book.findById(id)
if (!book) {
const error = new Error(`Invalid Book _id: ${id}`)
error.message = 404
return next(error)
}
validToken = tokenValidate(req);
const user = await User.findOne({ _id: validToken.id })
if (validToken) {
try {
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) {
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 })
} else { res.status(400).json({ status, response: 'The book is already reserved' })}

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 })
} catch (e) {
console.error(e)
}
})
} catch (e) {
console.error(e)
}
} else { res.status(401).json({ status, response: 'Invalid Token' })}
})

// 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 })
validToken = tokenValidate(req);
const user = await User.findOne({ _id: validToken.id })

if (validToken) {
const status = 200
const book = await Book.findById(id)

// 404 if unable to find book.
if (!book) { res.status(404).json({ status, response: 'Unable to find book' })}

// 400 if book is not reserved.
if (book.reserved.status == false) { res.status(400).json({ status, response: 'The book is not reserved' })}

// 401 if ID of the user that checked out the book does not match.
if (tokenValidate._id != book.reserved.memberID) { res.status(401).json({ status, response: 'The book is reserved but not by this user.' })}

// After validation, set reserved status to false, and memberId to null
book.reserved.status = false
reserved.memberId = null

console.log(message)
res.status(status).json({ status, message })
} else { res.status(401).json({ status, response: 'Invalid Token' })}
})

function tokenValidate(req) {
const token = req.headers.authorization.split('Bearer ')[1]
const payload = jsonwebtoken.verify(token, SECRET_KEY)

return payload
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

You could just export this function so you don't need to copy it everywhere!

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
18 changes: 17 additions & 1 deletion db/seeds.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
const mongoose = require('mongoose')
const Book = require('../api/models/book')
const User = require('../api/models/user')
const config = require('../nodemon.json')

const reset = async () => {
mongoose.connect(config.env.MONGO_DB_CONNECTION, { useNewUrlParser: true })
await Book.deleteMany() // Deletes all records
await Book.deleteMany() // Deletes all records for books.
await User.deleteMany() // Deletes all records for users.

return await Book.create([
{
title: 'The Colour of Magic',
Expand Down Expand Up @@ -43,6 +46,19 @@ const reset = async () => {
])
}

return await User.create([
{
username: 'superAdmin',
password: 'Passw0rd1',
admin: true
},
{
username: 'User',
password: 'Passw0rd',
admin: false
}
])

reset().catch(console.error).then((response) => {
console.log(`Seeds successful! ${response.length} records created.`)
return mongoose.disconnect()
Expand Down
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": ""
}
}
Loading