@m92/express-utils
Version:
Utility Module for Express Framework
198 lines (156 loc) • 6.01 kB
JavaScript
'use strict'
import autoBind from 'auto-bind'
import AUTHENTICATOR_ERRORS from '../constants/AUTHENTICATOR_ERRORS'
export default class Authenticator {
constructor (CONFIG, CONSTANTS, thisExpressUtils) {
this.CONFIG = CONFIG
this.CONSTANTS = CONSTANTS
const { DISABLE_OAUTH, DISABLE_AAUTH } = CONFIG
const { HttpClient, CustomError, reqHandler, ERROR_CLASSIFICATION, httpContext } = thisExpressUtils
const { asyncWrapper } = reqHandler
this.ERROR_CLASSIFICATION = ERROR_CLASSIFICATION
this.HttpClient = HttpClient
this.CustomError = CustomError
this.httpContext = httpContext
autoBind(this)
const validateToken = (
(DISABLE_OAUTH && this._bypassMiddleware) ||
this._validateToken
)
this.validateToken = asyncWrapper(validateToken)
const validateApiKey = (
(DISABLE_AAUTH && this._bypassMiddleware) ||
this._validateApiKey
)
this.validateApiKey = asyncWrapper(validateApiKey)
}
async _bypassMiddleware (request, response, next) {
return process.nextTick(next)
}
async _validateToken (request, response, next) {
// Check if 'byPassOAuth' Property has been Set by Another Middleware to Disable Auth
const { byPassOAuth } = request
if (byPassOAuth === true) { return process.nextTick(next) }
const apiParams = this._buildOAuthParams(request) || {}
await this._callOAuthApi(apiParams, request, response)
next()
}
async _validateApiKey (request, response, next) {
// Check if 'byPassAAuth' Property has been Set by Another Middleware to Disable Auth
const { byPassAAuth } = request
if (byPassAAuth === true) { return process.nextTick(next) }
const apiParams = this._buildAAuthParams(request) || {}
await this._callAAuthApi(apiParams, request, response)
next()
}
// --------- Token Authentication --------------------------------------------
_buildOAuthParams (request) {
const { CONFIG, CONSTANTS, CustomError } = this
const token = this._extractRequestToken(request)
if (!token) {
throw new CustomError(undefined, AUTHENTICATOR_ERRORS.TOKEN_NOT_FOUND)
}
const { OAUTH_POOL_ID = '', OAUTH_ENDPOINT = '' } = CONFIG
const { POOL_ID_REQUEST_KEY = '' } = CONSTANTS
const url = OAUTH_ENDPOINT
const poolId = OAUTH_POOL_ID || request[POOL_ID_REQUEST_KEY]
const body = { poolId, token }
const oauthParams = { url, body }
return oauthParams
}
async _callOAuthApi (params, routerRequest, routerResponse) {
const { url = '', headers = {}, body = {} } = params
const options = {
method: 'POST',
url,
headers,
data: body
}
const httpClient = new this.HttpClient()
const response = await httpClient.request(options)
return this._setRouterRequestResponseForOAuth(routerRequest, routerResponse, response)
}
_extractRequestToken (request) {
const { CONSTANTS } = this
const {
DEFAULT_AUTH,
AUTH_HEADER,
ACCESS_TOKEN_PROP
} = CONSTANTS
const token = (DEFAULT_AUTH && request[AUTH_HEADER.REQUEST_PROP]) || request[ACCESS_TOKEN_PROP]
return token
}
_setRouterRequestResponseForOAuth (routerRequest, routerResponse, authResponse) {
const { CONSTANTS, httpContext } = this
const {
DEFAULT_AUTH,
AUTH_HEADER,
ACCESS_TOKEN_PROP,
CLAIMS_REQUEST_KEY,
OAUTH_RESPONSE_PROP
} = CONSTANTS
const { data: oauthBody = {} } = authResponse
const { statusCode, data: oauthData = {} } = oauthBody
const { claims = {}, refreshedToken } = oauthData
// '!statusCode' => response data is not of structure 'ResponseBody'
if (!statusCode) {
routerResponse[OAUTH_RESPONSE_PROP] = oauthData
return
}
routerRequest[CLAIMS_REQUEST_KEY] = claims
const refreshedTokenResponseProp = (DEFAULT_AUTH && AUTH_HEADER.REQUEST_PROP) || ACCESS_TOKEN_PROP
routerResponse[refreshedTokenResponseProp] = refreshedToken
httpContext.setResponseToken(refreshedToken)
httpContext.setUser(claims)
}
// ---------------------------------------------------------------------------
// --------- Token Authentication --------------------------------------------
_buildAAuthParams (request) {
const { CONFIG, CONSTANTS, CustomError } = this
const apiKey = this._extractApiKey(request)
if (!apiKey) {
throw new CustomError(undefined, AUTHENTICATOR_ERRORS.API_KEY_NOT_FOUND)
}
const { AAUTH_ENDPOINT, AAUTH_API_KEY } = CONFIG
const { API_KEY_HEADER_KEY } = CONSTANTS
const url = AAUTH_ENDPOINT
const headers = {
[API_KEY_HEADER_KEY]: AAUTH_API_KEY
}
const body = { apiKey }
const aauthParams = { url, headers, body }
return aauthParams
}
async _callAAuthApi (params, routerRequest, routerResponse) {
const { url = '', headers = {}, body = {} } = params
const options = {
method: 'POST',
url,
headers,
data: body
}
const httpClient = new this.HttpClient()
const response = await httpClient.request(options)
return this._setRouterRequestResponseForAAuth(routerRequest, routerResponse, response)
}
_extractApiKey (request) {
const { CONSTANTS } = this
const { API_KEY_REQUEST_KEY } = CONSTANTS
const apiKey = request[API_KEY_REQUEST_KEY]
return apiKey
}
_setRouterRequestResponseForAAuth (routerRequest, routerResponse, authResponse) {
const { CONSTANTS, httpContext } = this
const { AAUTH_RESPONSE_PROP, API_KEY_REQUEST_KEY } = CONSTANTS
const { data: aauthBody = {} } = authResponse
const { statusCode, data: aauthData = {} } = aauthBody
// '!statusCode' => response data is not of structure 'ResponseBody'
if (!statusCode) {
routerResponse[AAUTH_RESPONSE_PROP] = aauthBody
return
}
routerRequest[API_KEY_REQUEST_KEY] = aauthData
httpContext.setApiUser(aauthData)
}
// ---------------------------------------------------------------------------
}