UNPKG

@bitblit/epsilon

Version:

Tiny adapter to simplify building API gateway Lambda APIS

237 lines 11 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EventUtil = void 0; const unauthorized_error_1 = require("./error/unauthorized-error"); const bad_request_error_1 = require("./error/bad-request-error"); const common_1 = require("@bitblit/ratchet/common"); const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); const epsilon_constants_1 = require("../epsilon-constants"); /** * Endpoints about the api itself */ class EventUtil { // Prevent instantiation // eslint-disable-next-line @typescript-eslint/no-empty-function constructor() { } static extractStage(event) { // This differs from extractApiGatewayStage in that the "real" stage can be // mapped differently than the gateway stage. This extracts the "real" stage // as just being the first part of the path. If they are the same, no harm no // foul if (!event.path.startsWith('/')) { throw new bad_request_error_1.BadRequestError('Path should start with / but does not : ' + event.path); } const idx = event.path.indexOf('/', 1); if (idx == -1) { throw new bad_request_error_1.BadRequestError('No second / found in the path : ' + event.path); } return event.path.substring(1, idx); } static extractHostHeader(event) { return common_1.MapRatchet.extractValueFromMapIgnoreCase(event.headers, 'Host'); } static extractProtocol(event) { // Since API gateway / ALB ALWAYS sets this return common_1.MapRatchet.extractValueFromMapIgnoreCase(event.headers, 'X-Forwarded-Proto'); } static extractApiGatewayStage(event) { const rc = EventUtil.extractRequestContext(event); return rc ? rc.stage : null; } static extractRequestContext(event) { return event.requestContext; } static extractAuthorizer(event) { const rc = EventUtil.extractRequestContext(event); return rc ? rc.authorizer : null; } static ipAddressChain(event) { const headerVal = event && event.headers ? common_1.MapRatchet.extractValueFromMapIgnoreCase(event.headers, 'X-Forwarded-For') : null; let headerList = headerVal ? String(headerVal).split(',') : []; headerList = headerList.map((s) => s.trim()); return headerList; } static ipAddress(event) { const list = EventUtil.ipAddressChain(event); return list && list.length > 0 ? list[0] : null; } static extractFullPath(event, overrideProtocol = null) { const protocol = overrideProtocol || EventUtil.extractProtocol(event) || 'https'; return protocol + '://' + event.requestContext['domainName'] + event.requestContext.path; } static extractFullPrefix(event, overrideProtocol = null) { const protocol = overrideProtocol || EventUtil.extractProtocol(event) || 'https'; const prefix = event.requestContext.path.substring(0, event.requestContext.path.indexOf('/', 1)); return protocol + '://' + event.requestContext['domainName'] + prefix; } static jsonBodyToObject(event) { let rval = null; if (event.body) { const contentType = common_1.MapRatchet.extractValueFromMapIgnoreCase(event.headers, 'Content-Type') || 'application/octet-stream'; rval = event.body; if (event.isBase64Encoded) { rval = common_1.Base64Ratchet.base64StringToString(rval); //Buffer.from(rval, 'base64'); } if (contentType.startsWith('application/json')) { // to handle cases where the charset is specified rval = JSON.parse(rval.toString('ascii')); } } return rval; } static calcLogLevelViaEventOrEnvParam(curLevel, event, rConfig) { let rval = curLevel; if ((rConfig === null || rConfig === void 0 ? void 0 : rConfig.envParamLogLevelName) && process.env[rConfig.envParamLogLevelName]) { rval = common_1.EnumRatchet.keyToEnum(common_1.LoggerLevelName, process.env[rConfig.envParamLogLevelName]); common_1.Logger.silly('Found env log level : %s', rval); } if (rConfig && rConfig.queryParamLogLevelName && event && event.queryStringParameters && event.queryStringParameters[rConfig.queryParamLogLevelName]) { rval = common_1.EnumRatchet.keyToEnum(common_1.LoggerLevelName, event.queryStringParameters[rConfig.queryParamLogLevelName]); common_1.Logger.silly('Found query log level : %s', rval); } return rval; } /** * This is a weird function - sometimes your customers will not decode their query params and it * results in query params that look like 'amp;SOMETHING' instead of 'SOMETHING'. This function * looks for params that look like that, and strips the amp; from them. If you have any * params you are expecting that have 'amp;' in front of them, DON'T use this function. * * Also, you are a moron for having a param that looks like that * * Yes, it would be better to fix this on the client side, but that is not always an option * in production * @param event */ static fixStillEncodedQueryParams(event) { if (event === null || event === void 0 ? void 0 : event.queryStringParameters) { const newParams = {}; Object.keys(event.queryStringParameters).forEach((k) => { const val = event.queryStringParameters[k]; if (k.toLowerCase().startsWith('amp;')) { newParams[k.substring(4)] = val; } else { newParams[k] = val; } }); event.queryStringParameters = newParams; } if (event === null || event === void 0 ? void 0 : event.multiValueQueryStringParameters) { const newParams = {}; Object.keys(event.multiValueQueryStringParameters).forEach((k) => { const val = event.multiValueQueryStringParameters[k]; if (k.toLowerCase().startsWith('amp;')) { newParams[k.substring(4)] = val; } else { newParams[k] = val; } }); event.multiValueQueryStringParameters = newParams; } } /** * Allows you to force in a token for an arbitrary event without having to pass the whole thing * through Epsilon. Useful for when you need to test a handler that needs authorization, * and don't need to instantiate all of epsilon, just the handler * @param event Event to decorate * @param jwtToken String containing a valid JWT token */ static applyTokenToEventForTesting(event, jwtToken) { const jwtFullData = jsonwebtoken_1.default.decode(jwtToken, { complete: true }); if (!jwtFullData['payload']) { throw new Error('No payload found in passed token'); } // CAW 2020-05-03 : Have to strip the payload layer to match behavior of WebTokenManipulator in live const jwtData = jwtFullData['payload']; // Make the header consistent with the authorizer event.headers = event.headers || {}; event.headers[epsilon_constants_1.EpsilonConstants.AUTH_HEADER_NAME.toLowerCase()] = 'Bearer ' + jwtToken; event.requestContext = event.requestContext || {}; const newAuth = Object.assign({}, event.requestContext.authorizer); newAuth.userData = jwtData; newAuth.userDataJSON = jwtData ? JSON.stringify(jwtData) : null; newAuth.srcData = jwtToken; event.requestContext.authorizer = newAuth; } static extractBasicAuthenticationToken(event, throwErrorOnMissingBad = false) { let rval = null; if (!!event && !!event.headers) { const headerVal = EventUtil.extractAuthorizationHeaderCaseInsensitive(event); if (!!headerVal && headerVal.startsWith('Basic ')) { const parsed = common_1.Base64Ratchet.base64StringToString(headerVal.substring(6)); const sp = parsed.split(':'); common_1.Logger.silly('Parsed to %j', sp); if (!!sp && sp.length === 2) { rval = { username: sp[0], password: sp[1], }; } } } if (!rval && throwErrorOnMissingBad) { throw new unauthorized_error_1.UnauthorizedError('Could not find valid basic authentication header'); } return rval; } static eventIsAGraphQLIntrospection(event) { let rval = false; if (!!event) { if (!!event.httpMethod && 'post' === event.httpMethod.toLowerCase()) { if (!!event.path && event.path.endsWith('/graphql')) { const body = EventUtil.jsonBodyToObject(event); rval = !!body && !!body['operationName'] && body['operationName'] === 'IntrospectionQuery'; } } } return rval; } static extractAuthorizationHeaderCaseInsensitive(evt) { return common_1.MapRatchet.caseInsensitiveAccess((evt === null || evt === void 0 ? void 0 : evt.headers) || {}, epsilon_constants_1.EpsilonConstants.AUTH_HEADER_NAME); } static extractBearerTokenFromEvent(evt) { let rval = null; const authHeader = common_1.StringRatchet.trimToEmpty(EventUtil.extractAuthorizationHeaderCaseInsensitive(evt)); if (authHeader.toLowerCase().startsWith('bearer ')) { rval = authHeader.substring(7); } return rval; } static hostIsLocal(host) { let rval = false; if (common_1.StringRatchet.trimToNull(host)) { host = host.includes(':') ? host.substring(0, host.indexOf(':')) : host; host = host.toLowerCase(); if (host === 'localhost' || host === '127.0.0.1') { rval = true; } } return rval; } static hostIsLocalOrNotRoutableIP4(host) { let rval = false; if (common_1.StringRatchet.trimToNull(host)) { host = host.includes(':') ? host.substring(0, host.indexOf(':')) : host; host = host.toLowerCase(); if (host === 'localhost' || host === '127.0.0.1' || host.startsWith('192.168.') || host.startsWith('10.') || host.startsWith('172.16.')) { rval = true; } } return rval; } } exports.EventUtil = EventUtil; //# sourceMappingURL=event-util.js.map