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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "dedicatedTo": "Blue, a great migrating bird.",
3
3
  "name": "serverless-offline",
4
- "version": "10.1.0",
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.1215.0",
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.8.1"
107
+ "ws": "^8.9.0"
108
108
  },
109
109
  "devDependencies": {
110
110
  "@istanbuljs/esm-loader-hook": "^0.2.0",
@@ -65,9 +65,9 @@ export default class ServerlessOffline {
65
65
  const { httpEvents, lambdas, scheduleEvents, webSocketEvents } =
66
66
  this.#getEvents()
67
67
 
68
- // if (lambdas.length > 0) {
69
- await this.#createLambda(lambdas)
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
- return this.#webSocket.start()
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 && http.private) {
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 (usageIdentifierKey !== this.#options.apiKey) {
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 { decode } from 'jsonwebtoken'
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 decoded = decode(jwtToken, { complete: true })
39
- if (!decoded) {
40
- return Boom.unauthorized('JWT not decoded')
41
- }
38
+ const claims = decodeJwt(jwtToken)
42
39
 
43
- const expirationDate = new Date(decoded.payload.exp * 1000)
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 } = decoded.payload
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: decoded.payload,
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 { decode } from 'jsonwebtoken'
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 = decode(token) || undefined
126
- if (claims && claims.scope) {
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 { decode } from 'jsonwebtoken'
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 = decode(token) || undefined
109
- if (claims && claims.scope) {
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 { decode } from 'jsonwebtoken'
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
- const claims = decode(token) || undefined
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
- const { host, websocketPort } = options
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
- const webSocketClients = new WebSocketClients(serverless, options, lambda)
18
+ this.#lambda = lambda
19
+ this.#options = options
20
+ this.#serverless = serverless
21
+ }
13
22
 
14
- this.#httpServer = new HttpServer(options, webSocketClients)
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']