serverless-offline 8.8.1 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/README.md +3 -3
  2. package/package.json +33 -57
  3. package/src/ServerlessOffline.js +412 -0
  4. package/src/config/commandOptions.js +155 -0
  5. package/src/config/constants.js +22 -0
  6. package/{dist → src}/config/defaultOptions.js +8 -17
  7. package/src/config/index.js +4 -0
  8. package/src/config/supportedRuntimes.js +47 -0
  9. package/src/events/authCanExecuteResource.js +35 -0
  10. package/src/events/authFunctionNameExtractor.js +75 -0
  11. package/src/events/authMatchPolicyResource.js +71 -0
  12. package/src/events/authValidateContext.js +51 -0
  13. package/src/events/http/Endpoint.js +135 -0
  14. package/src/events/http/Http.js +50 -0
  15. package/src/events/http/HttpEventDefinition.js +20 -0
  16. package/src/events/http/HttpServer.js +1277 -0
  17. package/src/events/http/OfflineEndpoint.js +33 -0
  18. package/src/events/http/authJWTSettingsExtractor.js +70 -0
  19. package/src/events/http/createAuthScheme.js +176 -0
  20. package/src/events/http/createJWTAuthScheme.js +106 -0
  21. package/src/events/http/index.js +1 -0
  22. package/src/events/http/javaHelpers.js +102 -0
  23. package/src/events/http/lambda-events/LambdaIntegrationEvent.js +57 -0
  24. package/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js +233 -0
  25. package/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +190 -0
  26. package/src/events/http/lambda-events/VelocityContext.js +147 -0
  27. package/src/events/http/lambda-events/index.js +4 -0
  28. package/src/events/http/lambda-events/renderVelocityTemplateObject.js +93 -0
  29. package/{dist → src}/events/http/parseResources.js +73 -78
  30. package/src/events/http/payloadSchemaValidator.js +13 -0
  31. package/{dist → src}/events/http/templates/offline-default.req.vm +0 -0
  32. package/{dist → src}/events/http/templates/offline-default.res.vm +0 -0
  33. package/src/events/schedule/Schedule.js +131 -0
  34. package/src/events/schedule/ScheduleEvent.js +18 -0
  35. package/src/events/schedule/ScheduleEventDefinition.js +21 -0
  36. package/src/events/schedule/index.js +1 -0
  37. package/src/events/websocket/HttpServer.js +69 -0
  38. package/src/events/websocket/WebSocket.js +52 -0
  39. package/src/events/websocket/WebSocketClients.js +462 -0
  40. package/src/events/websocket/WebSocketEventDefinition.js +18 -0
  41. package/src/events/websocket/WebSocketServer.js +73 -0
  42. package/src/events/websocket/http-routes/_catchAll/catchAllRoute.js +16 -0
  43. package/src/events/websocket/http-routes/_catchAll/index.js +1 -0
  44. package/src/events/websocket/http-routes/connections/ConnectionsController.js +28 -0
  45. package/src/events/websocket/http-routes/connections/connectionsRoutes.js +70 -0
  46. package/src/events/websocket/http-routes/connections/index.js +1 -0
  47. package/src/events/websocket/http-routes/index.js +2 -0
  48. package/src/events/websocket/index.js +1 -0
  49. package/src/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +65 -0
  50. package/src/events/websocket/lambda-events/WebSocketConnectEvent.js +68 -0
  51. package/src/events/websocket/lambda-events/WebSocketDisconnectEvent.js +31 -0
  52. package/src/events/websocket/lambda-events/WebSocketEvent.js +29 -0
  53. package/src/events/websocket/lambda-events/WebSocketRequestContext.js +67 -0
  54. package/src/events/websocket/lambda-events/index.js +4 -0
  55. package/src/index.js +12 -0
  56. package/src/lambda/HttpServer.js +108 -0
  57. package/src/lambda/Lambda.js +68 -0
  58. package/src/lambda/LambdaContext.js +33 -0
  59. package/src/lambda/LambdaFunction.js +308 -0
  60. package/src/lambda/LambdaFunctionPool.js +109 -0
  61. package/src/lambda/__tests__/LambdaContext.test.js +30 -0
  62. package/src/lambda/__tests__/LambdaFunction.test.js +196 -0
  63. package/src/lambda/__tests__/fixtures/Lambda/LambdaFunctionThatReturnsJSONObject.fixture.js +47 -0
  64. package/src/lambda/__tests__/fixtures/Lambda/LambdaFunctionThatReturnsNativeString.fixture.js +46 -0
  65. package/src/lambda/__tests__/fixtures/Lambda/package.json +3 -0
  66. package/src/lambda/__tests__/fixtures/lambdaFunction.fixture.js +145 -0
  67. package/src/lambda/__tests__/fixtures/package.json +3 -0
  68. package/src/lambda/__tests__/routes/invocations/InvocationsController.test.js +42 -0
  69. package/src/lambda/handler-runner/HandlerRunner.js +136 -0
  70. package/src/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +72 -0
  71. package/src/lambda/handler-runner/child-process-runner/childProcessHelper.js +42 -0
  72. package/src/lambda/handler-runner/child-process-runner/index.js +1 -0
  73. package/src/lambda/handler-runner/docker-runner/DockerContainer.js +417 -0
  74. package/src/lambda/handler-runner/docker-runner/DockerImage.js +35 -0
  75. package/src/lambda/handler-runner/docker-runner/DockerRunner.js +63 -0
  76. package/src/lambda/handler-runner/docker-runner/index.js +1 -0
  77. package/src/lambda/handler-runner/go-runner/GoRunner.js +166 -0
  78. package/src/lambda/handler-runner/go-runner/index.js +1 -0
  79. package/src/lambda/handler-runner/in-process-runner/InProcessRunner.js +125 -0
  80. package/src/lambda/handler-runner/in-process-runner/index.js +1 -0
  81. package/src/lambda/handler-runner/index.js +1 -0
  82. package/src/lambda/handler-runner/java-runner/JavaRunner.js +114 -0
  83. package/src/lambda/handler-runner/java-runner/index.js +1 -0
  84. package/src/lambda/handler-runner/python-runner/PythonRunner.js +138 -0
  85. package/src/lambda/handler-runner/python-runner/index.js +1 -0
  86. package/{dist → src}/lambda/handler-runner/python-runner/invoke.py +0 -0
  87. package/src/lambda/handler-runner/ruby-runner/RubyRunner.js +107 -0
  88. package/src/lambda/handler-runner/ruby-runner/index.js +1 -0
  89. package/{dist → src}/lambda/handler-runner/ruby-runner/invoke.rb +0 -0
  90. package/src/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +70 -0
  91. package/src/lambda/handler-runner/worker-thread-runner/index.js +1 -0
  92. package/src/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +29 -0
  93. package/src/lambda/index.js +1 -0
  94. package/src/lambda/routes/index.js +2 -0
  95. package/src/lambda/routes/invocations/InvocationsController.js +102 -0
  96. package/src/lambda/routes/invocations/index.js +1 -0
  97. package/src/lambda/routes/invocations/invocationsRoute.js +77 -0
  98. package/src/lambda/routes/invoke-async/InvokeAsyncController.js +20 -0
  99. package/src/lambda/routes/invoke-async/index.js +1 -0
  100. package/src/lambda/routes/invoke-async/invokeAsyncRoute.js +33 -0
  101. package/src/utils/__tests__/createUniqueId.test.js +18 -0
  102. package/src/utils/__tests__/formatToClfTime.test.js +14 -0
  103. package/src/utils/__tests__/generateHapiPath.test.js +46 -0
  104. package/src/utils/__tests__/lowerCaseKeys.test.js +30 -0
  105. package/src/utils/__tests__/parseHeaders.test.js +13 -0
  106. package/src/utils/__tests__/parseMultiValueHeaders.test.js +24 -0
  107. package/src/utils/__tests__/parseMultiValueQueryStringParameters.test.js +159 -0
  108. package/src/utils/__tests__/parseQueryStringParameters.test.js +15 -0
  109. package/src/utils/__tests__/splitHandlerPathAndName.test.js +54 -0
  110. package/src/utils/__tests__/unflatten.test.js +32 -0
  111. package/src/utils/checkDockerDaemon.js +19 -0
  112. package/src/utils/checkGoVersion.js +16 -0
  113. package/src/utils/createApiKey.js +5 -0
  114. package/src/utils/createUniqueId.js +5 -0
  115. package/src/utils/detectExecutable.js +11 -0
  116. package/{dist → src}/utils/formatToClfTime.js +6 -14
  117. package/src/utils/generateHapiPath.js +26 -0
  118. package/src/utils/getHttpApiCorsConfig.js +28 -0
  119. package/src/utils/index.js +42 -0
  120. package/src/utils/jsonPath.js +13 -0
  121. package/src/utils/logRoutes.js +64 -0
  122. package/src/utils/lowerCaseKeys.js +6 -0
  123. package/src/utils/parseHeaders.js +14 -0
  124. package/src/utils/parseMultiValueHeaders.js +27 -0
  125. package/src/utils/parseMultiValueQueryStringParameters.js +31 -0
  126. package/src/utils/parseQueryStringParameters.js +15 -0
  127. package/src/utils/resolveJoins.js +29 -0
  128. package/src/utils/splitHandlerPathAndName.js +31 -0
  129. package/src/utils/unflatten.js +11 -0
  130. package/CHANGELOG.md +0 -78
  131. package/dist/ServerlessOffline.js +0 -508
  132. package/dist/config/commandOptions.js +0 -149
  133. package/dist/config/constants.js +0 -30
  134. package/dist/config/index.js +0 -55
  135. package/dist/config/supportedRuntimes.js +0 -40
  136. package/dist/debugLog.js +0 -12
  137. package/dist/events/authCanExecuteResource.js +0 -35
  138. package/dist/events/authFunctionNameExtractor.js +0 -87
  139. package/dist/events/authMatchPolicyResource.js +0 -62
  140. package/dist/events/authValidateContext.js +0 -53
  141. package/dist/events/http/Endpoint.js +0 -173
  142. package/dist/events/http/Http.js +0 -77
  143. package/dist/events/http/HttpEventDefinition.js +0 -36
  144. package/dist/events/http/HttpServer.js +0 -1370
  145. package/dist/events/http/OfflineEndpoint.js +0 -38
  146. package/dist/events/http/authJWTSettingsExtractor.js +0 -76
  147. package/dist/events/http/createAuthScheme.js +0 -184
  148. package/dist/events/http/createJWTAuthScheme.js +0 -159
  149. package/dist/events/http/index.js +0 -15
  150. package/dist/events/http/javaHelpers.js +0 -99
  151. package/dist/events/http/lambda-events/LambdaIntegrationEvent.js +0 -87
  152. package/dist/events/http/lambda-events/LambdaProxyIntegrationEvent.js +0 -246
  153. package/dist/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +0 -225
  154. package/dist/events/http/lambda-events/VelocityContext.js +0 -170
  155. package/dist/events/http/lambda-events/index.js +0 -39
  156. package/dist/events/http/lambda-events/renderVelocityTemplateObject.js +0 -111
  157. package/dist/events/http/payloadSchemaValidator.js +0 -13
  158. package/dist/events/schedule/Schedule.js +0 -183
  159. package/dist/events/schedule/ScheduleEvent.js +0 -27
  160. package/dist/events/schedule/ScheduleEventDefinition.js +0 -36
  161. package/dist/events/schedule/index.js +0 -15
  162. package/dist/events/websocket/HttpServer.js +0 -114
  163. package/dist/events/websocket/WebSocket.js +0 -78
  164. package/dist/events/websocket/WebSocketClients.js +0 -577
  165. package/dist/events/websocket/WebSocketEventDefinition.js +0 -32
  166. package/dist/events/websocket/WebSocketServer.js +0 -139
  167. package/dist/events/websocket/http-routes/_catchAll/catchAllRoute.js +0 -33
  168. package/dist/events/websocket/http-routes/_catchAll/index.js +0 -15
  169. package/dist/events/websocket/http-routes/connections/ConnectionsController.js +0 -45
  170. package/dist/events/websocket/http-routes/connections/connectionsRoutes.js +0 -95
  171. package/dist/events/websocket/http-routes/connections/index.js +0 -15
  172. package/dist/events/websocket/http-routes/index.js +0 -23
  173. package/dist/events/websocket/index.js +0 -15
  174. package/dist/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +0 -99
  175. package/dist/events/websocket/lambda-events/WebSocketConnectEvent.js +0 -101
  176. package/dist/events/websocket/lambda-events/WebSocketDisconnectEvent.js +0 -47
  177. package/dist/events/websocket/lambda-events/WebSocketEvent.js +0 -54
  178. package/dist/events/websocket/lambda-events/WebSocketRequestContext.js +0 -98
  179. package/dist/events/websocket/lambda-events/index.js +0 -39
  180. package/dist/index.js +0 -15
  181. package/dist/lambda/HttpServer.js +0 -124
  182. package/dist/lambda/Lambda.js +0 -117
  183. package/dist/lambda/LambdaContext.js +0 -53
  184. package/dist/lambda/LambdaFunction.js +0 -390
  185. package/dist/lambda/LambdaFunctionPool.js +0 -127
  186. package/dist/lambda/handler-runner/HandlerRunner.js +0 -195
  187. package/dist/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +0 -124
  188. package/dist/lambda/handler-runner/child-process-runner/childProcessHelper.js +0 -49
  189. package/dist/lambda/handler-runner/child-process-runner/index.js +0 -15
  190. package/dist/lambda/handler-runner/docker-runner/DockerContainer.js +0 -515
  191. package/dist/lambda/handler-runner/docker-runner/DockerImage.js +0 -67
  192. package/dist/lambda/handler-runner/docker-runner/DockerRunner.js +0 -74
  193. package/dist/lambda/handler-runner/docker-runner/index.js +0 -15
  194. package/dist/lambda/handler-runner/go-runner/GoRunner.js +0 -230
  195. package/dist/lambda/handler-runner/go-runner/index.js +0 -15
  196. package/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js +0 -228
  197. package/dist/lambda/handler-runner/in-process-runner/index.js +0 -15
  198. package/dist/lambda/handler-runner/index.js +0 -15
  199. package/dist/lambda/handler-runner/java-runner/JavaRunner.js +0 -153
  200. package/dist/lambda/handler-runner/java-runner/index.js +0 -15
  201. package/dist/lambda/handler-runner/python-runner/PythonRunner.js +0 -185
  202. package/dist/lambda/handler-runner/python-runner/index.js +0 -15
  203. package/dist/lambda/handler-runner/ruby-runner/RubyRunner.js +0 -147
  204. package/dist/lambda/handler-runner/ruby-runner/index.js +0 -15
  205. package/dist/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +0 -92
  206. package/dist/lambda/handler-runner/worker-thread-runner/index.js +0 -15
  207. package/dist/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +0 -31
  208. package/dist/lambda/index.js +0 -15
  209. package/dist/lambda/routes/index.js +0 -23
  210. package/dist/lambda/routes/invocations/InvocationsController.js +0 -142
  211. package/dist/lambda/routes/invocations/index.js +0 -15
  212. package/dist/lambda/routes/invocations/invocationsRoute.js +0 -90
  213. package/dist/lambda/routes/invoke-async/InvokeAsyncController.js +0 -38
  214. package/dist/lambda/routes/invoke-async/index.js +0 -15
  215. package/dist/lambda/routes/invoke-async/invokeAsyncRoute.js +0 -43
  216. package/dist/main.js +0 -11
  217. package/dist/serverlessLog.js +0 -91
  218. package/dist/utils/checkDockerDaemon.js +0 -27
  219. package/dist/utils/checkGoVersion.js +0 -27
  220. package/dist/utils/createApiKey.js +0 -12
  221. package/dist/utils/createUniqueId.js +0 -14
  222. package/dist/utils/detectExecutable.js +0 -21
  223. package/dist/utils/generateHapiPath.js +0 -28
  224. package/dist/utils/getHttpApiCorsConfig.js +0 -40
  225. package/dist/utils/index.js +0 -165
  226. package/dist/utils/jsonPath.js +0 -21
  227. package/dist/utils/lowerCaseKeys.js +0 -14
  228. package/dist/utils/parseHeaders.js +0 -23
  229. package/dist/utils/parseMultiValueHeaders.js +0 -36
  230. package/dist/utils/parseMultiValueQueryStringParameters.js +0 -40
  231. package/dist/utils/parseQueryStringParameters.js +0 -26
  232. package/dist/utils/resolveJoins.js +0 -36
  233. package/dist/utils/satisfiesVersionRange.js +0 -20
  234. package/dist/utils/splitHandlerPathAndName.js +0 -37
  235. package/dist/utils/unflatten.js +0 -18
