UNPKG

@bitblit/epsilon

Version:

Tiny adapter to simplify building API gateway Lambda APIS

225 lines 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var unauthorized_error_1 = require("./error/unauthorized-error"); var logger_1 = require("@bitblit/ratchet/dist/common/logger"); var bad_request_error_1 = require("./error/bad-request-error"); var map_ratchet_1 = require("@bitblit/ratchet/dist/common/map-ratchet"); var jwt = require("jsonwebtoken"); var base64_ratchet_1 = require("@bitblit/ratchet/dist/common/base64-ratchet"); var string_ratchet_1 = require("@bitblit/ratchet/dist/common/string-ratchet"); /** * Endpoints about the api itself */ var EventUtil = /** @class */ (function () { function EventUtil() { } // Prevent instantiation EventUtil.extractToken = function (event) { var auth = EventUtil.extractAuthorizer(event); if (!auth) { logger_1.Logger.debug('Could not extract authorizer from event : %j', event); throw new unauthorized_error_1.UnauthorizedError('Missing authorization context'); } else { if (!!auth['userData']) { return auth['userData']; } else if (auth['userDataJSON']) { return JSON.parse(string_ratchet_1.StringRatchet.safeString(auth['userDataJSON'])); } else { throw new unauthorized_error_1.UnauthorizedError('Missing authorization context data'); } } }; EventUtil.extractTokenSrc = function (event) { var auth = EventUtil.extractAuthorizer(event); return auth ? string_ratchet_1.StringRatchet.safeString(auth['srcData']) : null; }; EventUtil.extractStage = function (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); } var 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); }; EventUtil.extractHostHeader = function (event) { return map_ratchet_1.MapRatchet.extractValueFromMapIgnoreCase(event.headers, 'Host'); }; EventUtil.extractProtocol = function (event) { // Since API gateway ALWAYS sets this return map_ratchet_1.MapRatchet.extractValueFromMapIgnoreCase(event.headers, 'X-Forwarded-Proto'); }; EventUtil.extractApiGatewayStage = function (event) { var rc = EventUtil.extractRequestContext(event); return rc ? rc.stage : null; }; EventUtil.extractRequestContext = function (event) { return event.requestContext; }; EventUtil.extractAuthorizer = function (event) { var rc = EventUtil.extractRequestContext(event); return rc ? rc.authorizer : null; }; EventUtil.ipAddressChain = function (event) { var headerVal = event && event.headers ? map_ratchet_1.MapRatchet.extractValueFromMapIgnoreCase(event.headers, 'X-Forwarded-For') : null; var headerList = headerVal ? String(headerVal).split(',') : []; headerList = headerList.map(function (s) { return s.trim(); }); return headerList; }; EventUtil.ipAddress = function (event) { var list = EventUtil.ipAddressChain(event); return list && list.length > 0 ? list[0] : null; }; EventUtil.extractFullPath = function (event, overrideProtocol) { if (overrideProtocol === void 0) { overrideProtocol = null; } var protocol = overrideProtocol || EventUtil.extractProtocol(event) || 'https'; return protocol + '://' + event.requestContext['domainName'] + event.requestContext.path; }; EventUtil.extractFullPrefix = function (event, overrideProtocol) { if (overrideProtocol === void 0) { overrideProtocol = null; } var protocol = overrideProtocol || EventUtil.extractProtocol(event) || 'https'; var prefix = event.requestContext.path.substring(0, event.requestContext.path.indexOf('/', 1)); return protocol + '://' + event.requestContext['domainName'] + prefix; }; EventUtil.bodyObject = function (event) { var rval = null; if (event.body) { var contentType = map_ratchet_1.MapRatchet.extractValueFromMapIgnoreCase(event.headers, 'Content-Type') || 'application/octet-stream'; rval = event.body; if (event.isBase64Encoded) { 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; }; EventUtil.calcLogLevelViaEventOrEnvParam = function (curLevel, event, rConfig) { var rval = curLevel; if (rConfig && rConfig.envParamLogLevelName && process.env[rConfig.envParamLogLevelName]) { rval = process.env[rConfig.envParamLogLevelName]; logger_1.Logger.silly('Found env log level : %s', rval); } if (rConfig && rConfig.queryParamLogLevelName && event && event.queryStringParameters && event.queryStringParameters[rConfig.queryParamLogLevelName]) { rval = event.queryStringParameters[rConfig.queryParamLogLevelName]; logger_1.Logger.silly('Found query log level : %s', rval); } return rval; }; /** * This is a weird function - sometimes your customers will not unencode 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 */ EventUtil.fixStillEncodedQueryParams = function (event) { if (!!event) { if (!!event.queryStringParameters) { var newParams_1 = {}; Object.keys(event.queryStringParameters).forEach(function (k) { var val = event.queryStringParameters[k]; if (k.toLowerCase().startsWith('amp;')) { newParams_1[k.substring(4)] = val; } else { newParams_1[k] = val; } }); event.queryStringParameters = newParams_1; } if (!!event.multiValueQueryStringParameters) { var newParams_2 = {}; Object.keys(event.multiValueQueryStringParameters).forEach(function (k) { var val = event.multiValueQueryStringParameters[k]; if (k.toLowerCase().startsWith('amp;')) { newParams_2[k.substring(4)] = val; } else { newParams_2[k] = val; } }); event.multiValueQueryStringParameters = newParams_2; } } }; /** * 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 */ EventUtil.applyTokenToEventForTesting = function (event, jwtToken) { var jwtFullData = jwt.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 var jwtData = jwtFullData['payload']; // Make the header consistent with the authorizer event.headers = event.headers || {}; event.headers['authorization'] = 'Bearer ' + jwtToken; event.requestContext = event.requestContext || {}; var newAuth = Object.assign({}, event.requestContext.authorizer); newAuth.userData = jwtData; newAuth.userDataJSON = jwtData ? JSON.stringify(jwtData) : null; newAuth.srcData = jwtToken; event.requestContext.authorizer = newAuth; }; EventUtil.extractBasicAuthenticationToken = function (event, throwErrorOnMissingBad) { if (throwErrorOnMissingBad === void 0) { throwErrorOnMissingBad = false; } var rval = null; if (!!event && !!event.headers) { var headerVal = map_ratchet_1.MapRatchet.caseInsensitiveAccess(event.headers, 'authorization'); if (!!headerVal && headerVal.startsWith('Basic ')) { var parsed = base64_ratchet_1.Base64Ratchet.base64StringToString(headerVal.substring(6)); var sp = parsed.split(':'); logger_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; }; EventUtil.eventIsAGraphQLIntrospection = function (event) { var rval = false; if (!!event) { if (!!event.httpMethod && 'post' === event.httpMethod.toLowerCase()) { if (!!event.path && event.path.endsWith('/graphql')) { var body = EventUtil.bodyObject(event); rval = !!body && !!body['operationName'] && body['operationName'] === 'IntrospectionQuery'; } } } return rval; }; return EventUtil; }()); exports.EventUtil = EventUtil; //# sourceMappingURL=event-util.js.map