serverless-offline 10.1.0 → 10.2.1
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/package.json +4 -4
- package/src/ServerlessOffline.js +7 -5
- package/src/events/http/HttpServer.js +7 -4
- package/src/events/http/createJWTAuthScheme.js +5 -9
- package/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js +3 -3
- package/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +3 -3
- package/src/events/http/lambda-events/VelocityContext.js +2 -5
- package/src/events/websocket/HttpServer.js +21 -1
- package/src/events/websocket/WebSocket.js +23 -3
- package/src/events/websocket/WebSocketServer.js +6 -1
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": "10.1
|
|
4
|
+
"version": "10.2.1",
|
|
5
5
|
"description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
@@ -86,16 +86,16 @@
|
|
|
86
86
|
"@hapi/h2o2": "^9.1.0",
|
|
87
87
|
"@hapi/hapi": "^20.2.2",
|
|
88
88
|
"@serverless/utils": "^6.7.0",
|
|
89
|
-
"aws-sdk": "^2.
|
|
89
|
+
"aws-sdk": "^2.1222.0",
|
|
90
90
|
"boxen": "^7.0.0",
|
|
91
91
|
"chalk": "^5.0.1",
|
|
92
92
|
"execa": "^6.1.0",
|
|
93
93
|
"fs-extra": "^10.1.0",
|
|
94
94
|
"java-invoke-local": "0.0.6",
|
|
95
|
+
"jose": "^4.9.3",
|
|
95
96
|
"js-string-escape": "^1.0.1",
|
|
96
97
|
"jsonpath-plus": "^7.2.0",
|
|
97
98
|
"jsonschema": "^1.4.1",
|
|
98
|
-
"jsonwebtoken": "^8.5.1",
|
|
99
99
|
"jszip": "^3.10.1",
|
|
100
100
|
"luxon": "^3.0.3",
|
|
101
101
|
"node-fetch": "^3.2.10",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"p-memoize": "^7.1.0",
|
|
105
105
|
"p-retry": "^5.1.1",
|
|
106
106
|
"velocityjs": "^2.0.6",
|
|
107
|
-
"ws": "^8.
|
|
107
|
+
"ws": "^8.9.0"
|
|
108
108
|
},
|
|
109
109
|
"devDependencies": {
|
|
110
110
|
"@istanbuljs/esm-loader-hook": "^0.2.0",
|
package/src/ServerlessOffline.js
CHANGED
|
@@ -65,9 +65,9 @@ export default class ServerlessOffline {
|
|
|
65
65
|
const { httpEvents, lambdas, scheduleEvents, webSocketEvents } =
|
|
66
66
|
this.#getEvents()
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
if (lambdas.length > 0) {
|
|
69
|
+
await this.#createLambda(lambdas)
|
|
70
|
+
}
|
|
71
71
|
|
|
72
72
|
const eventModules = []
|
|
73
73
|
|
|
@@ -206,9 +206,11 @@ export default class ServerlessOffline {
|
|
|
206
206
|
this.#lambda,
|
|
207
207
|
)
|
|
208
208
|
|
|
209
|
+
await this.#webSocket.createServer()
|
|
210
|
+
|
|
209
211
|
this.#webSocket.create(events)
|
|
210
212
|
|
|
211
|
-
|
|
213
|
+
await this.#webSocket.start()
|
|
212
214
|
}
|
|
213
215
|
|
|
214
216
|
#mergeOptions() {
|
|
@@ -335,7 +337,7 @@ export default class ServerlessOffline {
|
|
|
335
337
|
}
|
|
336
338
|
}
|
|
337
339
|
|
|
338
|
-
if (http
|
|
340
|
+
if (http?.private) {
|
|
339
341
|
hasPrivateHttpEvent = true
|
|
340
342
|
}
|
|
341
343
|
|
|
@@ -440,7 +440,7 @@ export default class HttpServer {
|
|
|
440
440
|
!this.#apiKeysValues.has(apiKey)
|
|
441
441
|
) {
|
|
442
442
|
log.debug(
|
|
443
|
-
`Method ${method} of function ${functionKey} token ${apiKey} not valid
|
|
443
|
+
`Method '${method}' of function '${functionKey}' token '${apiKey}' not valid.`,
|
|
444
444
|
)
|
|
445
445
|
|
|
446
446
|
return errorResponse()
|
|
@@ -452,15 +452,18 @@ export default class HttpServer {
|
|
|
452
452
|
) {
|
|
453
453
|
const { usageIdentifierKey } = request.auth.credentials
|
|
454
454
|
|
|
455
|
-
if (
|
|
455
|
+
if (
|
|
456
|
+
usageIdentifierKey !== this.#options.apiKey &&
|
|
457
|
+
!this.#apiKeysValues.has(usageIdentifierKey)
|
|
458
|
+
) {
|
|
456
459
|
log.debug(
|
|
457
|
-
`Method ${method} of function ${functionKey} token ${usageIdentifierKey} not valid
|
|
460
|
+
`Method '${method}' of function '${functionKey}' token '${usageIdentifierKey}' not valid.`,
|
|
458
461
|
)
|
|
459
462
|
|
|
460
463
|
return errorResponse()
|
|
461
464
|
}
|
|
462
465
|
} else {
|
|
463
|
-
log.debug(`Missing x-api-key on private function ${functionKey}
|
|
466
|
+
log.debug(`Missing 'x-api-key' on private function '${functionKey}'.`)
|
|
464
467
|
|
|
465
468
|
return errorResponse()
|
|
466
469
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Boom from '@hapi/boom'
|
|
2
2
|
import { log } from '@serverless/utils/log.js'
|
|
3
|
-
import {
|
|
3
|
+
import { decodeJwt } from 'jose'
|
|
4
4
|
|
|
5
5
|
const { isArray } = Array
|
|
6
6
|
|
|
@@ -35,18 +35,14 @@ export default function createAuthScheme(jwtOptions) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
try {
|
|
38
|
-
const
|
|
39
|
-
if (!decoded) {
|
|
40
|
-
return Boom.unauthorized('JWT not decoded')
|
|
41
|
-
}
|
|
38
|
+
const claims = decodeJwt(jwtToken)
|
|
42
39
|
|
|
43
|
-
const expirationDate = new Date(
|
|
40
|
+
const expirationDate = new Date(claims.exp * 1000)
|
|
44
41
|
if (expirationDate.valueOf() < Date.now()) {
|
|
45
42
|
return Boom.unauthorized('JWT Token expired')
|
|
46
43
|
}
|
|
47
44
|
|
|
48
|
-
const { aud, iss, scope } =
|
|
49
|
-
const clientId = decoded.payload.client_id
|
|
45
|
+
const { aud, iss, scope, client_id: clientId } = claims
|
|
50
46
|
if (iss !== jwtOptions.issuerUrl) {
|
|
51
47
|
log.notice(`JWT Token not from correct issuer url`)
|
|
52
48
|
|
|
@@ -91,7 +87,7 @@ export default function createAuthScheme(jwtOptions) {
|
|
|
91
87
|
// return resolve(
|
|
92
88
|
return h.authenticated({
|
|
93
89
|
credentials: {
|
|
94
|
-
claims
|
|
90
|
+
claims,
|
|
95
91
|
scopes,
|
|
96
92
|
},
|
|
97
93
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer'
|
|
2
2
|
import { env } from 'node:process'
|
|
3
3
|
import { log } from '@serverless/utils/log.js'
|
|
4
|
-
import {
|
|
4
|
+
import { decodeJwt } from 'jose'
|
|
5
5
|
import {
|
|
6
6
|
createUniqueId,
|
|
7
7
|
formatToClfTime,
|
|
@@ -122,8 +122,8 @@ export default class LambdaProxyIntegrationEvent {
|
|
|
122
122
|
|
|
123
123
|
if (token) {
|
|
124
124
|
try {
|
|
125
|
-
claims =
|
|
126
|
-
if (claims
|
|
125
|
+
claims = decodeJwt(token)
|
|
126
|
+
if (claims.scope) {
|
|
127
127
|
scopes = claims.scope.split(' ')
|
|
128
128
|
// In AWS HTTP Api the scope property is removed from the decoded JWT
|
|
129
129
|
// I'm leaving this property because I'm not sure how all of the authorizers
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer'
|
|
2
2
|
import { env } from 'node:process'
|
|
3
3
|
import { log } from '@serverless/utils/log.js'
|
|
4
|
-
import {
|
|
4
|
+
import { decodeJwt } from 'jose'
|
|
5
5
|
import {
|
|
6
6
|
formatToClfTime,
|
|
7
7
|
lowerCaseKeys,
|
|
@@ -105,8 +105,8 @@ export default class LambdaProxyIntegrationEventV2 {
|
|
|
105
105
|
|
|
106
106
|
if (token) {
|
|
107
107
|
try {
|
|
108
|
-
claims =
|
|
109
|
-
if (claims
|
|
108
|
+
claims = decodeJwt(token)
|
|
109
|
+
if (claims.scope) {
|
|
110
110
|
scopes = claims.scope.split(' ')
|
|
111
111
|
// In AWS HTTP Api the scope property is removed from the decoded JWT
|
|
112
112
|
// I'm leaving this property because I'm not sure how all of the authorizers
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer'
|
|
2
2
|
import { env } from 'node:process'
|
|
3
3
|
import jsEscapeString from 'js-string-escape'
|
|
4
|
-
import {
|
|
4
|
+
import { decodeJwt } from 'jose'
|
|
5
5
|
import {
|
|
6
6
|
createUniqueId,
|
|
7
7
|
isPlainObject,
|
|
@@ -83,10 +83,7 @@ export default class VelocityContext {
|
|
|
83
83
|
|
|
84
84
|
if (token) {
|
|
85
85
|
try {
|
|
86
|
-
|
|
87
|
-
if (claims) {
|
|
88
|
-
assign(authorizer, { claims })
|
|
89
|
-
}
|
|
86
|
+
assign(authorizer, { claims: decodeJwt(token) })
|
|
90
87
|
} catch {
|
|
91
88
|
// Nothing
|
|
92
89
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
1
3
|
import { exit } from 'node:process'
|
|
2
4
|
import { Server } from '@hapi/hapi'
|
|
3
5
|
import { log } from '@serverless/utils/log.js'
|
|
@@ -13,8 +15,22 @@ export default class HttpServer {
|
|
|
13
15
|
constructor(options, webSocketClients) {
|
|
14
16
|
this.#options = options
|
|
15
17
|
this.#webSocketClients = webSocketClients
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async #loadCerts(httpsProtocol) {
|
|
21
|
+
const [cert, key] = await Promise.all([
|
|
22
|
+
readFile(resolve(httpsProtocol, 'cert.pem'), 'utf-8'),
|
|
23
|
+
readFile(resolve(httpsProtocol, 'key.pem'), 'utf-8'),
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
cert,
|
|
28
|
+
key,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
16
31
|
|
|
17
|
-
|
|
32
|
+
async createServer() {
|
|
33
|
+
const { host, httpsProtocol, websocketPort } = this.#options
|
|
18
34
|
|
|
19
35
|
const serverOptions = {
|
|
20
36
|
host,
|
|
@@ -24,6 +40,10 @@ export default class HttpServer {
|
|
|
24
40
|
// e.g. : /my-path is the same as /my-path/
|
|
25
41
|
stripTrailingSlash: true,
|
|
26
42
|
},
|
|
43
|
+
// https support
|
|
44
|
+
...(httpsProtocol != null && {
|
|
45
|
+
tls: await this.#loadCerts(httpsProtocol),
|
|
46
|
+
}),
|
|
27
47
|
}
|
|
28
48
|
|
|
29
49
|
this.#server = new Server(serverOptions)
|
|
@@ -6,19 +6,39 @@ import WebSocketServer from './WebSocketServer.js'
|
|
|
6
6
|
export default class WebSocket {
|
|
7
7
|
#httpServer = null
|
|
8
8
|
|
|
9
|
+
#lambda = null
|
|
10
|
+
|
|
11
|
+
#options = null
|
|
12
|
+
|
|
13
|
+
#serverless = null
|
|
14
|
+
|
|
9
15
|
#webSocketServer = null
|
|
10
16
|
|
|
11
17
|
constructor(serverless, options, lambda) {
|
|
12
|
-
|
|
18
|
+
this.#lambda = lambda
|
|
19
|
+
this.#options = options
|
|
20
|
+
this.#serverless = serverless
|
|
21
|
+
}
|
|
13
22
|
|
|
14
|
-
|
|
23
|
+
async createServer() {
|
|
24
|
+
const webSocketClients = new WebSocketClients(
|
|
25
|
+
this.#serverless,
|
|
26
|
+
this.#options,
|
|
27
|
+
this.#lambda,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
this.#httpServer = new HttpServer(this.#options, webSocketClients)
|
|
31
|
+
|
|
32
|
+
await this.#httpServer.createServer()
|
|
15
33
|
|
|
16
34
|
// share server
|
|
17
35
|
this.#webSocketServer = new WebSocketServer(
|
|
18
|
-
options,
|
|
36
|
+
this.#options,
|
|
19
37
|
webSocketClients,
|
|
20
38
|
this.#httpServer.server,
|
|
21
39
|
)
|
|
40
|
+
|
|
41
|
+
await this.#webSocketServer.createServer()
|
|
22
42
|
}
|
|
23
43
|
|
|
24
44
|
start() {
|
|
@@ -7,14 +7,19 @@ export default class WebSocketServer {
|
|
|
7
7
|
|
|
8
8
|
#options = null
|
|
9
9
|
|
|
10
|
+
#sharedServer = null
|
|
11
|
+
|
|
10
12
|
#webSocketClients = null
|
|
11
13
|
|
|
12
14
|
constructor(options, webSocketClients, sharedServer) {
|
|
13
15
|
this.#options = options
|
|
16
|
+
this.#sharedServer = sharedServer
|
|
14
17
|
this.#webSocketClients = webSocketClients
|
|
18
|
+
}
|
|
15
19
|
|
|
20
|
+
async createServer() {
|
|
16
21
|
const server = new WsWebSocketServer({
|
|
17
|
-
server: sharedServer,
|
|
22
|
+
server: this.#sharedServer,
|
|
18
23
|
verifyClient: async ({ req }, cb) => {
|
|
19
24
|
const connectionId = createUniqueId()
|
|
20
25
|
const key = req.headers['sec-websocket-key']
|