Files
ipfs-bch-wallet-consumer/src/controllers/rest-api/middleware/validators.js
T

195 lines
5.8 KiB
JavaScript

/*
REST API validator middleware.
These are a series of functions that ensure the user making a REST API
matches a user in the database (or not). In can do fine-grain user control
such as telling the difference between an admin, a normal user, and an
anonymous user.
This middleware is used to gatekeep access to different REST API resources.
CT 9/17/22:
This library was lightly refactored to make it work with the new unit tests.
This not and the commented code below can be deleted once it is verified that
this refactor did not result in any breaking changes.
*/
import User from '../../../adapters/localdb/models/users.js'
import config from '../../../../config/index.js'
import jwt from 'jsonwebtoken'
import wlogger from '../../../adapters/wlogger.js'
let _this
class Validators {
constructor () {
this.User = User
this.jwt = jwt
this.config = config
_this = this
}
async ensureUser (ctx, next) {
try {
const token = _this.getToken(ctx)
if (!token) {
throw new Error('Token could not be retrieved from header')
}
let decoded = null
try {
// console.log(`token: ${JSON.stringify(token, null, 2)}`)
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
decoded = _this.jwt.verify(token, config.token)
} catch (err) {
throw new Error('Could not verify JWT')
}
ctx.state.user = await _this.User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log('Err: Could not find user.')
throw new Error('Could not find user')
}
// return next()
return true
} catch (error) {
// console.log('Ensure user error: ', error)
// console.log('ctx: ', ctx)
ctx.status = 401
ctx.throw(401, error.message)
}
}
// This funciton is almost identical to ensureUser, except at the end, it verifies
// that the 'type' associated with the user equals 'admin'.
async ensureAdmin (ctx, next) {
try {
// console.log(`getToken: ${typeof (getToken)}`)
const token = _this.getToken(ctx)
if (!token) {
// console.log(`Err: Token not provided.`)
// ctx.throw(401)
throw new Error('Token could not be retrieved from header')
}
let decoded = null
try {
// console.log(`token: ${JSON.stringify(token, null, 2)}`)
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
decoded = _this.jwt.verify(token, config.token)
} catch (err) {
// console.log(`Err: Token could not be decoded: ${err}`)
// ctx.throw(401)
throw new Error('Could not verify JWT')
}
ctx.state.user = await _this.User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
// ctx.throw(401)
throw new Error('Could not find user')
}
if (ctx.state.user.type !== 'admin') {
// ctx.throw(401, 'not admin')
throw new Error('User is not an admin')
}
// return next()
return true
} catch (error) {
ctx.status = 401
ctx.throw(401, error.message)
}
}
// 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.
async ensureTargetUserOrAdmin (ctx, next) {
try {
// console.log(`getToken: ${typeof (getToken)}`)
const token = _this.getToken(ctx)
if (!token) {
// console.log(`Err: Token not provided.`)
// ctx.throw(401)
throw new Error('Token could not be retrieved from header')
}
// 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 = _this.jwt.verify(token, config.token)
} catch (err) {
console.log(`Err: Token could not be decoded: ${err}`)
// ctx.throw(401)
throw new Error('Could not verify JWT')
}
ctx.state.user = await _this.User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
// ctx.throw(401)
throw new Error('Could not find user')
}
// console.log('ctx.state.user: ', ctx.state.user)
// 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()) {
wlogger.verbose(
`Calling user and target user do not match! Calling user: ${ctx.state.user._id}, 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')
throw new Error('User is not an admin')
} else {
wlogger.verbose("It's ok. The user is an admin.")
}
}
// return next()
return true
} catch (error) {
// console.log('Error in ensureTargetUserOrAdmin(): ', error)
ctx.status = 401
ctx.throw(401, error.message)
}
}
getToken (ctx) {
const header = ctx.request.header.authorization
if (!header) {
return null
}
const parts = header.split(' ')
if (parts.length !== 2) {
return null
}
const scheme = parts[0]
const token = parts[1]
if (/^Bearer$/i.test(scheme)) {
return token
}
return null
}
}
export default Validators