Skip to content

Commit

Permalink
Add /users/:id route
Browse files Browse the repository at this point in the history
* includes tests
* user must be owner to access information
* server side for issue #60
  • Loading branch information
etmoore committed May 3, 2017
1 parent 01cdc5c commit 32a2881
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 66 deletions.
2 changes: 2 additions & 0 deletions server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser')

const asksRoutes = require('./routes/asks')
const usersRoutes = require('./routes/users')
const authRoutes = require('./routes/auth')

const app = express()
Expand All @@ -26,6 +27,7 @@ app.use(cookieParser())
app.use(express.static(path.join(__dirname, 'public')))

app.use('/api/v1/asks', asksRoutes)
app.use('/api/v1/users', usersRoutes)
app.use('/api/v1/auth', authRoutes)

// catch 404 and forward to error handler
Expand Down
23 changes: 15 additions & 8 deletions server/db/seeds/01_users.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
const authHelpers = require('../../helpers/auth')

exports.seed = (knex, Promise) => {
exports.seed = knex => {
return knex('asks').del()
.then(() => knex('users').del())
.then(() => {
const pwHash = authHelpers.hashPassword('testuser123')
return knex('users').insert({
firstName: 'Test',
lastName: 'User',
email: '[email protected]',
password: pwHash
})
return knex('users').insert([
{
firstName: 'Test',
lastName: 'User',
email: '[email protected]',
password: authHelpers.hashPassword('testuser123')
},
{
firstName: 'Another',
lastName: 'User',
email: '[email protected]',
password: authHelpers.hashPassword('anotheruser123')
}
])
})
}
20 changes: 13 additions & 7 deletions server/helpers/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,25 @@ function encodeToken (user, exp) {
}

function decodeToken (token, callback) {
jwt.verify(token, process.env.TOKEN_SECRET, callback)
return jwt.verify(token, process.env.TOKEN_SECRET, callback)
}

function protect (req, res, next) {
function decodeTokenSync (token) {
return jwt.verify(token, process.env.TOKEN_SECRET)
}

function requireLogin (req, res, next) {
if (!req.headers.authorization) {
return next(new Error('Please log in'))
const error = new Error('Please log in')
error.status = 401
return next(error)
}
const header = req.headers.authorization.split(' ')
const token = header[1]
decodeToken(token, (err, payload) => {
if (err) {
res.status(401).json({
status: err.message
})
err.status = 401
next(err)
} else {
const userID = parseInt(payload.sub, 10)
// confirm that the user is still in the DB
Expand All @@ -51,5 +56,6 @@ module.exports = {
hashPassword,
encodeToken,
decodeToken,
protect
decodeTokenSync,
requireLogin
}
8 changes: 4 additions & 4 deletions server/routes/asks.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const express = require('express')
const Ask = require('../db/models/Ask')
const protect = require('../helpers/auth').protect
const requireLogin = require('../helpers/auth').requireLogin

const router = express.Router()

Expand All @@ -16,21 +16,21 @@ router.get('/:id', (req, res, next) => {
.catch(err => next(err))
})

router.post('/', protect, (req, res, next) => {
router.post('/', requireLogin, (req, res, next) => {
Ask.createAsk(req.body)
.then(askID => Ask.getAsk(askID))
.then(ask => res.json(ask))
.catch(err => next(err))
})

router.put('/:id', protect, (req, res, next) => {
router.put('/:id', requireLogin, (req, res, next) => {
Ask.updateAsk(req.params.id, req.body)
.then(id => Ask.getAsk(id))
.then(updatedJob => res.json(updatedJob))
.catch(err => next(err))
})

router.delete('/:id', protect, (req, res, next) => {
router.delete('/:id', requireLogin, (req, res, next) => {
Ask.deleteAsk(req.params.id)
.then(() => res.json({ status: 'success' }))
.catch(err => next(err))
Expand Down
6 changes: 0 additions & 6 deletions server/routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,4 @@ router.post('/login', (req, res, next) => {
.catch(err => next(err))
})

router.get('/user', authHelpers.protect, (req, res, next) => {
res.status(200).json({
status: 'success'
})
})

module.exports = router
30 changes: 30 additions & 0 deletions server/routes/users.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const express = require('express')
const _ = require('lodash')
const User = require('../db/models/User')
const authHelpers = require('../helpers/auth')

const router = express.Router()

router.get('/:id', authHelpers.requireLogin, (req, res, next) => {
const requestedUserID = parseInt(req.params.id, 10)
const authToken = req.headers.authorization.split(' ')[1]
const currentUserID = authHelpers.decodeTokenSync(authToken).sub
User.getUserByID(requestedUserID)
.then((user) => {
if (!user) {
const error = new Error('User not found')
error.status = 404
return next(error)
}
if (requestedUserID !== currentUserID) {
const error = new Error('You are not authorized to access this page')
error.status = 403
return next(error)
}
user = _.pick(user, ['firstName', 'lastName', 'email'])
return res.json(user)
})
.catch(err => next(err))
})

module.exports = router
8 changes: 4 additions & 4 deletions server/test/ask_routes.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const authHelpers = require('../helpers/auth')
const should = chai.should()
chai.use(chaiHttp)

describe('API Routes', function () {
describe('Ask Routes', function () {
beforeEach(() => {
return knex.migrate.rollback()
.then(() => knex.migrate.latest())
Expand Down Expand Up @@ -114,7 +114,7 @@ describe('API Routes', function () {
})
.end((err, res) => {
should.exist(err)
res.should.have.status(500)
res.should.have.status(401)
res.should.be.json
res.body.status.should.eql('error')
res.body.status.should.eql('error')
Expand Down Expand Up @@ -182,7 +182,7 @@ describe('API Routes', function () {
.end((err, res) => {
should.exist(err)
res.should.be.json
res.should.have.status(500)
res.should.have.status(401)
res.body.status.should.eql('error')
res.body.error.should.eql('Please log in')
done()
Expand Down Expand Up @@ -228,7 +228,7 @@ describe('API Routes', function () {
})
.catch((err) => {
should.exist(err)
err.status.should.eql(500)
err.status.should.eql(401)
})
})
})
Expand Down
38 changes: 1 addition & 37 deletions server/test/auth_routes.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const should = chai.should()

chai.use(chaiHttp)

describe('API Routes', function () {
describe('Auth Routes', function () {
beforeEach(() => {
return knex.migrate.rollback()
.then(() => knex.migrate.latest())
Expand Down Expand Up @@ -94,40 +94,4 @@ describe('API Routes', function () {
})
})
})

describe('GET api/v1/auth/user', () => {
it('should return with status: success', (done) => {
chai.request(server)
.post('/api/v1/auth/login')
.send({
email: '[email protected]',
password: 'testuser123'
})
.end((error, response) => {
should.not.exist(error)
const token = response.body.token
chai.request(server)
.get('/api/v1/auth/user')
.set('authorization', `Bearer ${token}`)
.end((err, res) => {
should.not.exist(err)
res.status.should.eql(200)
res.should.be.json
res.body.status.should.eql('success')
done()
})
})
})
it('should throw an error if a user is not logged in', (done) => {
chai.request(server)
.get('/api/v1/auth/user')
.end((err, res) => {
should.exist(err)
res.status.should.eql(500)
res.should.be.json
res.body.status.should.eql('error')
done()
})
})
})
})
18 changes: 18 additions & 0 deletions server/test/spec_helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const chai = require('chai')
const chaiHttp = require('chai-http')
const server = require('../app')

chai.use(chaiHttp)

function loginUser () {
return chai.request(server)
.post('/api/v1/auth/login')
.send({
email: '[email protected]',
password: 'testuser123'
})
}

module.exports = {
loginUser
}
102 changes: 102 additions & 0 deletions server/test/user_routes.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/* eslint-disable no-undef, no-unused-expressions, handle-callback-err */
process.env.NODE_ENV = 'test'

const chai = require('chai')
const chaiHttp = require('chai-http')
const server = require('../app')
const knex = require('../db/knex')
const User = require('../db/models/User')
const should = chai.should()
const authHelpers = require('../helpers/auth')
const specHelpers = require('./spec_helpers')
chai.use(chaiHttp)

describe('User Routes', function () {
beforeEach(() => {
return knex.migrate.rollback()
.then(() => knex.migrate.latest())
.then(() => knex.seed.run())
})

afterEach(() => {
return knex.migrate.rollback()
})

describe('GET /api/v1/user/:id', () => {
it('provides user data to authenticated user', (done) => {
specHelpers.loginUser()
.end((err, res) => {
const authToken = res.body.token
const userID = authHelpers.decodeTokenSync(authToken).sub
chai.request(server)
.get(`/api/v1/users/${userID}`)
.set('authorization', `Bearer ${authToken}`)
.end((err, res) => {
should.not.exist(err)
res.should.be.json
res.status.should.eql(200)
res.body.should.include.keys('firstName', 'lastName', 'email')
res.body.should.not.include.keys('password')
done()
})
})
})
it('returns 404 error if the user does not exist', (done) => {
specHelpers.loginUser()
.end((err, res) => {
const authToken = res.body.token
const userID = authHelpers.decodeTokenSync(authToken).sub
chai.request(server)
.get('/api/v1/users/0')
.set('authorization', `Bearer ${authToken}`)
.end((err, res) => {
should.exist(err)
res.should.be.json
res.status.should.equal(404)
res.body.status.should.equal('error')
res.body.error.should.equal('User not found')
done()
})
})
})
it('returns 401 error if the user provides no authentication', (done) => {
specHelpers.loginUser()
.end((err, res) => {
const authToken = res.body.token
const userID = authHelpers.decodeTokenSync(authToken).sub
chai.request(server)
.get(`/api/v1/users/${userID}`)
// NOT setting authentication header
.end((err, res) => {
should.exist(err)
res.should.be.json
res.status.should.equal(401)
res.body.status.should.equal('error')
res.body.error.should.equal('Please log in')
done()
})
})
})
it('returns 403 error if the user does not own the profile', (done) => {
specHelpers.loginUser()
.end((err, res) => {
const authToken = res.body.token
User.getUserByEmail('[email protected]')
.then((anotherUser) => {
chai.request(server)
.get(`/api/v1/users/${anotherUser.id}`)
.set('authorization', `Bearer ${authToken}`)
.end((err, res) => {
should.exist(err)
res.should.be.json
res.status.should.equal(403)
res.body.status.should.equal('error')
res.body.error.should.equal('You are not authorized to access this page')
done()
})
})
.catch(err => console.log(err))
})
})
})
})

0 comments on commit 32a2881

Please sign in to comment.