@studio/gateway
Advanced tools
+22
| Copyright (c) 2024 Maximilian Antoni | ||
| Permission is hereby granted, free of charge, to any person | ||
| obtaining a copy of this software and associated documentation | ||
| files (the "Software"), to deal in the Software without | ||
| restriction, including without limitation the rights to use, | ||
| copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the | ||
| Software is furnished to do so, subject to the following | ||
| conditions: | ||
| The above copyright notice and this permission notice shall be | ||
| included in all copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||
| OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||
| HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
| WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||
| OTHER DEALINGS IN THE SOFTWARE. |
+29
-0
| # Changes | ||
| ## 3.0.0 | ||
| - [`365bf27`](https://github.com/javascript-studio/studio-gateway/commit/365bf2715f1b8e477f8d19d23ddea34eced54941) | ||
| Drop node 12 and 14, support node 18 and 20 | ||
| - [`7154beb`](https://github.com/javascript-studio/studio-gateway/commit/7154beb585b09bf3de305c576610fe713e2d6e8c) | ||
| Use husky and lint-staged | ||
| - [`9796ba6`](https://github.com/javascript-studio/studio-gateway/commit/9796ba62d04d2943b1296b2296ac74fd03b1f9b6) | ||
| Use prettier | ||
| - [`f181da6`](https://github.com/javascript-studio/studio-gateway/commit/f181da600e04d18d830d094fbaed21f0e7b7d211) | ||
| npm audit | ||
| - [`4254f0a`](https://github.com/javascript-studio/studio-gateway/commit/4254f0a4744f63f70f58a28b0370041eaf828d0b) | ||
| Update minimist | ||
| - [`3fbdced`](https://github.com/javascript-studio/studio-gateway/commit/3fbdcedcda1da8c5bf1f27d8e68849b72ce0fc01) | ||
| Update Studio Fail | ||
| - [`4cc686f`](https://github.com/javascript-studio/studio-gateway/commit/4cc686fc005e8865d12b5af95bbed856067de53d) | ||
| Upgrade Studio ESLint Config and update eslint | ||
| - [`0a4bad2`](https://github.com/javascript-studio/studio-gateway/commit/0a4bad2a2f0b3d36e3b1571b3c76a9e632954565) | ||
| Upgrade Studio Changes | ||
| - [`3a10115`](https://github.com/javascript-studio/studio-gateway/commit/3a10115d4c2db6793127d6c2e201f3541188fe0a) | ||
| Upgrade referee-sinon | ||
| - [`a61a5df`](https://github.com/javascript-studio/studio-gateway/commit/a61a5df5933954500626bf3ed685f811fa0b39b1) | ||
| Rename master to main | ||
| - [`9d48128`](https://github.com/javascript-studio/studio-gateway/commit/9d48128eeb6e58e7c82467f2dbd1b3e0d92ac76f) | ||
| State MIT license in package.json | ||
| - [`e976dba`](https://github.com/javascript-studio/studio-gateway/commit/e976dba0fccb7376080189ca3c445304115c513c) | ||
| Add LICENSE | ||
| _Released by [Maximilian Antoni](https://github.com/mantoni) on 2024-01-17._ | ||
| ## 2.5.4 | ||
@@ -4,0 +33,0 @@ |
+28
-15
@@ -31,7 +31,8 @@ /* | ||
| exports.create = function (options = {}) { | ||
| const swagger = inlineSwaggerRefs(loadSwagger({ | ||
| file: options.swagger_file, | ||
| env: options.swagger_env | ||
| })); | ||
| const swagger = inlineSwaggerRefs( | ||
| loadSwagger({ | ||
| file: options.swagger_file, | ||
| env: options.swagger_env | ||
| }) | ||
| ); | ||
| const router = createRouter(swagger.paths, swagger.basePath || '/'); | ||
@@ -48,5 +49,9 @@ const emitter = new EventEmitter(); | ||
| try { | ||
| fn = integrationProcessor(emitter, resource, config, | ||
| fn = integrationProcessor( | ||
| emitter, | ||
| resource, | ||
| config, | ||
| options.stage || 'local', | ||
| options.stageVariables || {}); | ||
| options.stageVariables || {} | ||
| ); | ||
| } catch (e) { | ||
@@ -57,4 +62,9 @@ e.message = `[${method.toUpperCase()} ${resource}] ${e.message}`; | ||
| if (config.security && config.security.length) { | ||
| fn = secutiryProcessor(emitter, swagger, config.security, | ||
| secutiry_cache, fn); | ||
| fn = secutiryProcessor( | ||
| emitter, | ||
| swagger, | ||
| config.security, | ||
| secutiry_cache, | ||
| fn | ||
| ); | ||
| } | ||
@@ -66,3 +76,3 @@ integrations[resource][method] = fn; | ||
| const server = http.createServer((req, res) => { | ||
| // eslint-disable-next-line node/no-deprecated-api | ||
| // eslint-disable-next-line n/no-deprecated-api | ||
| const parsed_url = url.parse(req.url, true); | ||
@@ -92,7 +102,10 @@ router(parsed_url.pathname, (name, params) => { | ||
| } catch (e) { | ||
| log.error({ | ||
| method: req.method, | ||
| url: req.url, | ||
| headers | ||
| }, e); | ||
| log.error( | ||
| { | ||
| method: req.method, | ||
| url: req.url, | ||
| headers | ||
| }, | ||
| e | ||
| ); | ||
| fail(res, e.message); | ||
@@ -99,0 +112,0 @@ } |
+32
-11
@@ -75,4 +75,5 @@ /* | ||
| const type = parameterType(prop.type); | ||
| mapper.push(parameterMapper('payload', type, key, | ||
| required.indexOf(key) !== -1)); | ||
| mapper.push( | ||
| parameterMapper('payload', type, key, required.indexOf(key) !== -1) | ||
| ); | ||
| } | ||
@@ -120,5 +121,8 @@ return mapper; | ||
| return (params, query, headers, raw_body) => { | ||
| const payload = headers['Content-Type'] === 'application/json' | ||
| ? (raw_body ? parseJSON(raw_body) : {}) | ||
| : querystring.parse(raw_body); | ||
| const payload = | ||
| headers['Content-Type'] === 'application/json' | ||
| ? raw_body | ||
| ? parseJSON(raw_body) | ||
| : {} | ||
| : querystring.parse(raw_body); | ||
| const request = { | ||
@@ -138,4 +142,9 @@ params, | ||
| exports.requestProcessor = function (resource, method, integration, stage, | ||
| stageVariables) { | ||
| exports.requestProcessor = function ( | ||
| resource, | ||
| method, | ||
| integration, | ||
| stage, | ||
| stageVariables | ||
| ) { | ||
| const parameters = method.parameters; | ||
@@ -158,4 +167,10 @@ const parametersProc = parameters | ||
| const payload = event.payload; | ||
| const rendered = renderTemplate(request_template, context, event, payload, | ||
| raw_body, stageVariables); | ||
| const rendered = renderTemplate( | ||
| request_template, | ||
| context, | ||
| event, | ||
| payload, | ||
| raw_body, | ||
| stageVariables | ||
| ); | ||
| return parseJSON(rendered); | ||
@@ -186,4 +201,10 @@ }; | ||
| const context = { stage }; | ||
| data = renderTemplate(response_template, context, {}, data, | ||
| JSON.stringify(data), properties.stageVariables); | ||
| data = renderTemplate( | ||
| response_template, | ||
| context, | ||
| {}, | ||
| data, | ||
| JSON.stringify(data), | ||
| properties.stageVariables | ||
| ); | ||
| } | ||
@@ -190,0 +211,0 @@ } else { |
@@ -17,7 +17,15 @@ /* | ||
| function now() { | ||
| return new Date().toISOString().split('T')[1].replace(/[Z.\-:]/g, ''); | ||
| return new Date() | ||
| .toISOString() | ||
| .split('T')[1] | ||
| .replace(/[Z.\-:]/g, ''); | ||
| } | ||
| exports.requestProcessor = function (resource, method, integration, stage, | ||
| stageVariables) { | ||
| exports.requestProcessor = function ( | ||
| resource, | ||
| method, | ||
| integration, | ||
| stage, | ||
| stageVariables | ||
| ) { | ||
| let request_id = 0; | ||
@@ -65,5 +73,7 @@ return (req, params, parsed_url, headers, raw_body, authorizer) => { | ||
| } | ||
| if (!response | ||
| || typeof response.statusCode !== 'number' | ||
| || typeof response.body !== 'string') { | ||
| if ( | ||
| !response || | ||
| typeof response.statusCode !== 'number' || | ||
| typeof response.body !== 'string' | ||
| ) { | ||
| res.writeHead(502); | ||
@@ -70,0 +80,0 @@ res.end('Bad Gateway'); |
@@ -7,3 +7,3 @@ 'use strict'; | ||
| function mapResponseData(param, properties) { | ||
| if (param.startsWith('\'')) { | ||
| if (param.startsWith("'")) { | ||
| return param.substring(1, param.length - 1); | ||
@@ -10,0 +10,0 @@ } |
+43
-17
@@ -28,4 +28,9 @@ /* | ||
| exports.integrationProcessor = function (emitter, resource, method, stage, | ||
| stageVariables) { | ||
| exports.integrationProcessor = function ( | ||
| emitter, | ||
| resource, | ||
| method, | ||
| stage, | ||
| stageVariables | ||
| ) { | ||
| const integration = method['x-amazon-apigateway-integration']; | ||
@@ -37,4 +42,5 @@ const properties = { stageVariables }; | ||
| if (integration.httpMethod !== 'POST') { | ||
| throw new Error(`Unexpected lambda integration httpMethod "${ | ||
| integration.httpMethod}". Only POST is supported.`); | ||
| throw new Error( | ||
| `Unexpected lambda integration httpMethod "${integration.httpMethod}". Only POST is supported.` | ||
| ); | ||
| } | ||
@@ -47,4 +53,9 @@ const lambda_name = parseLambdaName(integration.uri); | ||
| const { requestProcessor, responseProcessor } = integration_impl; | ||
| const requestProc = requestProcessor(resource, method, integration, stage, | ||
| stageVariables); | ||
| const requestProc = requestProcessor( | ||
| resource, | ||
| method, | ||
| integration, | ||
| stage, | ||
| stageVariables | ||
| ); | ||
| const responseProc = responseProcessor(integration, stage, properties); | ||
@@ -70,11 +81,22 @@ return (req, res, params, parsed_url, headers, raw_body, policy) => { | ||
| try { | ||
| event = requestProc(req, params, parsed_url, headers, raw_body, authorizer); | ||
| event = requestProc( | ||
| req, | ||
| params, | ||
| parsed_url, | ||
| headers, | ||
| raw_body, | ||
| authorizer | ||
| ); | ||
| } catch (e) { | ||
| if (e.code === INVALID) { | ||
| const errorMessage = 'Invalid request'; | ||
| log.warn(errorMessage, { | ||
| method: req.method, | ||
| url: req.url, | ||
| headers | ||
| }, e); | ||
| log.warn( | ||
| errorMessage, | ||
| { | ||
| method: req.method, | ||
| url: req.url, | ||
| headers | ||
| }, | ||
| e | ||
| ); | ||
| res.writeHead(400, { 'Content-Type': 'application/json' }); | ||
@@ -85,7 +107,11 @@ res.end(JSON.stringify({ errorMessage })); | ||
| const errorMessage = 'Internal server error'; | ||
| log.error(errorMessage, { | ||
| method: req.method, | ||
| url: req.url, | ||
| headers | ||
| }, e); | ||
| log.error( | ||
| errorMessage, | ||
| { | ||
| method: req.method, | ||
| url: req.url, | ||
| headers | ||
| }, | ||
| e | ||
| ); | ||
| res.writeHead(500, { 'Content-Type': 'application/json' }); | ||
@@ -92,0 +118,0 @@ res.end(JSON.stringify({ errorMessage })); |
@@ -8,6 +8,4 @@ 'use strict'; | ||
| const GROUP_LAMBDA_NAME = '([^\\:/]+)'; | ||
| const API_GATEWAY_URI = `arn:aws:apigateway:${GROUP_REGION}:lambda:path/` | ||
| + `${GROUP_VERSION}`; | ||
| const LAMBDA_URI = `arn:aws:lambda:${GROUP_REGION}:${GROUP_ARN}:function:` | ||
| + `${GROUP_PREFIX}_${GROUP_LAMBDA_NAME}`; | ||
| const API_GATEWAY_URI = `arn:aws:apigateway:${GROUP_REGION}:lambda:path/${GROUP_VERSION}`; | ||
| const LAMBDA_URI = `arn:aws:lambda:${GROUP_REGION}:${GROUP_ARN}:function:${GROUP_PREFIX}_${GROUP_LAMBDA_NAME}`; | ||
| const LAMBDA_URI_RE = new RegExp(`^${API_GATEWAY_URI}/functions/${LAMBDA_URI}`); | ||
@@ -24,2 +22,1 @@ | ||
| exports.parseLambdaName = parseLambdaName; | ||
+0
-2
@@ -7,3 +7,2 @@ /* | ||
| class Route { | ||
| constructor(name, re, keys) { | ||
@@ -27,3 +26,2 @@ this.name = name; | ||
| } | ||
| } | ||
@@ -30,0 +28,0 @@ |
+36
-24
@@ -13,4 +13,9 @@ /* | ||
| exports.secutiryProcessor = function (emitter, swagger, security, cache, | ||
| delegate) { | ||
| exports.secutiryProcessor = function ( | ||
| emitter, | ||
| swagger, | ||
| security, | ||
| cache, | ||
| delegate | ||
| ) { | ||
| const security_key = Object.keys(security[0])[0]; | ||
@@ -29,4 +34,5 @@ const security_def = swagger.securityDefinitions[security_key]; | ||
| if (auth_type !== 'custom') { | ||
| throw new Error(`${security_key} "x-amazon-apigateway-authtype" ` | ||
| + 'must be "custom"'); | ||
| throw new Error( | ||
| `${security_key} "x-amazon-apigateway-authtype" must be "custom"` | ||
| ); | ||
| } | ||
@@ -62,24 +68,30 @@ const authorizer = security_def['x-amazon-apigateway-authorizer']; | ||
| } | ||
| emitter.emit('lambda', lambda_name, { | ||
| authorizationToken | ||
| }, {}, (err, policy) => { | ||
| if (err) { | ||
| reject(res, 401, String(err)); | ||
| return; | ||
| emitter.emit( | ||
| 'lambda', | ||
| lambda_name, | ||
| { | ||
| authorizationToken | ||
| }, | ||
| {}, | ||
| (err, policy) => { | ||
| if (err) { | ||
| reject(res, 401, String(err)); | ||
| return; | ||
| } | ||
| const { Statement } = policy.policyDocument; | ||
| if (Statement.length !== 1 || Statement[0].Effect !== 'Allow') { | ||
| reject(res, 403, 'Unauthorized'); | ||
| return; | ||
| } | ||
| const ttl = authorizer.authorizerResultTtlInSeconds; | ||
| if (ttl && authorizationToken) { | ||
| cache[authorizationToken] = { | ||
| policy, | ||
| timeout: setTimeout(removeCached(authorizationToken), ttl * 1000) | ||
| }; | ||
| } | ||
| delegate(req, res, params, query, headers, raw_body, policy); | ||
| } | ||
| const { Statement } = policy.policyDocument; | ||
| if (Statement.length !== 1 || Statement[0].Effect !== 'Allow') { | ||
| reject(res, 403, 'Unauthorized'); | ||
| return; | ||
| } | ||
| const ttl = authorizer.authorizerResultTtlInSeconds; | ||
| if (ttl && authorizationToken) { | ||
| cache[authorizationToken] = { | ||
| policy, | ||
| timeout: setTimeout(removeCached(authorizationToken), ttl * 1000) | ||
| }; | ||
| } | ||
| delegate(req, res, params, query, headers, raw_body, policy); | ||
| }); | ||
| ); | ||
| }; | ||
| }; |
+1
-1
@@ -66,3 +66,3 @@ /*eslint no-sync:0*/ | ||
| const file = options.file || 'swagger.json'; | ||
| // eslint-disable-next-line node/no-sync | ||
| // eslint-disable-next-line n/no-sync | ||
| const raw = fs.readFileSync(file, 'utf8'); | ||
@@ -69,0 +69,0 @@ let json; |
+23
-13
@@ -16,3 +16,2 @@ /* | ||
| class Input { | ||
| constructor(request, payload, body) { | ||
@@ -26,5 +25,7 @@ this._request = request; | ||
| if (name) { | ||
| return this._request.params[name] | ||
| || this._request.query[name] | ||
| || this._request.headers[name]; | ||
| return ( | ||
| this._request.params[name] || | ||
| this._request.query[name] || | ||
| this._request.headers[name] | ||
| ); | ||
| } | ||
@@ -45,3 +46,2 @@ return { | ||
| } | ||
| } | ||
@@ -91,11 +91,21 @@ | ||
| exports.renderTemplate = function (template, context, request, payload, body, | ||
| stageVariables) { | ||
| exports.renderTemplate = function ( | ||
| template, | ||
| context, | ||
| request, | ||
| payload, | ||
| body, | ||
| stageVariables | ||
| ) { | ||
| const input = new Input(request, payload, body); | ||
| return getTemplate(template).render({ | ||
| context, | ||
| input, | ||
| util, | ||
| stageVariables | ||
| }, null, true); | ||
| return getTemplate(template).render( | ||
| { | ||
| context, | ||
| input, | ||
| util, | ||
| stageVariables | ||
| }, | ||
| null, | ||
| true | ||
| ); | ||
| }; |
@@ -8,3 +8,4 @@ 'use strict'; | ||
| Object.keys(headers).forEach((key) => { | ||
| upper_headers[key.replace(/(^[a-z]|-[a-z])/g, matchToUpperCase)] = headers[key]; | ||
| upper_headers[key.replace(/(^[a-z]|-[a-z])/g, matchToUpperCase)] = | ||
| headers[key]; | ||
| }); | ||
@@ -11,0 +12,0 @@ return upper_headers; |
+15
-9
| { | ||
| "name": "@studio/gateway", | ||
| "version": "2.5.4", | ||
| "version": "3.0.0", | ||
| "description": "JavaScript Studio Gateway", | ||
@@ -19,3 +19,6 @@ "author": "Maximilian Antoni <max@javascript.studio>", | ||
| "version": "changes --commits --footer", | ||
| "postversion": "git push --follow-tags && npm publish" | ||
| "postversion": "git push --follow-tags && npm publish", | ||
| "prettier:check": "prettier --check '**/*.{js,md}'", | ||
| "prettier:write": "prettier --write '**/*.{js,md}'", | ||
| "prepare": "husky install" | ||
| }, | ||
@@ -26,3 +29,3 @@ "eslintConfig": { | ||
| "dependencies": { | ||
| "@studio/fail": "^1.5.0", | ||
| "@studio/fail": "^1.7.0", | ||
| "@studio/log": "^2.0.0", | ||
@@ -33,15 +36,18 @@ "dotenv": "^6.0.0", | ||
| "jsonpath": "^1.0.2", | ||
| "minimist": "^1.2.0", | ||
| "minimist": "^1.2.8", | ||
| "velocityjs": "^1.1.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@sinonjs/referee-sinon": "^9.0.1", | ||
| "@studio/changes": "^2.0.0", | ||
| "@studio/eslint-config": "^2.0.0", | ||
| "eslint": "^7.23.0", | ||
| "eslint-plugin-mocha": "^8.1.0", | ||
| "@sinonjs/referee-sinon": "^12.0.0", | ||
| "@studio/changes": "^3.0.0", | ||
| "@studio/eslint-config": "^6.0.0", | ||
| "eslint": "^8.56.0", | ||
| "eslint-plugin-node": "^11.1.0", | ||
| "husky": "^8.0.3", | ||
| "lint-staged": "^15.2.0", | ||
| "mocha": "^8.3.2", | ||
| "prettier": "^3.2.4", | ||
| "supertest": "^3.4.2" | ||
| }, | ||
| "license": "MIT", | ||
| "files": [ | ||
@@ -48,0 +54,0 @@ "bin", |
+5
-5
@@ -26,3 +26,3 @@ # Studio Gateway | ||
| const lambda = Lambda.create(); | ||
| const gateway = Gateway.create() | ||
| const gateway = Gateway.create(); | ||
| gateway.on('lambda', lambda.invoke); | ||
@@ -36,6 +36,6 @@ gateway.listen(1337); | ||
| for the given options. | ||
| - `swagger_file`: The swagger file to read. Defaults to `swagger.json`. | ||
| - `swagger_env`: The [dotenv][] config to read. | ||
| - `stage`: The stage name to use. Defaults to "local". | ||
| - `stageVariables`: The stage variables to use. Default to an empty object. | ||
| - `swagger_file`: The swagger file to read. Defaults to `swagger.json`. | ||
| - `swagger_env`: The [dotenv][] config to read. | ||
| - `stage`: The stage name to use. Defaults to "local". | ||
| - `stageVariables`: The stage variables to use. Default to an empty object. | ||
| - `gateway.listen(port[, callback])`: Bind the server to the given port. | ||
@@ -42,0 +42,0 @@ |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
45020
8.84%17
6.25%937
11.15%10
25%Updated
Updated