serverless-offline 11.2.3 → 11.4.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.
- package/README.md +10 -4
- package/package.json +11 -11
- package/src/events/http/HttpServer.js +38 -3
- package/src/events/http/createAuthScheme.js +248 -127
- package/src/utils/getRawQueryParams.js +11 -0
- package/src/utils/index.js +1 -0
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ This plugin is updated by its users, I just do maintenance and ensure that PRs a
|
|
|
43
43
|
- [Installation](#installation)
|
|
44
44
|
- [Usage and command line options](#usage-and-command-line-options)
|
|
45
45
|
- [Run modes](#run-modes)
|
|
46
|
-
- [
|
|
46
|
+
- [Invoke Lambda](#invoke-lambda)
|
|
47
47
|
- [The `process.env.IS_OFFLINE` variable](#the-processenvis_offline-variable)
|
|
48
48
|
- [Docker and Layers](#docker-and-layers)
|
|
49
49
|
- [Authorizers](#authorizers)
|
|
@@ -295,7 +295,7 @@ Lambda handlers for the `node.js` runtime can run in different execution modes w
|
|
|
295
295
|
|
|
296
296
|
the Lambda handler process is running in a child process.
|
|
297
297
|
|
|
298
|
-
##
|
|
298
|
+
## Invoke Lambda
|
|
299
299
|
|
|
300
300
|
To use `Lambda.invoke` you need to set the lambda endpoint to the `serverless-offline` endpoint:
|
|
301
301
|
|
|
@@ -326,14 +326,20 @@ const lambda = new aws.Lambda({
|
|
|
326
326
|
})
|
|
327
327
|
|
|
328
328
|
export async function handler() {
|
|
329
|
-
const clientContextData = stringify({
|
|
329
|
+
const clientContextData = stringify({
|
|
330
|
+
foo: 'foo',
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
const payload = stringify({
|
|
334
|
+
data: 'foo',
|
|
335
|
+
})
|
|
330
336
|
|
|
331
337
|
const params = {
|
|
332
338
|
ClientContext: Buffer.from(clientContextData).toString('base64'),
|
|
333
339
|
// FunctionName is composed of: service name - stage - function name, e.g.
|
|
334
340
|
FunctionName: 'myServiceName-dev-invokedHandler',
|
|
335
341
|
InvocationType: 'RequestResponse',
|
|
336
|
-
Payload:
|
|
342
|
+
Payload: payload,
|
|
337
343
|
}
|
|
338
344
|
|
|
339
345
|
const response = await lambda.invoke(params).promise()
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dedicatedTo": "Blue, a great migrating bird.",
|
|
3
3
|
"name": "serverless-offline",
|
|
4
|
-
"version": "11.
|
|
4
|
+
"version": "11.4.0",
|
|
5
5
|
"description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
@@ -82,46 +82,46 @@
|
|
|
82
82
|
]
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
|
-
"@aws-sdk/client-lambda": "^3.
|
|
85
|
+
"@aws-sdk/client-lambda": "^3.210.0",
|
|
86
86
|
"@hapi/boom": "^10.0.0",
|
|
87
87
|
"@hapi/h2o2": "^10.0.0",
|
|
88
|
-
"@hapi/hapi": "^
|
|
89
|
-
"@serverless/utils": "^6.8.
|
|
88
|
+
"@hapi/hapi": "^21.0.0",
|
|
89
|
+
"@serverless/utils": "^6.8.2",
|
|
90
90
|
"boxen": "^7.0.0",
|
|
91
91
|
"chalk": "^5.1.2",
|
|
92
92
|
"execa": "^6.1.0",
|
|
93
93
|
"fs-extra": "^10.1.0",
|
|
94
94
|
"is-wsl": "^2.2.0",
|
|
95
95
|
"java-invoke-local": "0.0.6",
|
|
96
|
-
"jose": "^4.
|
|
96
|
+
"jose": "^4.11.0",
|
|
97
97
|
"js-string-escape": "^1.0.1",
|
|
98
98
|
"jsonpath-plus": "^7.2.0",
|
|
99
99
|
"jsonschema": "^1.4.1",
|
|
100
100
|
"jszip": "^3.10.1",
|
|
101
101
|
"luxon": "^3.1.0",
|
|
102
|
-
"node-fetch": "^3.
|
|
102
|
+
"node-fetch": "^3.3.0",
|
|
103
103
|
"node-schedule": "^2.1.0",
|
|
104
|
-
"object.hasown": "^1.1.
|
|
104
|
+
"object.hasown": "^1.1.2",
|
|
105
105
|
"p-memoize": "^7.1.1",
|
|
106
106
|
"p-retry": "^5.1.1",
|
|
107
107
|
"velocityjs": "^2.0.6",
|
|
108
|
-
"ws": "^8.
|
|
108
|
+
"ws": "^8.11.0"
|
|
109
109
|
},
|
|
110
110
|
"devDependencies": {
|
|
111
111
|
"@istanbuljs/esm-loader-hook": "^0.2.0",
|
|
112
112
|
"archiver": "^5.3.1",
|
|
113
|
-
"eslint": "^8.
|
|
113
|
+
"eslint": "^8.27.0",
|
|
114
114
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
115
115
|
"eslint-config-prettier": "^8.5.0",
|
|
116
116
|
"eslint-plugin-import": "^2.25.4",
|
|
117
117
|
"eslint-plugin-prettier": "^4.2.1",
|
|
118
118
|
"git-list-updated": "^1.2.1",
|
|
119
|
-
"husky": "^8.0.
|
|
119
|
+
"husky": "^8.0.2",
|
|
120
120
|
"lint-staged": "^13.0.3",
|
|
121
121
|
"mocha": "^10.1.0",
|
|
122
122
|
"nyc": "^15.1.0",
|
|
123
123
|
"prettier": "^2.7.1",
|
|
124
|
-
"serverless": "^3.
|
|
124
|
+
"serverless": "^3.24.1",
|
|
125
125
|
"standard-version": "^9.5.0"
|
|
126
126
|
},
|
|
127
127
|
"peerDependencies": {
|
|
@@ -253,6 +253,16 @@ export default class HttpServer {
|
|
|
253
253
|
return null
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
+
if (
|
|
257
|
+
(endpoint.authorizer.name &&
|
|
258
|
+
this.#serverless.service.provider?.httpApi?.authorizers?.[
|
|
259
|
+
endpoint.authorizer.name
|
|
260
|
+
]?.type === 'request') ||
|
|
261
|
+
endpoint.authorizer.type === 'request'
|
|
262
|
+
) {
|
|
263
|
+
return null
|
|
264
|
+
}
|
|
265
|
+
|
|
256
266
|
const jwtSettings = this.#extractJWTAuthSettings(endpoint)
|
|
257
267
|
if (!jwtSettings) {
|
|
258
268
|
return null
|
|
@@ -303,11 +313,36 @@ export default class HttpServer {
|
|
|
303
313
|
log.error(`Authorization function ${authFunctionName} does not exist`)
|
|
304
314
|
return null
|
|
305
315
|
}
|
|
316
|
+
const serverlessAuthorizerOptions =
|
|
317
|
+
this.#serverless.service.provider.httpApi &&
|
|
318
|
+
this.#serverless.service.provider.httpApi.authorizers &&
|
|
319
|
+
this.#serverless.service.provider.httpApi.authorizers[authFunctionName]
|
|
306
320
|
|
|
307
321
|
const authorizerOptions = {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
322
|
+
enableSimpleResponses:
|
|
323
|
+
(endpoint.isHttpApi &&
|
|
324
|
+
serverlessAuthorizerOptions?.enableSimpleResponses) ||
|
|
325
|
+
false,
|
|
326
|
+
identitySource:
|
|
327
|
+
serverlessAuthorizerOptions?.identitySource ||
|
|
328
|
+
'method.request.header.Authorization',
|
|
329
|
+
identityValidationExpression:
|
|
330
|
+
serverlessAuthorizerOptions?.identityValidationExpression || '(.*)',
|
|
331
|
+
payloadVersion: !endpoint.isHttpApi
|
|
332
|
+
? '1.0'
|
|
333
|
+
: serverlessAuthorizerOptions?.payloadVersion || '2.0',
|
|
334
|
+
resultTtlInSeconds:
|
|
335
|
+
serverlessAuthorizerOptions?.resultTtlInSeconds || '300',
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (
|
|
339
|
+
authorizerOptions.enableSimpleResponses &&
|
|
340
|
+
authorizerOptions.payloadVersion === '1.0'
|
|
341
|
+
) {
|
|
342
|
+
log.error(
|
|
343
|
+
`Cannot create Authorization function '${authFunctionName}' if payloadVersion is '1.0' and enableSimpleResponses is true`,
|
|
344
|
+
)
|
|
345
|
+
return null
|
|
311
346
|
}
|
|
312
347
|
|
|
313
348
|
if (typeof endpoint.authorizer === 'string') {
|
|
@@ -3,6 +3,7 @@ import { log } from '@serverless/utils/log.js'
|
|
|
3
3
|
import authCanExecuteResource from '../authCanExecuteResource.js'
|
|
4
4
|
import authValidateContext from '../authValidateContext.js'
|
|
5
5
|
import {
|
|
6
|
+
getRawQueryParams,
|
|
6
7
|
nullIfEmpty,
|
|
7
8
|
parseHeaders,
|
|
8
9
|
parseMultiValueHeaders,
|
|
@@ -10,83 +11,77 @@ import {
|
|
|
10
11
|
parseQueryStringParameters,
|
|
11
12
|
} from '../../utils/index.js'
|
|
12
13
|
|
|
14
|
+
const IDENTITY_SOURCE_TYPE_HEADER = 'header'
|
|
15
|
+
const IDENTITY_SOURCE_TYPE_QUERYSTRING = 'querystring'
|
|
16
|
+
|
|
13
17
|
export default function createAuthScheme(authorizerOptions, provider, lambda) {
|
|
14
18
|
const authFunName = authorizerOptions.name
|
|
15
|
-
let
|
|
19
|
+
let identitySourceField = 'authorization'
|
|
20
|
+
let identitySourceType = IDENTITY_SOURCE_TYPE_HEADER
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
const finalizeAuthScheme = () => {
|
|
23
|
+
return () => ({
|
|
24
|
+
async authenticate(request, h) {
|
|
25
|
+
log.notice()
|
|
26
|
+
log.notice(
|
|
27
|
+
`Running Authorization function for ${request.method} ${request.path} (λ: ${authFunName})`,
|
|
28
|
+
)
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
throw new Error(
|
|
24
|
-
`Serverless Offline only supports retrieving tokens from the headers (λ: ${authFunName})`,
|
|
25
|
-
)
|
|
26
|
-
}
|
|
30
|
+
const { rawHeaders, url } = request.raw.req
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
// Get path params
|
|
33
|
+
// aws doesn't auto decode path params - hapi does
|
|
34
|
+
const pathParams = { ...request.params }
|
|
35
|
+
|
|
36
|
+
const accountId = 'random-account-id'
|
|
37
|
+
const apiId = 'random-api-id'
|
|
38
|
+
const requestId = 'random-request-id'
|
|
39
|
+
|
|
40
|
+
const httpMethod = request.method.toUpperCase()
|
|
41
|
+
const resourcePath = request.route.path.replace(
|
|
42
|
+
new RegExp(`^/${provider.stage}`),
|
|
43
|
+
'',
|
|
44
|
+
)
|
|
30
45
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
async authenticate(request, h) {
|
|
34
|
-
log.notice()
|
|
35
|
-
log.notice(
|
|
36
|
-
`Running Authorization function for ${request.method} ${request.path} (λ: ${authFunName})`,
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
// Get Authorization header
|
|
40
|
-
const { req } = request.raw
|
|
41
|
-
|
|
42
|
-
// Get path params
|
|
43
|
-
// aws doesn't auto decode path params - hapi does
|
|
44
|
-
const pathParams = { ...request.params }
|
|
45
|
-
|
|
46
|
-
const accountId = 'random-account-id'
|
|
47
|
-
const apiId = 'random-api-id'
|
|
48
|
-
const httpMethod = request.method.toUpperCase()
|
|
49
|
-
const resourcePath = request.route.path.replace(
|
|
50
|
-
new RegExp(`^/${provider.stage}`),
|
|
51
|
-
'',
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
let event = {
|
|
55
|
-
enhancedAuthContext: {},
|
|
56
|
-
methodArn: `arn:aws:execute-api:${provider.region}:${accountId}:${apiId}/${provider.stage}/${httpMethod}${resourcePath}`,
|
|
57
|
-
requestContext: {
|
|
58
|
-
accountId,
|
|
59
|
-
apiId,
|
|
60
|
-
httpMethod,
|
|
61
|
-
path: request.path,
|
|
62
|
-
requestId: 'random-request-id',
|
|
63
|
-
resourceId: 'random-resource-id',
|
|
64
|
-
resourcePath,
|
|
65
|
-
stage: provider.stage,
|
|
66
|
-
},
|
|
67
|
-
resource: resourcePath,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Create event Object for authFunction
|
|
71
|
-
// methodArn is the ARN of the function we are running we are authorizing access to (or not)
|
|
72
|
-
// Account ID and API ID are not simulated
|
|
73
|
-
if (authorizerOptions.type === 'request') {
|
|
74
|
-
const { rawHeaders, url } = req
|
|
75
|
-
|
|
76
|
-
event = {
|
|
77
|
-
...event,
|
|
46
|
+
let event = {
|
|
47
|
+
enhancedAuthContext: {},
|
|
78
48
|
headers: parseHeaders(rawHeaders),
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
49
|
+
requestContext: {
|
|
50
|
+
accountId,
|
|
51
|
+
apiId,
|
|
52
|
+
domainName: `${apiId}.execute-api.us-east-1.amazonaws.com`,
|
|
53
|
+
domainPrefix: apiId,
|
|
54
|
+
requestId,
|
|
55
|
+
stage: provider.stage,
|
|
56
|
+
},
|
|
57
|
+
version: authorizerOptions.payloadVersion,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const protocol = `${request.server.info.protocol.toUpperCase()}/${
|
|
61
|
+
request.raw.req.httpVersion
|
|
62
|
+
}`
|
|
63
|
+
const currentDate = new Date()
|
|
64
|
+
const resourceId = `${httpMethod} ${resourcePath}`
|
|
65
|
+
const methodArn = `arn:aws:execute-api:${provider.region}:${accountId}:${apiId}/${provider.stage}/${httpMethod}${resourcePath}`
|
|
66
|
+
|
|
67
|
+
let authorization
|
|
68
|
+
if (identitySourceType === IDENTITY_SOURCE_TYPE_HEADER) {
|
|
69
|
+
const headers = request.raw.req.headers ?? {}
|
|
70
|
+
authorization = headers[identitySourceField]
|
|
71
|
+
} else if (identitySourceType === IDENTITY_SOURCE_TYPE_QUERYSTRING) {
|
|
72
|
+
const queryStringParameters = parseQueryStringParameters(url) ?? {}
|
|
73
|
+
authorization = queryStringParameters[identitySourceField]
|
|
74
|
+
} else {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`No Authorization source has been specified. This should never happen. (λ: ${authFunName})`,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (authorization === undefined) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Identity Source is null for ${identitySourceType} ${identitySourceField} (λ: ${authFunName})`,
|
|
83
|
+
)
|
|
87
84
|
}
|
|
88
|
-
} else {
|
|
89
|
-
const authorization = req.headers[identityHeader]
|
|
90
85
|
|
|
91
86
|
const identityValidationExpression = new RegExp(
|
|
92
87
|
authorizerOptions.identityValidationExpression,
|
|
@@ -95,82 +90,208 @@ export default function createAuthScheme(authorizerOptions, provider, lambda) {
|
|
|
95
90
|
identityValidationExpression.test(authorization)
|
|
96
91
|
const finalAuthorization = matchedAuthorization ? authorization : ''
|
|
97
92
|
|
|
98
|
-
log.debug(
|
|
93
|
+
log.debug(
|
|
94
|
+
`Retrieved ${identitySourceField} ${identitySourceType} "${finalAuthorization}"`,
|
|
95
|
+
)
|
|
99
96
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
if (authorizerOptions.payloadVersion === '1.0') {
|
|
98
|
+
event = {
|
|
99
|
+
...event,
|
|
100
|
+
authorizationToken: finalAuthorization,
|
|
101
|
+
httpMethod: request.method.toUpperCase(),
|
|
102
|
+
identitySource: finalAuthorization,
|
|
103
|
+
methodArn,
|
|
104
|
+
multiValueHeaders: parseMultiValueHeaders(rawHeaders),
|
|
105
|
+
multiValueQueryStringParameters:
|
|
106
|
+
parseMultiValueQueryStringParameters(url),
|
|
107
|
+
path: request.path,
|
|
108
|
+
pathParameters: nullIfEmpty(pathParams),
|
|
109
|
+
queryStringParameters: parseQueryStringParameters(url),
|
|
110
|
+
requestContext: {
|
|
111
|
+
extendedRequestId: requestId,
|
|
112
|
+
httpMethod,
|
|
113
|
+
path: request.path,
|
|
114
|
+
protocol,
|
|
115
|
+
requestTime: currentDate.toString(),
|
|
116
|
+
requestTimeEpoch: currentDate.getTime(),
|
|
117
|
+
resourceId,
|
|
118
|
+
resourcePath,
|
|
119
|
+
stage: provider.stage,
|
|
120
|
+
},
|
|
121
|
+
resource: resourcePath,
|
|
122
|
+
}
|
|
104
123
|
}
|
|
105
|
-
}
|
|
106
124
|
|
|
107
|
-
|
|
108
|
-
|
|
125
|
+
if (authorizerOptions.payloadVersion === '2.0') {
|
|
126
|
+
event = {
|
|
127
|
+
...event,
|
|
128
|
+
identitySource: [finalAuthorization],
|
|
129
|
+
rawPath: request.path,
|
|
130
|
+
rawQueryString: getRawQueryParams(url),
|
|
131
|
+
requestContext: {
|
|
132
|
+
http: {
|
|
133
|
+
method: httpMethod,
|
|
134
|
+
path: resourcePath,
|
|
135
|
+
protocol,
|
|
136
|
+
},
|
|
137
|
+
routeKey: resourceId,
|
|
138
|
+
time: currentDate.toString(),
|
|
139
|
+
timeEpoch: currentDate.getTime(),
|
|
140
|
+
},
|
|
141
|
+
routeArn: methodArn,
|
|
142
|
+
routeKey: resourceId,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
109
145
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (
|
|
146
|
+
// methodArn is the ARN of the function we are running we are authorizing access to (or not)
|
|
147
|
+
// Account ID and API ID are not simulated
|
|
148
|
+
if (authorizerOptions.type === 'request') {
|
|
149
|
+
event = {
|
|
150
|
+
...event,
|
|
151
|
+
type: 'REQUEST',
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
// This is safe since type: 'TOKEN' cannot have payload format 2.0
|
|
155
|
+
event = {
|
|
156
|
+
...event,
|
|
157
|
+
type: 'TOKEN',
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const lambdaFunction = lambda.get(authFunName)
|
|
162
|
+
lambdaFunction.setEvent(event)
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const result = await lambdaFunction.runHandler()
|
|
166
|
+
|
|
167
|
+
if (authorizerOptions.enableSimpleResponses) {
|
|
168
|
+
if (result.isAuthorized) {
|
|
169
|
+
const authorizer = {
|
|
170
|
+
integrationLatency: '42',
|
|
171
|
+
...result.context,
|
|
172
|
+
}
|
|
173
|
+
return h.authenticated({
|
|
174
|
+
credentials: {
|
|
175
|
+
authorizer,
|
|
176
|
+
context: result.context || {},
|
|
177
|
+
},
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
return Boom.forbidden(
|
|
181
|
+
'User is not authorized to access this resource',
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (result === 'Unauthorized')
|
|
186
|
+
return Boom.unauthorized('Unauthorized')
|
|
187
|
+
|
|
188
|
+
// Validate that the policy document has the principalId set
|
|
189
|
+
if (!result.principalId) {
|
|
190
|
+
log.notice(
|
|
191
|
+
`Authorization response did not include a principalId: (λ: ${authFunName})`,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return Boom.forbidden('No principalId set on the Response')
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (
|
|
198
|
+
!authCanExecuteResource(
|
|
199
|
+
result.policyDocument,
|
|
200
|
+
event.methodArn || event.routeArn,
|
|
201
|
+
)
|
|
202
|
+
) {
|
|
203
|
+
log.notice(
|
|
204
|
+
`Authorization response didn't authorize user to access resource: (λ: ${authFunName})`,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return Boom.forbidden(
|
|
208
|
+
'User is not authorized to access this resource',
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// validate the resulting context, ensuring that all
|
|
213
|
+
// values are either string, number, or boolean types
|
|
214
|
+
if (result.context) {
|
|
215
|
+
const validationResult = authValidateContext(
|
|
216
|
+
result.context,
|
|
217
|
+
authFunName,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if (validationResult instanceof Error) {
|
|
221
|
+
return validationResult
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
result.context = validationResult
|
|
225
|
+
}
|
|
113
226
|
|
|
114
|
-
// Validate that the policy document has the principalId set
|
|
115
|
-
if (!result.principalId) {
|
|
116
227
|
log.notice(
|
|
117
|
-
`Authorization
|
|
228
|
+
`Authorization function returned a successful response: (λ: ${authFunName})`,
|
|
118
229
|
)
|
|
119
230
|
|
|
120
|
-
|
|
121
|
-
|
|
231
|
+
const authorizer = {
|
|
232
|
+
integrationLatency: '42',
|
|
233
|
+
principalId: result.principalId,
|
|
234
|
+
...result.context,
|
|
235
|
+
}
|
|
122
236
|
|
|
123
|
-
|
|
237
|
+
// Set the credentials for the rest of the pipeline
|
|
238
|
+
return h.authenticated({
|
|
239
|
+
credentials: {
|
|
240
|
+
authorizer,
|
|
241
|
+
context: result.context,
|
|
242
|
+
principalId: result.principalId,
|
|
243
|
+
usageIdentifierKey: result.usageIdentifierKey,
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
} catch {
|
|
124
247
|
log.notice(
|
|
125
|
-
`Authorization
|
|
248
|
+
`Authorization function returned an error response: (λ: ${authFunName})`,
|
|
126
249
|
)
|
|
127
250
|
|
|
128
|
-
return Boom.
|
|
129
|
-
'User is not authorized to access this resource',
|
|
130
|
-
)
|
|
251
|
+
return Boom.unauthorized('Unauthorized')
|
|
131
252
|
}
|
|
253
|
+
},
|
|
254
|
+
})
|
|
255
|
+
}
|
|
132
256
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (result.context) {
|
|
136
|
-
const validationResult = authValidateContext(
|
|
137
|
-
result.context,
|
|
138
|
-
authFunName,
|
|
139
|
-
)
|
|
257
|
+
const checkForIdentitySourceMatch = (exp, expectedLength) => {
|
|
258
|
+
const identitySourceMatch = exp.exec(authorizerOptions.identitySource)
|
|
140
259
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
260
|
+
if (!identitySourceMatch || identitySourceMatch.length !== expectedLength) {
|
|
261
|
+
return undefined
|
|
262
|
+
}
|
|
263
|
+
return identitySourceMatch[expectedLength - 1]
|
|
264
|
+
}
|
|
144
265
|
|
|
145
|
-
|
|
146
|
-
|
|
266
|
+
if (
|
|
267
|
+
authorizerOptions.type !== 'request' ||
|
|
268
|
+
authorizerOptions.identitySource
|
|
269
|
+
) {
|
|
270
|
+
const headerRegExp = /^(method.|\$)request.header.((?:\w+-?)+\w+)$/
|
|
271
|
+
const queryStringRegExp =
|
|
272
|
+
/^(method.|\$)request.querystring.((?:\w+-?)+\w+)$/
|
|
147
273
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
274
|
+
const identityHeaderResult = checkForIdentitySourceMatch(headerRegExp, 3)
|
|
275
|
+
if (identityHeaderResult !== undefined) {
|
|
276
|
+
identitySourceField = identityHeaderResult.toLowerCase()
|
|
277
|
+
identitySourceType = IDENTITY_SOURCE_TYPE_HEADER
|
|
278
|
+
return finalizeAuthScheme()
|
|
279
|
+
}
|
|
151
280
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
281
|
+
const identityQueryStringResult = checkForIdentitySourceMatch(
|
|
282
|
+
queryStringRegExp,
|
|
283
|
+
3,
|
|
284
|
+
)
|
|
285
|
+
if (identityQueryStringResult !== undefined) {
|
|
286
|
+
identitySourceField = identityQueryStringResult
|
|
287
|
+
identitySourceType = IDENTITY_SOURCE_TYPE_QUERYSTRING
|
|
288
|
+
return finalizeAuthScheme()
|
|
289
|
+
}
|
|
157
290
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
context: result.context,
|
|
163
|
-
principalId: result.principalId,
|
|
164
|
-
usageIdentifierKey: result.usageIdentifierKey,
|
|
165
|
-
},
|
|
166
|
-
})
|
|
167
|
-
} catch {
|
|
168
|
-
log.notice(
|
|
169
|
-
`Authorization function returned an error response: (λ: ${authFunName})`,
|
|
170
|
-
)
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Serverless Offline only supports retrieving tokens from headers and querystring parameters (λ: ${authFunName})`,
|
|
293
|
+
)
|
|
294
|
+
}
|
|
171
295
|
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
})
|
|
296
|
+
return finalizeAuthScheme()
|
|
176
297
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import parseQueryStringParameters from './parseQueryStringParameters.js'
|
|
2
|
+
|
|
3
|
+
export default function getRawQueryParams(url) {
|
|
4
|
+
const queryParams = parseQueryStringParameters(url) || {}
|
|
5
|
+
return Object.keys(queryParams)
|
|
6
|
+
.reduce(function reducer(accumulator, currentKey) {
|
|
7
|
+
accumulator.push(`${currentKey}=${queryParams[currentKey]}`)
|
|
8
|
+
return accumulator
|
|
9
|
+
}, [])
|
|
10
|
+
.join('&')
|
|
11
|
+
}
|
package/src/utils/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export { default as formatToClfTime } from './formatToClfTime.js'
|
|
|
7
7
|
export { default as generateHapiPath } from './generateHapiPath.js'
|
|
8
8
|
export { default as getApiKeysValues } from './getApiKeysValues.js'
|
|
9
9
|
export { default as getHttpApiCorsConfig } from './getHttpApiCorsConfig.js'
|
|
10
|
+
export { default as getRawQueryParams } from './getRawQueryParams.js'
|
|
10
11
|
export { default as jsonPath } from './jsonPath.js'
|
|
11
12
|
export { default as lowerCaseKeys } from './lowerCaseKeys.js'
|
|
12
13
|
export { default as parseHeaders } from './parseHeaders.js'
|