@bitblit/epsilon
Version:
Tiny adapter to simplify building API gateway Lambda APIS
282 lines • 14.6 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BuiltInFilters = void 0;
const logger_1 = require("@bitblit/ratchet/common/logger");
const string_ratchet_1 = require("@bitblit/ratchet/common/string-ratchet");
const map_ratchet_1 = require("@bitblit/ratchet/common/map-ratchet");
const event_util_1 = require("../../http/event-util");
const bad_request_error_1 = require("../../http/error/bad-request-error");
const response_util_1 = require("../../http/response-util");
const misconfigured_error_1 = require("../../http/error/misconfigured-error");
const common_1 = require("@bitblit/ratchet/common");
class BuiltInFilters {
static combineFilters(fCtx, filters) {
return __awaiter(this, void 0, void 0, function* () {
let cont = true;
if (filters && filters.length > 0) {
for (let i = 0; i < filters.length && cont; i++) {
cont = yield filters[i](fCtx);
}
}
return cont;
});
}
static applyGzipIfPossible(fCtx) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (((_a = fCtx.event) === null || _a === void 0 ? void 0 : _a.headers) && fCtx.result) {
const encodingHeader = fCtx.event && fCtx.event.headers ? map_ratchet_1.MapRatchet.extractValueFromMapIgnoreCase(fCtx.event.headers, 'accept-encoding') : null;
fCtx.result = yield response_util_1.ResponseUtil.applyGzipIfPossible(encodingHeader, fCtx.result);
}
return true;
});
}
static addConstantHeaders(fCtx, headers) {
return __awaiter(this, void 0, void 0, function* () {
if (headers && fCtx.result) {
fCtx.result.headers = Object.assign({}, headers, fCtx.result.headers);
}
else {
logger_1.Logger.warn('Could not add headers - either result or headers were missing');
}
return true;
});
}
static addAWSRequestIdHeader(fCtx, headerName = 'X-REQUEST-ID') {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (fCtx.result && string_ratchet_1.StringRatchet.trimToNull(headerName) && headerName.startsWith('X-')) {
fCtx.result.headers = fCtx.result.headers || {};
fCtx.result.headers[headerName] = ((_a = fCtx.context) === null || _a === void 0 ? void 0 : _a.awsRequestId) || 'Request-Id-Missing';
}
else {
logger_1.Logger.warn('Could not add request id header - either result or context were missing or name was invalid');
}
return true;
});
}
static addAllowEverythingCORSHeaders(fCtx) {
return __awaiter(this, void 0, void 0, function* () {
return BuiltInFilters.addConstantHeaders(fCtx, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Headers': '*',
});
});
}
static addAllowReflectionCORSHeaders(fCtx) {
return __awaiter(this, void 0, void 0, function* () {
return BuiltInFilters.addConstantHeaders(fCtx, {
'Access-Control-Allow-Origin': map_ratchet_1.MapRatchet.caseInsensitiveAccess(fCtx.event.headers, 'Origin') || '*',
'Access-Control-Allow-Methods': map_ratchet_1.MapRatchet.caseInsensitiveAccess(fCtx.event.headers, 'Access-Control-Request-Method') || '*',
'Access-Control-Allow-Headers': map_ratchet_1.MapRatchet.caseInsensitiveAccess(fCtx.event.headers, 'Access-Control-Request-Headers') || '*',
});
});
}
static uriDecodeQueryParams(fCtx) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
if ((_a = fCtx === null || fCtx === void 0 ? void 0 : fCtx.event) === null || _a === void 0 ? void 0 : _a.queryStringParameters) {
Object.keys(fCtx.event.queryStringParameters).forEach((k) => {
const val = fCtx.event.queryStringParameters[k];
if (val) {
fCtx.event.queryStringParameters[k] = BuiltInFilters.decodeUriComponentAndReplacePlus(val);
}
});
}
if ((_b = fCtx === null || fCtx === void 0 ? void 0 : fCtx.event) === null || _b === void 0 ? void 0 : _b.multiValueQueryStringParameters) {
Object.keys(fCtx.event.multiValueQueryStringParameters).forEach((k) => {
const val = fCtx.event.multiValueQueryStringParameters[k];
if (val && val.length) {
const cleaned = val.map((v) => BuiltInFilters.decodeUriComponentAndReplacePlus(v));
fCtx.event.multiValueQueryStringParameters[k] = cleaned;
}
});
}
return true;
});
}
/**
* Performs decodeURIComponent on a value after replacing all "+" values with spaces.
*/
static decodeUriComponentAndReplacePlus(val) {
return decodeURIComponent(val.replace(/\+/g, ' '));
}
static fixStillEncodedQueryParams(fCtx) {
return __awaiter(this, void 0, void 0, function* () {
event_util_1.EventUtil.fixStillEncodedQueryParams(fCtx.event);
return true;
});
}
static disallowStringNullAsPathParameter(fCtx) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if ((_a = fCtx === null || fCtx === void 0 ? void 0 : fCtx.event) === null || _a === void 0 ? void 0 : _a.pathParameters) {
Object.keys(fCtx.event.pathParameters).forEach((k) => {
if ('null' === string_ratchet_1.StringRatchet.trimToEmpty(fCtx.event.pathParameters[k]).toLowerCase()) {
throw new bad_request_error_1.BadRequestError().withFormattedErrorMessage('Path parameter %s was string -null-', k);
}
});
}
return true;
});
}
static disallowStringNullAsQueryStringParameter(fCtx) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if ((_a = fCtx === null || fCtx === void 0 ? void 0 : fCtx.event) === null || _a === void 0 ? void 0 : _a.queryStringParameters) {
Object.keys(fCtx.event.queryStringParameters).forEach((k) => {
if ('null' === string_ratchet_1.StringRatchet.trimToEmpty(fCtx.event.queryStringParameters[k]).toLowerCase()) {
throw new bad_request_error_1.BadRequestError().withFormattedErrorMessage('Query parameter %s was string -null-', k);
}
});
}
return true;
});
}
static ensureEventMaps(fCtx) {
return __awaiter(this, void 0, void 0, function* () {
fCtx.event.queryStringParameters = fCtx.event.queryStringParameters || {};
fCtx.event.headers = fCtx.event.headers || {};
fCtx.event.pathParameters = fCtx.event.pathParameters || {};
return true;
});
}
static parseJsonBodyToObject(fCtx) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if ((_a = fCtx.event) === null || _a === void 0 ? void 0 : _a.body) {
try {
fCtx.event.parsedBody = event_util_1.EventUtil.jsonBodyToObject(fCtx.event);
}
catch (err) {
throw new common_1.RestfulApiHttpError('Supplied body was not parsable as valid JSON').withHttpStatusCode(400);
}
}
return true;
});
}
static checkMaximumLambdaBodySize(fCtx) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (((_a = fCtx.result) === null || _a === void 0 ? void 0 : _a.body) && fCtx.result.body.length > BuiltInFilters.MAXIMUM_LAMBDA_BODY_SIZE_BYTES) {
const delta = fCtx.result.body.length - BuiltInFilters.MAXIMUM_LAMBDA_BODY_SIZE_BYTES;
throw new common_1.RestfulApiHttpError('Response size is ' + fCtx.result.body.length + ' bytes, which is ' + delta + ' bytes too large for this handler').withHttpStatusCode(500);
}
return true;
});
}
static validateInboundBody(fCtx) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (((_a = fCtx === null || fCtx === void 0 ? void 0 : fCtx.event) === null || _a === void 0 ? void 0 : _a.parsedBody) && fCtx.routeAndParse) {
if (fCtx.routeAndParse.mapping.validation) {
if (!fCtx.modelValidator) {
throw new misconfigured_error_1.MisconfiguredError('Requested body validation but supplied no validator');
}
const errors = fCtx.modelValidator.validate(fCtx.routeAndParse.mapping.validation.modelName, fCtx.event.parsedBody, fCtx.routeAndParse.mapping.validation.emptyAllowed, fCtx.routeAndParse.mapping.validation.extraPropertiesAllowed);
if (errors.length > 0) {
logger_1.Logger.info('Found errors while validating %s object %j', fCtx.routeAndParse.mapping.validation.modelName, errors);
const newError = new bad_request_error_1.BadRequestError(...errors);
throw newError;
}
}
}
else {
logger_1.Logger.debug('No validation since no route specified or no parsed body');
}
return true;
});
}
static validateInboundQueryParams(fCtx) {
return __awaiter(this, void 0, void 0, function* () {
// TODO: Implement ME!
return true;
});
}
static validateInboundPathParams(fCtx) {
return __awaiter(this, void 0, void 0, function* () {
// TODO: Implement ME!
return true;
});
}
static validateOutboundResponse(fCtx) {
return __awaiter(this, void 0, void 0, function* () {
// Use !== true below because commonly it just wont be spec'd
if (fCtx === null || fCtx === void 0 ? void 0 : fCtx.rawResult) {
if (fCtx.routeAndParse.mapping.outboundValidation) {
logger_1.Logger.debug('Applying outbound check to %j', fCtx.rawResult);
const errors = fCtx.modelValidator.validate(fCtx.routeAndParse.mapping.outboundValidation.modelName, fCtx.rawResult, fCtx.routeAndParse.mapping.outboundValidation.emptyAllowed, fCtx.routeAndParse.mapping.outboundValidation.extraPropertiesAllowed);
if (errors.length > 0) {
logger_1.Logger.error('Found outbound errors while validating %s object %j', fCtx.routeAndParse.mapping.outboundValidation.modelName, errors);
errors.unshift('Server sent object invalid according to spec');
throw new common_1.RestfulApiHttpError().withErrors(errors).withHttpStatusCode(500).withDetails(fCtx.rawResult);
}
}
else {
logger_1.Logger.debug('Applied no outbound validation because none set');
}
}
else {
logger_1.Logger.debug('No validation since no outbound body or disabled');
}
return true;
});
}
static autoRespondToOptionsRequestWithCors(fCtx) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (string_ratchet_1.StringRatchet.trimToEmpty((_a = fCtx === null || fCtx === void 0 ? void 0 : fCtx.event) === null || _a === void 0 ? void 0 : _a.httpMethod).toLowerCase() === 'options') {
fCtx.result = {
statusCode: 200,
body: '{"cors":true, "m":2}',
};
yield BuiltInFilters.addAllowReflectionCORSHeaders(fCtx);
return false;
}
else {
return true;
}
});
}
static autoRespond(fCtx, inBody) {
return __awaiter(this, void 0, void 0, function* () {
const body = inBody || {
message: 'Not Implemented',
};
fCtx.result = {
statusCode: 200,
body: JSON.stringify(body),
};
return false;
});
}
static secureOutboundServerErrorForProduction(fCtx, errorMessage, errCode) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if ((_a = fCtx === null || fCtx === void 0 ? void 0 : fCtx.result) === null || _a === void 0 ? void 0 : _a.statusCode) {
if (errCode === null || fCtx.result.statusCode === errCode) {
logger_1.Logger.warn('Securing outbound error info (was : %j)', fCtx.result.body);
fCtx.rawResult = new common_1.RestfulApiHttpError(errorMessage).withHttpStatusCode(fCtx.result.statusCode);
const oldResult = fCtx.result;
fCtx.result = response_util_1.ResponseUtil.errorResponse(fCtx.rawResult);
// Need this to preserve any CORS headers, etc
fCtx.result.headers = Object.assign({}, oldResult.headers || {}, fCtx.result.headers || {});
}
}
return true;
});
}
}
exports.BuiltInFilters = BuiltInFilters;
BuiltInFilters.MAXIMUM_LAMBDA_BODY_SIZE_BYTES = 1024 * 1024 * 5 - 1024 * 100; // 5Mb - 100k buffer
//# sourceMappingURL=built-in-filters.js.map