Ported admin-type user validatoin from p2p vps server

This commit is contained in:
Chris Troutner
2019-05-16 15:24:55 -07:00
parent 6d80e0173f
commit 00fa492c86
6 changed files with 141 additions and 32 deletions
+99 -7
View File
@@ -3,30 +3,122 @@ const config = require('../../config')
const getToken = require('../utils/auth')
const jwt = require('jsonwebtoken')
module.exports = async function ensureUser (ctx, next) {
//console.log(`getToken: ${typeof (getToken)}`)
async function ensureUser (ctx, next) {
// console.log(`getToken: ${typeof (getToken)}`)
const token = getToken(ctx)
if (!token) {
//console.log(`Err: Token not provided.`)
// console.log(`Err: Token not provided.`)
ctx.throw(401)
}
let decoded = null
try {
//console.log(`token: ${JSON.stringify(token, null, 2)}`)
//console.log(`config: ${JSON.stringify(config, null, 2)}`)
// console.log(`token: ${JSON.stringify(token, null, 2)}`)
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
decoded = jwt.verify(token, config.token)
} catch (err) {
//console.log(`Err: Token could not be decoded: ${err}`)
// console.log(`Err: Token could not be decoded: ${err}`)
ctx.throw(401)
}
ctx.state.user = await User.findById(decoded.id, '-password')
if (!ctx.state.user) {
//console.log(`Err: Could not find user.`)
// console.log(`Err: Could not find user.`)
ctx.throw(401)
}
return next()
}
// This funciton is almost identical to ensureUser, except at the end, it verifies
// that the 'type' associated with the user equals 'admin'.
async function ensureAdmin (ctx, next) {
// console.log(`getToken: ${typeof (getToken)}`)
const token = getToken(ctx)
if (!token) {
// console.log(`Err: Token not provided.`)
ctx.throw(401)
}
let decoded = null
try {
// console.log(`token: ${JSON.stringify(token, null, 2)}`)
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
decoded = jwt.verify(token, config.token)
} catch (err) {
// console.log(`Err: Token could not be decoded: ${err}`)
ctx.throw(401)
}
ctx.state.user = await User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
ctx.throw(401)
}
if (ctx.state.user.type !== 'admin') {
ctx.throw(401, 'not admin')
}
return next()
}
// This middleware ensures that the :id used in the API endpoint matches the
// the ID used in the JWT, or failing that, the ID used in the JWT matches
// an Admin user. This prevents situations like users updating other users
// profiles or non-admins deleting users.
// TODO Tests must be developed before developing this function.
async function ensureTargetUserOrAdmin (ctx, next) {
// console.log(`getToken: ${typeof (getToken)}`)
const token = getToken(ctx)
if (!token) {
// console.log(`Err: Token not provided.`)
ctx.throw(401)
}
// The user ID targeted in this API call.
const targetId = ctx.params.id
// console.log(`targetId: ${JSON.stringify(targetId, null, 2)}`)
let decoded = null
try {
// console.log(`token: ${JSON.stringify(token, null, 2)}`)
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
decoded = jwt.verify(token, config.token)
} catch (err) {
// console.log(`Err: Token could not be decoded: ${err}`)
ctx.throw(401)
}
ctx.state.user = await User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
ctx.throw(401)
}
// console.log(`ctx.state.user: ${JSON.stringify(ctx.state.user, null, 2)}`)
// Ensure the calling user and the target user are the same.
if (ctx.state.user._id.toString() !== targetId.toString()) {
console.log(`Calling user and target user do not match!`)
console.log(`Calling user: ${ctx.state.user._id}`)
console.log(`Target user: ${targetId}`)
// If they don't match, then the calling user better be an admin.
if (ctx.state.user.type !== 'admin') {
ctx.throw(401, 'not admin')
} else {
console.log(`It's ok. The user is an admin.`)
}
}
return next()
}
module.exports = {
ensureUser,
ensureAdmin,
ensureTargetUserOrAdmin
}
+8 -8
View File
@@ -4,7 +4,7 @@ const config = require('../../config')
const jwt = require('jsonwebtoken')
const User = new mongoose.Schema({
type: { type: String, default: 'User' },
type: { type: String, default: 'user' },
name: { type: String },
username: { type: String, required: true, unique: true },
password: { type: String, required: true }
@@ -23,16 +23,16 @@ User.pre('save', function preSave (next) {
resolve(salt)
})
})
.then(salt => {
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) { throw new Error(err) }
.then(salt => {
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) { throw new Error(err) }
user.password = hash
user.password = hash
next(null)
next(null)
})
})
})
.catch(err => next(err))
.catch(err => next(err))
})
User.methods.validatePassword = function validatePassword (password) {
+12
View File
@@ -16,6 +16,7 @@ const User = require('../../models/users')
*
* @apiSuccess {Object} users User object
* @apiSuccess {ObjectId} users._id User id
* @apiSuccess {String} user.type User type (admin or user)
* @apiSuccess {String} users.name User name
* @apiSuccess {String} users.username User username
*
@@ -69,6 +70,7 @@ async function createUser (ctx) {
*
* @apiSuccess {Object[]} users Array of user objects
* @apiSuccess {ObjectId} users._id User id
* @apiSuccess {String} user.type User type (admin or user)
* @apiSuccess {String} users.name User name
* @apiSuccess {String} users.username User username
*
@@ -101,6 +103,7 @@ async function getUsers (ctx) {
*
* @apiSuccess {Object} users User object
* @apiSuccess {ObjectId} users._id User id
* @apiSuccess {String} user.type User type (admin or user)
* @apiSuccess {String} users.name User name
* @apiSuccess {String} users.username User username
*
@@ -153,6 +156,7 @@ async function getUser (ctx, next) {
*
* @apiSuccess {Object} users User object
* @apiSuccess {ObjectId} users._id User id
* @apiSuccess {String} user.type User type (admin or user)
* @apiSuccess {String} users.name Updated name
* @apiSuccess {String} users.username Updated username
*
@@ -180,8 +184,16 @@ async function getUser (ctx, next) {
async function updateUser (ctx) {
const user = ctx.body.user
// Save a copy of the original user type.
const userType = user.type
Object.assign(user, ctx.request.body.user)
// Unless the calling user is an admin, they can not change the user type.
if (userType !== 'admin') {
user.type = userType
}
await user.save()
ctx.body = {
+5 -5
View File
@@ -1,4 +1,4 @@
const ensureUser = require('../../middleware/validators')
const validator = require('../../middleware/validators')
const user = require('./controller')
// export const baseUrl = '/users'
@@ -16,7 +16,7 @@ module.exports.routes = [
method: 'GET',
route: '/',
handlers: [
ensureUser,
validator.ensureUser,
user.getUsers
]
},
@@ -24,7 +24,7 @@ module.exports.routes = [
method: 'GET',
route: '/:id',
handlers: [
ensureUser,
validator.ensureUser,
user.getUser
]
},
@@ -32,7 +32,7 @@ module.exports.routes = [
method: 'PUT',
route: '/:id',
handlers: [
ensureUser,
validator.ensureTargetUserOrAdmin,
user.getUser,
user.updateUser
]
@@ -41,7 +41,7 @@ module.exports.routes = [
method: 'DELETE',
route: '/:id',
handlers: [
ensureUser,
validator.ensureTargetUserOrAdmin,
user.getUser,
user.deleteUser
]
+13 -8
View File
@@ -8,18 +8,15 @@ const utils = require('./utils')
const rp = require('request-promise')
const assert = require('chai').assert
should()
// const request = supertest.agent(app.listen())
const context = {}
const LOCALHOST = 'http://localhost:5000'
describe('Auth', () => {
before(async () => {
await app.startServer()
utils.cleanDb()
@@ -60,7 +57,7 @@ describe('Auth', () => {
let result = await rp(options)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
assert(false, 'Unexpected result')
@@ -95,10 +92,18 @@ describe('Auth', () => {
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert(result.statusCode === 200, 'Status Code 200 expected.')
assert(result.body.user.username === 'test', 'Username of test expected')
assert(result.body.user.password === undefined, 'Password expected to be omited')
assert(
result.body.user.username === 'test',
'Username of test expected'
)
assert(
result.body.user.password === undefined,
'Password expected to be omited'
)
} catch (err) {
console.log('Error authenticating test user: ' + JSON.stringify(err, null, 2))
console.log(
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
)
throw err
}
})
+4 -4
View File
@@ -277,7 +277,7 @@ describe('Users', () => {
}
})
it("should throw 404 if user doesn't exist", async () => {
it('should throw 401 if non-admin updating other user', async () => {
const { token } = context
try {
@@ -295,7 +295,7 @@ describe('Users', () => {
await rp(options)
assert.equal(true, false, 'Unexpected behavior')
} catch (err) {
assert.equal(err.statusCode, 404)
assert.equal(err.statusCode, 401)
}
})
@@ -355,7 +355,7 @@ describe('Users', () => {
}
})
it('should throw 404 if user doesn\'t exist', async () => {
it('should throw 401 if deleting other user', async () => {
const { token } = context
try {
@@ -373,7 +373,7 @@ describe('Users', () => {
await rp(options)
assert.equal(true, false, 'Unexpected behavior')
} catch (err) {
assert.equal(err.statusCode, 404)
assert.equal(err.statusCode, 401)
}
})