UNPKG

yandex-cloud-functions-router

Version:
185 lines (184 loc) 10.1 kB
"use strict"; 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.httpRouter = void 0; const routerError_1 = require("../models/routerError"); const log_1 = require("../helpers/log"); const appendCorsHeadersToMainResponse_1 = require("./http/cors/appendCorsHeadersToMainResponse"); const handleCorsPreflight_1 = require("./http/cors/handleCorsPreflight"); const matchObjectPattern_1 = require("../helpers/matchObjectPattern"); const resolveCorsOptions_1 = require("./http/cors/resolveCorsOptions"); const validateHttpMethod = (httpMethod, event) => { const result = httpMethod ? httpMethod.map((m) => m.trim().toLowerCase()).indexOf(event.httpMethod.trim().toLocaleLowerCase()) !== -1 : true; log_1.debug(event.requestContext.requestId, `Validating HTTP method: ${result ? 'valid' : 'invalid'}`, { allowed: httpMethod !== null && httpMethod !== void 0 ? httpMethod : '', actual: event.httpMethod }); return result; }; const validatePath = (path, event) => { const result = path ? path.map((p) => p.trim().toLowerCase()).indexOf(event.path.trim().toLocaleLowerCase()) !== -1 : true; log_1.debug(event.requestContext.requestId, `Validating path: ${result ? 'valid' : 'invalid'}`, { allowed: path !== null && path !== void 0 ? path : '', actual: event.path }); return result; }; const validateParams = (params, event) => { if (params) { const eventParams = Object.entries(event.queryStringParameters).reduce((result, [key, value]) => (Object.assign(Object.assign({}, result), { [key.trim().toLowerCase()]: value })), {}); const handlerParams = Object.entries(params).reduce((result, [key, value]) => (Object.assign(Object.assign({}, result), { [key.trim().toLowerCase()]: value })), {}); log_1.debug(event.requestContext.requestId, 'Validating HTTP params', { request: eventParams, route: handlerParams }); const validationErrorFound = Object.entries(handlerParams).some(([name, { type, value, pattern }]) => { var _a, _b; const eventParamValue = eventParams[name]; if (type === 'exact') { log_1.debug(event.requestContext.requestId, `Validating ${name} param (type - exact): ${!(value === eventParamValue) ? 'invalid' : 'valid'}`, { request: eventParamValue, route: value !== null && value !== void 0 ? value : '' }); return !(value === eventParamValue); } else if (type === 'substring') { if (value) { log_1.debug(event.requestContext.requestId, `Validating ${name} param (type - substring): ${!(eventParamValue.indexOf(value) !== -1) ? 'invalid' : 'valid'}`, { request: eventParamValue, route: value !== null && value !== void 0 ? value : '' }); return !(eventParamValue.indexOf(value) !== -1); } else { // throw validation error on empty value log_1.debug(event.requestContext.requestId, `Validating ${name} param (type - substring): invalid`, { request: eventParamValue, route: '(empty value)' }); return true; } } else if (type === 'regexp') { log_1.debug(event.requestContext.requestId, `Validating ${name} param (type - regexp): ${!((_a = pattern === null || pattern === void 0 ? void 0 : pattern.test(eventParamValue)) !== null && _a !== void 0 ? _a : false) ? 'invalid' : 'valid'}`, { request: eventParamValue, route: value !== null && value !== void 0 ? value : '' }); return !((_b = pattern === null || pattern === void 0 ? void 0 : pattern.test(eventParamValue)) !== null && _b !== void 0 ? _b : false); } else { log_1.debug(event.requestContext.requestId, `Validating ${name} param: unknown validation type - ${type}`, { request: eventParamValue, route: value !== null && value !== void 0 ? value : '' }); throw new routerError_1.HttpParamNotSupportedTypeRouteError(`Not supported type: ${type}`); } }); return !validationErrorFound; } else { return true; } }; const validateBodyPattern = (pattern, event) => { var _a; if (pattern) { log_1.debug(event.requestContext.requestId, 'Validating HTTP body', { pattern }); if (pattern.json) { try { const contentTypeMatched = String(((_a = Object.entries(event.headers).find(([name]) => name.trim().toLowerCase() === 'content-type')) !== null && _a !== void 0 ? _a : [])[1] || '') .toLowerCase() .indexOf('application/json') !== -1; log_1.debug(event.requestContext.requestId, 'Validating HTTP body type (JSON)', { contentTypeMatched }); if (contentTypeMatched) { const bodyObject = JSON.parse(event.body); const result = matchObjectPattern_1.matchObjectPattern(bodyObject, pattern.json); log_1.debug(event.requestContext.requestId, 'Validating HTTP body', { bodyObject, result }); return result; } else { return false; } } catch (e) { log_1.debug(event.requestContext.requestId, 'Validating HTTP body failed with error', { error: e }); if (e instanceof SyntaxError) { return false; } else { throw e; } } } else { return false; } } else { return true; } }; const validateWithValidators = (validators, event, context) => { var _a; try { return validators ? validators.every((validator) => validator(event, context)) : true; } catch (e) { log_1.log('WARN', context.requestId, `Validator failed with error: ${((_a = e === null || e === void 0 ? void 0 : e.toString()) !== null && _a !== void 0 ? _a : 'unknown error').replace(/[\r\n]+/g, '')}`, {}); return false; } }; const unwrapBase64Body = (event) => { var _a; if (event.isBase64Encoded) { log_1.debug(event.requestContext.requestId, 'HTTP request - unwrapping base64 body', {}); const body = Buffer.from((_a = event.body) !== null && _a !== void 0 ? _a : '', 'base64').toString('utf-8'); return Object.assign(Object.assign({}, event), { body, isBase64Encoded: false }); } else { return event; } }; const httpRouter = (routes, event, context, options) => __awaiter(void 0, void 0, void 0, function* () { const corsOptions = resolveCorsOptions_1.resolveCorsOptions(options === null || options === void 0 ? void 0 : options.cors); log_1.debug(context.requestId, 'HTTP request processing started', { CORS: corsOptions }); for (const { httpMethod, path, params, body, validators, decodeBase64Body, handler } of routes) { const matched = validateHttpMethod(httpMethod, event) && validateParams(params, event) && validateBodyPattern(body, event) && validatePath(path, event); log_1.debug(context.requestId, 'HTTP request matching completed', { matched, event, httpMethod: httpMethod !== null && httpMethod !== void 0 ? httpMethod : '', path: path !== null && path !== void 0 ? path : '', params: params !== null && params !== void 0 ? params : '', body: body !== null && body !== void 0 ? body : '' }); if (matched) { const unwrappedEvent = decodeBase64Body ? unwrapBase64Body(event) : event; const validatorsPassed = validateWithValidators(validators, unwrappedEvent, context); log_1.debug(context.requestId, 'HTTP request validating completed', { validatorsPassed }); if (validatorsPassed) { const handlerResult = handler(unwrappedEvent, context); const result = handlerResult instanceof Promise ? yield handlerResult : handlerResult; log_1.debug(context.requestId, 'HTTP processed', {}); return appendCorsHeadersToMainResponse_1.appendCorsHeadersToMainResponse(event, result, corsOptions); } else { log_1.log('WARN', context.requestId, 'Invalid request', {}); throw new routerError_1.InvalidRequestError('Invalid request.'); } } } const preflightResponse = handleCorsPreflight_1.handleCorsPreflight(event, corsOptions); if (preflightResponse) { log_1.debug(context.requestId, 'HTTP CORS preflight request processed', { preflightResponse }); log_1.log('INFO', context.requestId, 'CORS preflight request handled ', {}); return preflightResponse; } else { log_1.debug(context.requestId, 'HTTP CORS preflight request skipped', {}); } log_1.log('WARN', context.requestId, 'There is no matched route', {}); throw new routerError_1.NoMatchedRouteError('There is no matched route.'); }); exports.httpRouter = httpRouter;