@bitblit/epsilon
Version:
Tiny adapter to simplify building API gateway Lambda APIS
237 lines • 11 kB
JavaScript
;
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