@@ -0,0 +1,233 @@
1
+ import { Buffer } from 'node:buffer'
2
+ import { env } from 'node:process'
3
+ import { log } from '@serverless/utils/log.js'
4
+ import { decode } from 'jsonwebtoken'
5
+ import {
6
+ createUniqueId,
7
+ formatToClfTime,
8
+ nullIfEmpty,
9
+ parseHeaders,
10
+ parseMultiValueHeaders,
11
+ parseMultiValueQueryStringParameters,
12
+ parseQueryStringParameters,
13
+ } from '../../../utils/index.js'
14
+
15
+ const { byteLength } = Buffer
16
+ const { parse } = JSON
17
+ const { assign } = Object
18
+
19
+ // https://serverless.com/framework/docs/providers/aws/events/apigateway/
20
+ // https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
21
+ // http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html
22
+ export default class LambdaProxyIntegrationEvent {
23
+ #additionalRequestContext = null
24
+
25
+ #path = null
26
+
27
+ #routeKey = null
28
+
29
+ #request = null
30
+
31
+ #stage = null
32
+
33
+ #stageVariables = null
34
+
35
+ constructor(
36
+ request,
37
+ stage,
38
+ path,
39
+ stageVariables,
40
+ routeKey,
41
+ additionalRequestContext,
42
+ ) {
43
+ this.#additionalRequestContext = additionalRequestContext || {}
44
+ this.#path = path
45
+ this.#routeKey = routeKey
46
+ this.#request = request
47
+ this.#stage = stage
48
+ this.#stageVariables = stageVariables
49
+ }
50
+
51
+ create() {
52
+ const authPrincipalId =
53
+ this.#request.auth &&
54
+ this.#request.auth.credentials &&
55
+ this.#request.auth.credentials.principalId
56
+
57
+ const authContext =
58
+ (this.#request.auth &&
59
+ this.#request.auth.credentials &&
60
+ this.#request.auth.credentials.context) ||
61
+ {}
62
+
63
+ let authAuthorizer
64
+
65
+ if (env.AUTHORIZER) {
66
+ try {
67
+ authAuthorizer = parse(env.AUTHORIZER)
68
+ } catch {
69
+ log.error(
70
+ 'Could not parse env.AUTHORIZER, make sure it is correct JSON',
71
+ )
72
+ }
73
+ }
74
+
75
+ let body = this.#request.payload
76
+
77
+ const { rawHeaders, url } = this.#request.raw.req
78
+
79
+ // NOTE FIXME request.raw.req.rawHeaders can only be null for testing (hapi shot inject())
80
+ const headers = parseHeaders(rawHeaders || []) || {}
81
+
82
+ if (headers['sls-offline-authorizer-override']) {
83
+ try {
84
+ authAuthorizer = parse(headers['sls-offline-authorizer-override'])
85
+ } catch {
86
+ log.error(
87
+ 'Could not parse header sls-offline-authorizer-override, make sure it is correct JSON',
88
+ )
89
+ }
90
+ }
91
+
92
+ if (body) {
93
+ if (typeof body !== 'string') {
94
+ // this.#request.payload is NOT the same as the rawPayload
95
+ body = this.#request.rawPayload
96
+ }
97
+
98
+ if (
99
+ !headers['Content-Length'] &&
100
+ !headers['content-length'] &&
101
+ !headers['Content-length'] &&
102
+ (typeof body === 'string' ||
103
+ body instanceof Buffer ||
104
+ body instanceof ArrayBuffer)
105
+ ) {
106
+ headers['Content-Length'] = String(byteLength(body))
107
+ }
108
+
109
+ // Set a default Content-Type if not provided.
110
+ if (
111
+ !headers['Content-Type'] &&
112
+ !headers['content-type'] &&
113
+ !headers['Content-type']
114
+ ) {
115
+ headers['Content-Type'] = 'application/json'
116
+ }
117
+ } else if (typeof body === 'undefined' || body === '') {
118
+ body = null
119
+ }
120
+
121
+ // clone own props
122
+ const pathParams = { ...this.#request.params }
123
+
124
+ let token = headers.Authorization || headers.authorization
125
+
126
+ if (token && token.split(' ')[0] === 'Bearer') {
127
+ ;[, token] = token.split(' ')
128
+ }
129
+
130
+ let claims
131
+ let scopes
132
+
133
+ if (token) {
134
+ try {
135
+ claims = decode(token) || undefined
136
+ if (claims && claims.scope) {
137
+ scopes = claims.scope.split(' ')
138
+ // In AWS HTTP Api the scope property is removed from the decoded JWT
139
+ // I'm leaving this property because I'm not sure how all of the authorizers
140
+ // for AWS REST Api handle JWT.
141
+ // claims = { ...claims }
142
+ // delete claims.scope
143
+ }
144
+ } catch {
145
+ // Do nothing
146
+ }
147
+ }
148
+
149
+ const {
150
+ headers: _headers,
151
+ info: { received, remoteAddress },
152
+ method,
153
+ route,
154
+ } = this.#request
155
+
156
+ const httpMethod = method.toUpperCase()
157
+ const requestTime = formatToClfTime(received)
158
+ const requestTimeEpoch = received
159
+ const resource = this.#routeKey || route.path.replace(`/${this.#stage}`, '')
160
+
161
+ return {
162
+ body,
163
+ headers,
164
+ httpMethod,
165
+ isBase64Encoded: false, // TODO hook up
166
+ multiValueHeaders: parseMultiValueHeaders(
167
+ // NOTE FIXME request.raw.req.rawHeaders can only be null for testing (hapi shot inject())
168
+ rawHeaders || [],
169
+ ),
170
+ multiValueQueryStringParameters:
171
+ parseMultiValueQueryStringParameters(url),
172
+ path: this.#path,
173
+ pathParameters: nullIfEmpty(pathParams),
174
+ queryStringParameters: parseQueryStringParameters(url),
175
+ requestContext: {
176
+ accountId: 'offlineContext_accountId',
177
+ apiId: 'offlineContext_apiId',
178
+ authorizer:
179
+ authAuthorizer ||
180
+ assign(authContext, {
181
+ claims,
182
+ // 'principalId' should have higher priority
183
+ principalId:
184
+ authPrincipalId ||
185
+ env.PRINCIPAL_ID ||
186
+ 'offlineContext_authorizer_principalId', // See #24
187
+ scopes,
188
+ }),
189
+ domainName: 'offlineContext_domainName',
190
+ domainPrefix: 'offlineContext_domainPrefix',
191
+ extendedRequestId: createUniqueId(),
192
+ httpMethod,
193
+ identity: {
194
+ accessKey: null,
195
+ accountId: env.SLS_ACCOUNT_ID || 'offlineContext_accountId',
196
+ apiKey: env.SLS_API_KEY || 'offlineContext_apiKey',
197
+ apiKeyId: env.SLS_API_KEY_ID || 'offlineContext_apiKeyId',
198
+ caller: env.SLS_CALLER || 'offlineContext_caller',
199
+ cognitoAuthenticationProvider:
200
+ _headers['cognito-authentication-provider'] ||
201
+ env.SLS_COGNITO_AUTHENTICATION_PROVIDER ||
202
+ 'offlineContext_cognitoAuthenticationProvider',
203
+ cognitoAuthenticationType:
204
+ env.SLS_COGNITO_AUTHENTICATION_TYPE ||
205
+ 'offlineContext_cognitoAuthenticationType',
206
+ cognitoIdentityId:
207
+ _headers['cognito-identity-id'] ||
208
+ env.SLS_COGNITO_IDENTITY_ID ||
209
+ 'offlineContext_cognitoIdentityId',
210
+ cognitoIdentityPoolId:
211
+ env.SLS_COGNITO_IDENTITY_POOL_ID ||
212
+ 'offlineContext_cognitoIdentityPoolId',
213
+ principalOrgId: null,
214
+ sourceIp: remoteAddress,
215
+ user: 'offlineContext_user',
216
+ userAgent: _headers['user-agent'] || '',
217
+ userArn: 'offlineContext_userArn',
218
+ },
219
+ operationName: this.#additionalRequestContext.operationName,
220
+ path: this.#path,
221
+ protocol: 'HTTP/1.1',
222
+ requestId: createUniqueId(),
223
+ requestTime,
224
+ requestTimeEpoch,
225
+ resourceId: 'offlineContext_resourceId',
226
+ resourcePath: route.path,
227
+ stage: this.#stage,
228
+ },
229
+ resource,
230
+ stageVariables: this.#stageVariables,
231
+ }
232
+ }
233
+ }
@@ -0,0 +1,190 @@
1
+ import { Buffer } from 'node:buffer'
2
+ import { env } from 'node:process'
3
+ import { log } from '@serverless/utils/log.js'
4
+ import { decode } from 'jsonwebtoken'
5
+ import {
6
+ formatToClfTime,
7
+ lowerCaseKeys,
8
+ nullIfEmpty,
9
+ parseHeaders,
10
+ } from '../../../utils/index.js'
11
+
12
+ const { isArray } = Array
13
+ const { parse } = JSON
14
+ const { assign, entries, fromEntries } = Object
15
+
16
+ // https://www.serverless.com/framework/docs/providers/aws/events/http-api/
17
+ // https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
18
+ export default class LambdaProxyIntegrationEventV2 {
19
+ #additionalRequestContext = null
20
+
21
+ #routeKey = null
22
+
23
+ #request = null
24
+
25
+ #stage = null
26
+
27
+ #stageVariables = null
28
+
29
+ constructor(
30
+ request,
31
+ stage,
32
+ routeKey,
33
+ stageVariables,
34
+ additionalRequestContext,
35
+ ) {
36
+ this.#additionalRequestContext = additionalRequestContext || {}
37
+ this.#routeKey = routeKey
38
+ this.#request = request
39
+ this.#stage = stage
40
+ this.#stageVariables = stageVariables
41
+ }
42
+
43
+ create() {
44
+ const authContext =
45
+ (this.#request.auth &&
46
+ this.#request.auth.credentials &&
47
+ this.#request.auth.credentials.context) ||
48
+ {}
49
+
50
+ let authAuthorizer
51
+
52
+ if (env.AUTHORIZER) {
53
+ try {
54
+ authAuthorizer = parse(env.AUTHORIZER)
55
+ } catch {
56
+ log.error(
57
+ 'Could not parse process.env.AUTHORIZER, make sure it is correct JSON',
58
+ )
59
+ }
60
+ }
61
+
62
+ let body = this.#request.payload
63
+
64
+ const { rawHeaders } = this.#request.raw.req
65
+
66
+ // NOTE FIXME request.raw.req.rawHeaders can only be null for testing (hapi shot inject())
67
+ const headers = lowerCaseKeys(parseHeaders(rawHeaders || [])) || {}
68
+
69
+ if (headers['sls-offline-authorizer-override']) {
70
+ try {
71
+ authAuthorizer = parse(headers['sls-offline-authorizer-override'])
72
+ } catch {
73
+ log.error(
74
+ 'Could not parse header sls-offline-authorizer-override, make sure it is correct JSON',
75
+ )
76
+ }
77
+ }
78
+
79
+ if (body) {
80
+ if (typeof body !== 'string') {
81
+ // this.#request.payload is NOT the same as the rawPayload
82
+ body = this.#request.rawPayload
83
+ }
84
+
85
+ if (
86
+ !headers['content-length'] &&
87
+ (typeof body === 'string' ||
88
+ body instanceof Buffer ||
89
+ body instanceof ArrayBuffer)
90
+ ) {
91
+ headers['content-length'] = String(Buffer.byteLength(body))
92
+ }
93
+
94
+ // Set a default Content-Type if not provided.
95
+ if (!headers['content-type']) {
96
+ headers['content-type'] = 'application/json'
97
+ }
98
+ } else if (typeof body === 'undefined' || body === '') {
99
+ body = null
100
+ }
101
+
102
+ // clone own props
103
+ const pathParams = { ...this.#request.params }
104
+
105
+ let token = headers.Authorization || headers.authorization
106
+
107
+ if (token && token.split(' ')[0] === 'Bearer') {
108
+ ;[, token] = token.split(' ')
109
+ }
110
+
111
+ let claims
112
+ let scopes
113
+
114
+ if (token) {
115
+ try {
116
+ claims = decode(token) || undefined
117
+ if (claims && claims.scope) {
118
+ scopes = claims.scope.split(' ')
119
+ // In AWS HTTP Api the scope property is removed from the decoded JWT
120
+ // I'm leaving this property because I'm not sure how all of the authorizers
121
+ // for AWS REST Api handle JWT.
122
+ // claims = { ...claims }
123
+ // delete claims.scope
124
+ }
125
+ } catch {
126
+ // Do nothing
127
+ }
128
+ }
129
+
130
+ const {
131
+ headers: _headers,
132
+ info: { received, remoteAddress },
133
+ method,
134
+ } = this.#request
135
+
136
+ const httpMethod = method.toUpperCase()
137
+ const requestTime = formatToClfTime(received)
138
+ const requestTimeEpoch = received
139
+
140
+ const cookies = entries(this.#request.state).flatMap(([key, value]) => {
141
+ if (isArray(value)) {
142
+ return value.map((v) => `${key}=${v}`)
143
+ }
144
+ return `${key}=${value}`
145
+ })
146
+
147
+ return {
148
+ body,
149
+ cookies,
150
+ headers,
151
+ isBase64Encoded: false,
152
+ pathParameters: nullIfEmpty(pathParams),
153
+ queryStringParameters: this.#request.url.search
154
+ ? fromEntries(Array.from(this.#request.url.searchParams))
155
+ : null,
156
+ rawPath: this.#request.url.pathname,
157
+ rawQueryString: this.#request.url.searchParams.toString(),
158
+ requestContext: {
159
+ accountId: 'offlineContext_accountId',
160
+ apiId: 'offlineContext_apiId',
161
+ authorizer:
162
+ authAuthorizer ||
163
+ assign(authContext, {
164
+ jwt: {
165
+ claims,
166
+ scopes,
167
+ },
168
+ }),
169
+ domainName: 'offlineContext_domainName',
170
+ domainPrefix: 'offlineContext_domainPrefix',
171
+ http: {
172
+ method: httpMethod,
173
+ path: this.#request.url.pathname,
174
+ protocol: 'HTTP/1.1',
175
+ sourceIp: remoteAddress,
176
+ userAgent: _headers['user-agent'] || '',
177
+ },
178
+ operationName: this.#additionalRequestContext.operationName,
179
+ requestId: 'offlineContext_resourceId',
180
+ routeKey: this.#routeKey,
181
+ stage: this.#stage,
182
+ time: requestTime,
183
+ timeEpoch: requestTimeEpoch,
184
+ },
185
+ routeKey: this.#routeKey,
186
+ stageVariables: this.#stageVariables,
187
+ version: '2.0',
188
+ }
189
+ }
190
+ }
@@ -0,0 +1,147 @@
1
+ import { Buffer } from 'node:buffer'
2
+ import { env } from 'node:process'
3
+ import jsEscapeString from 'js-string-escape'
4
+ import { decode } from 'jsonwebtoken'
5
+ import {
6
+ createUniqueId,
7
+ isPlainObject,
8
+ jsonPath,
9
+ parseHeaders,
10
+ } from '../../../utils/index.js'
11
+
12
+ const { parse, stringify } = JSON
13
+ const { assign, entries, fromEntries } = Object
14
+
15
+ function escapeJavaScript(x) {
16
+ if (typeof x === 'string') {
17
+ return jsEscapeString(x).replace(/\\n/g, '\n') // See #26,
18
+ }
19
+
20
+ if (isPlainObject(x)) {
21
+ const result = fromEntries(
22
+ entries(x).map(([key, value]) => [key, jsEscapeString(value)]),
23
+ )
24
+
25
+ return stringify(result) // Is this really how APIG does it?
26
+ }
27
+
28
+ if (typeof x.toString === 'function') {
29
+ return escapeJavaScript(x.toString())
30
+ }
31
+
32
+ return x
33
+ }
34
+
35
+ /*
36
+ Returns a context object that mocks APIG mapping template reference
37
+ http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
38
+ */
39
+ export default class VelocityContext {
40
+ #path = null
41
+
42
+ #payload = null
43
+
44
+ #request = null
45
+
46
+ #stage = null
47
+
48
+ constructor(request, stage, payload, path) {
49
+ this.#path = path
50
+ this.#payload = payload
51
+ this.#request = request
52
+ this.#stage = stage
53
+ }
54
+
55
+ getContext() {
56
+ const path = (x) => jsonPath(this.#payload, x)
57
+
58
+ const authPrincipalId =
59
+ this.#request.auth &&
60
+ this.#request.auth.credentials &&
61
+ this.#request.auth.credentials.principalId
62
+
63
+ let authorizer =
64
+ this.#request.auth &&
65
+ this.#request.auth.credentials &&
66
+ this.#request.auth.credentials.authorizer
67
+
68
+ // NOTE FIXME request.raw.req.rawHeaders can only be null for testing (hapi shot inject())
69
+ const headers = parseHeaders(this.#request.raw.req.rawHeaders || [])
70
+
71
+ let token = headers && (headers.Authorization || headers.authorization)
72
+
73
+ if (token && token.split(' ')[0] === 'Bearer') {
74
+ ;[, token] = token.split(' ')
75
+ }
76
+
77
+ if (!authorizer) authorizer = {}
78
+
79
+ authorizer.principalId =
80
+ authPrincipalId ||
81
+ env.PRINCIPAL_ID ||
82
+ 'offlineContext_authorizer_principalId' // See #24
83
+
84
+ if (token) {
85
+ try {
86
+ const claims = decode(token) || undefined
87
+ if (claims) {
88
+ assign(authorizer, { claims })
89
+ }
90
+ } catch {
91
+ // Nothing
92
+ }
93
+ }
94
+
95
+ return {
96
+ context: {
97
+ apiId: 'offlineContext_apiId',
98
+ authorizer,
99
+ httpMethod: this.#request.method.toUpperCase(),
100
+ identity: {
101
+ accountId: 'offlineContext_accountId',
102
+ apiKey: 'offlineContext_apiKey',
103
+ apiKeyId: 'offlineContext_apiKeyId',
104
+ caller: 'offlineContext_caller',
105
+ cognitoAuthenticationProvider:
106
+ 'offlineContext_cognitoAuthenticationProvider',
107
+ cognitoAuthenticationType: 'offlineContext_cognitoAuthenticationType',
108
+ sourceIp: this.#request.info.remoteAddress,
109
+ user: 'offlineContext_user',
110
+ userAgent: this.#request.headers['user-agent'] || '',
111
+ userArn: 'offlineContext_userArn',
112
+ },
113
+ requestId: createUniqueId(),
114
+ resourceId: 'offlineContext_resourceId',
115
+ resourcePath: this.#path,
116
+ stage: this.#stage,
117
+ },
118
+ input: {
119
+ body: this.#payload, // Not a string yet, todo
120
+ json: (x) => stringify(path(x)),
121
+ params: (x) =>
122
+ typeof x === 'string'
123
+ ? this.#request.params[x] || this.#request.query[x] || headers[x]
124
+ : {
125
+ header: headers,
126
+ path: {
127
+ ...this.#request.params,
128
+ },
129
+ querystring: {
130
+ ...this.#request.query,
131
+ },
132
+ },
133
+ path,
134
+ },
135
+ util: {
136
+ base64Decode: (x) =>
137
+ Buffer.from(x.toString(), 'base64').toString('binary'),
138
+ base64Encode: (x) =>
139
+ Buffer.from(x.toString(), 'binary').toString('base64'),
140
+ escapeJavaScript,
141
+ parseJson: parse,
142
+ urlDecode: (x) => decodeURIComponent(x.replace(/\+/g, ' ')),
143
+ urlEncode: encodeURI,
144
+ },
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,4 @@
1
+ export { default as LambdaIntegrationEvent } from './LambdaIntegrationEvent.js'
2
+ export { default as LambdaProxyIntegrationEvent } from './LambdaProxyIntegrationEvent.js'
3
+ export { default as renderVelocityTemplateObject } from './renderVelocityTemplateObject.js'
4
+ export { default as VelocityContext } from './VelocityContext.js'
@@ -0,0 +1,93 @@
1
+ import { log } from '@serverless/utils/log.js'
2
+ import velocityjs from 'velocityjs'
3
+ import runInPollutedScope from '../javaHelpers.js'
4
+ import { isPlainObject } from '../../../utils/index.js'
5
+
6
+ const { parse } = JSON
7
+ const { entries } = Object
8
+
9
+ function tryToParseJSON(string) {
10
+ let parsed
11
+ try {
12
+ parsed = parse(string)
13
+ } catch {
14
+ // nothing! Some things are not meant to be parsed.
15
+ }
16
+
17
+ return parsed || string
18
+ }
19
+
20
+ function renderVelocityString(velocityString, context) {
21
+ // runs in a "polluted" (extended) String.prototype replacement scope
22
+ const renderResult = runInPollutedScope(() =>
23
+ // This line can throw, but this function does not handle errors
24
+ // Quick args explanation:
25
+ // { escape: false } --> otherwise would escape &, < and > chars with html (&amp;, &lt; and &gt;)
26
+ // render(context, null, true) --> null: no custom macros; true: silent mode, just like APIG
27
+ new velocityjs.Compile(velocityjs.parse(velocityString), {
28
+ escape: false,
29
+ }).render(context, null, true),
30
+ )
31
+
32
+ log.debug('Velocity rendered:', renderResult || 'undefined')
33
+
34
+ // Haaaa Velocity... this language sure loves strings a lot
35
+ switch (renderResult) {
36
+ case 'undefined':
37
+ return undefined // But we don't, we want JavaScript types
38
+
39
+ case 'null':
40
+ return null
41
+
42
+ case 'true':
43
+ return true
44
+
45
+ case 'false':
46
+ return false
47
+
48
+ default:
49
+ return tryToParseJSON(renderResult)
50
+ }
51
+ }
52
+
53
+ /*
54
+ Deeply traverses a Serverless-style JSON (Velocity) template
55
+ When it finds a string, assumes it's Velocity language and renders it.
56
+ */
57
+ export default function renderVelocityTemplateObject(templateObject, context) {
58
+ const result = {}
59
+
60
+ let toProcess = templateObject
61
+
62
+ // In some projects, the template object is a string, let us see if it's JSON
63
+ if (typeof toProcess === 'string') {
64
+ toProcess = tryToParseJSON(toProcess)
65
+ }
66
+
67
+ // Let's check again
68
+ if (isPlainObject(toProcess)) {
69
+ entries(toProcess).forEach(([key, value]) => {
70
+ log.debug('Processing key:', key, '- value:', value)
71
+
72
+ if (typeof value === 'string') {
73
+ result[key] = renderVelocityString(value, context)
74
+ // Go deeper
75
+ } else if (isPlainObject(value)) {
76
+ result[key] = renderVelocityTemplateObject(value, context)
77
+ // This should never happen: value should either be a string or a plain object
78
+ } else {
79
+ result[key] = value
80
+ }
81
+ })
82
+ // Still a string? Maybe it's some complex Velocity stuff
83
+ } else if (typeof toProcess === 'string') {
84
+ // If the plugin threw here then you should consider reviewing your template or posting an issue.
85
+ const alternativeResult = tryToParseJSON(
86
+ renderVelocityString(toProcess, context),
87
+ )
88
+
89
+ return isPlainObject(alternativeResult) ? alternativeResult : result
90
+ }
91
+
92
+ return result
93
+ }