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