nfp_sites/base/authentication/security.mjs

63 lines
1.8 KiB
JavaScript

import crypto from 'crypto'
import { HttpError } from 'flaska'
import { decode, encode } from '../util.mjs'
import config from '../config.mjs'
const levels = {
Normal: 1,
Manager: 10,
Admin: 100,
}
const issuer = config.get('mssql:connectionUser')
const secret = config.get('jwtsecret')
export function authenticate(minLevel = levels.Manager) {
return function(ctx) {
if (!ctx.req.headers.authorization) {
throw new HttpError(401, 'Authentication token missing')
}
if (!ctx.req.headers.authorization.startsWith('Bearer ')) {
throw new HttpError(401, 'Authentication token invalid')
}
let parts = ctx.req.headers.authorization.slice(7).split('.')
if (parts.length !== 4) {
throw new HttpError(401, 'Authentication token invalid')
}
const hmac = crypto.createHmac('sha256', secret)
const token = [parts[0], parts[1], parts[2]].join('.')
hmac.update(token)
let apiSignature = encode(hmac.digest())
if (apiSignature !== parts[3]) {
throw new HttpError(401, 'Authentication token invalid signature')
}
let header
let body
try {
header = JSON.parse(decode(parts[0]).toString('utf8'))
body = JSON.parse(decode(parts[1]).toString('utf8'))
} catch (err) {
throw new HttpError(401, 'Authentication token invalid json')
}
if (header.alg !== 'HS256') {
throw new HttpError(401, 'Authentication token invalid alg')
}
let unixNow = Math.floor(Date.now() / 1000)
// Validate token, add a little skew support for issued_at
if (body.iss !== issuer || !body.iat || !body.exp
|| body.iat > unixNow + 300 || body.exp <= unixNow) {
throw new HttpError(403, 'Authentication token expired or invalid')
}
ctx.state.auth_user = body
ctx.state.auth_token = token
}
}