advms-koa-auth
Version:
140 lines (121 loc) • 4.16 kB
JavaScript
const request = require('request-promise-native')
const jwt = require('jsonwebtoken')
const fs = require('fs')
const keyPath = process.env.JWT_KEY || 'certs/public.pem'
const log = require('debug')('advms-auth')
module.exports = function (options, router) {
const cert = (() => {
return fs.existsSync(keyPath)
? fs.readFileSync(keyPath)
: request({
method: 'GET',
uri: options.oauthUrl + '/public.pem'
})
})()
const auth = async function (ctx, next) {
log('start')
const token = ctx.request.get(options.header || 'authorization') ||
(options.cookie && 'Bearer ' + ctx.cookies.get(options.cookie))
log('fetching public key for auth')
const key = await cert
log('public key fetched')
try {
ctx.user = await new Promise((resolve, reject) => {
if (!token || !token.startsWith('Bearer ')) return reject(new Error('authorization header validation failed'))
log('token verify start')
jwt.verify(token.substring(7), key, { algorithms: ['RS256'] }, (e, d) => {
log('verify done')
if (e) return reject(e)
resolve(d)
})
})
if (!ctx.user || (options.claim && !ctx.user[options.claim])) {
throw new Error('no user or claim is invalid')
}
} catch (e) {
log('error:', e)
ctx.throw(401)
}
await next()
}
if (!router) {
log('no router passed. We done.')
return auth
}
router.get('callback', '/auth/callback', async ctx => {
log('callback request recieved')
let token = {}
try {
log('requesting auth token')
token = JSON.parse(await request({
method: 'POST',
uri: options.oauthUrl + '/token',
form: {
grant_type: 'authorization_code',
client_id: options.client_id,
client_secret: options.client_secret,
code: ctx.query.code,
redirect_uri: ctx.origin + router.url('callback')
}
}))
log('auth token request complete')
} catch (e) {
log('token request error:', e.message)
}
if (token.error) {
log('token request fs server error', token.error)
return
}
log('storing token in cookie')
let date = new Date()
date.setSeconds(date.getSeconds() + token.expires_in - 60)
ctx.cookies.set(options.cookie, token.access_token, {
path: options.cookie_path,
expires: date,
secure: ctx.request.secure
})
log('token saved')
return ctx.redirect(Buffer.from(ctx.query.state, 'base64').toString('utf8'))
})
router.get('/auth/logout', async ctx => {
let date = new Date()
date.setSeconds(date.getSeconds() - 60)
ctx.cookies.set(options.cookie, null, {
path: options.cookie_path,
expires: date,
secure: ctx.request.secure
})
ctx.redirect(options.oauthUrl + '/authorize/logout' +
'?response_type=code' +
'&client_id=' + options.client_id +
'&state=' + Buffer.from(options.redirect).toString('base64') +
'&redirect_uri=' + ctx.origin + router.url('callback'))
})
router.use(async (ctx, next) => {
log('authorization middleware')
let action = '/authorize/login'
if (ctx.cookies.get(options.cookie)) {
try {
ctx.request.headers.authorization = 'Bearer ' + ctx.cookies.get(options.cookie)
await auth(ctx, next)
log('authorization middleware is done')
return
} catch (e) {
if (e.message !== 'Unauthorized') throw e
action = '/authorize/logout'
}
}
log('redirecting to fs server', action)
ctx.redirect(options.oauthUrl + action +
'?response_type=code' +
'&client_id=' + options.client_id +
'&state=' + Buffer.from(ctx.request.url).toString('base64') +
'&redirect_uri=' + ctx.origin + router.url('callback'))
})
router.get('/auth/login', async ctx => {
log('redirecting to fs server')
ctx.redirect(options.redirect)
})
return auth
}