serverless-openapi-documenter 0.0.62 → 0.0.64

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 CHANGED
@@ -61,6 +61,10 @@ Options:
61
61
  #### Model Details
62
62
  * [Models](#models)
63
63
  * [Notes on Schemas](#notes-on-schemas)
64
+ * [Request Schema Validators](#serverless-request-schema-validators)
65
+ #### Response Headers
66
+ * [CORS](#cors)
67
+ * [OWASP Secure Headers](#owasp)
64
68
 
65
69
  ### OpenAPI Mapping
66
70
 
@@ -406,6 +410,76 @@ custom:
406
410
  type: string
407
411
  ```
408
412
 
413
+ ##### Serverless Request Schema Validators
414
+
415
+ As of 0.0.64, you can now make use of [Request Schema Validators](https://www.serverless.com/framework/docs/providers/aws/events/apigateway#request-schema-validators). This allows you to define Request models via the `apiGateway` settings:
416
+
417
+ ```yml
418
+ provider:
419
+ ...
420
+ apiGateway:
421
+ request:
422
+ schemas:
423
+ post-create-model:
424
+ name: PostCreateModel
425
+ schema: ${file(api_schema/post_add_schema.json)}
426
+ description: "A Model validation for adding posts"
427
+ ```
428
+
429
+ which are then used like:
430
+
431
+ ```yml
432
+ functions:
433
+ create:
434
+ handler: posts.create
435
+ events:
436
+ - http:
437
+ path: posts/create
438
+ method: post
439
+ request:
440
+ schemas:
441
+ application/json: post-create-model
442
+ documentation:
443
+ ...
444
+ ```
445
+
446
+ The generator will match to the model within the `apiGateway` settings model list. If you are using the `apiGateway` to define models, please do not re-use any names that you might define in the [`models`](#models) list.
447
+
448
+ You can also skip writing a `requestBody` and `requestModels` if you have defined a `request` property in your event.
449
+
450
+ If you're not using `apiGateway`, you can still make use of `request` by writing in the other styles that serverless accepts for Request Schema Validators:
451
+
452
+ ```yml
453
+ functions:
454
+ create:
455
+ handler: posts.create
456
+ events:
457
+ - http:
458
+ path: posts/create
459
+ method: post
460
+ request:
461
+ schemas:
462
+ application/json:
463
+ schema: ${file(create_request.json)}
464
+ name: PostCreateModel
465
+ description: 'Validation model for Creating Posts'
466
+
467
+ ```
468
+
469
+ or
470
+
471
+ ```yml
472
+ functions:
473
+ create:
474
+ handler: posts.create
475
+ events:
476
+ - http:
477
+ path: posts/create
478
+ method: post
479
+ request:
480
+ schemas:
481
+ application/json: ${file(create_request.json)}
482
+ ```
409
483
 
410
484
  #### Functions
411
485
 
@@ -729,6 +803,80 @@ You can automatically generate CORS response headers by setting `cors` at the fu
729
803
 
730
804
  The generator will interpret your settings for CORS and automatically add the response headers. If for whatever reason you wish to override these, you can set them via the above `responseHeaders` setting and it'll apply your overrides.
731
805
 
806
+ ##### OWASP
807
+
808
+ You can make use of the [OWASP Secure Headers](https://owasp.org/www-project-secure-headers/#x-permitted-cross-domain-policies) to generate response headers. These are a selection of response headers with default values that OWASP recommends returning with your response to help secure your application.
809
+
810
+ The OWASP Secure Headers Project contains a set of recommended headers to return with recommended values, when generating the documentation, the generator will attempt to get the latest version of this document and apply the latest recommendations. If you do not allow outside connections, it will default to a version of recommendations from **2023-05-26 12:22:30 UTC**.
811
+
812
+ Like CORS, if you have already set any of the OWASP Secure headers via `responseHeaders`, it will not overwrite them.
813
+
814
+ To make use of OWASP Secure Headers, you can use the following:
815
+
816
+ ###### All OWASP Secure Headers
817
+
818
+ ```yml
819
+ methodResponse:
820
+ - statusCode: 200
821
+ responseBody:
822
+ description: Success
823
+ responseModels:
824
+ application/json: "CreateResponse"
825
+ owasp: true
826
+ ```
827
+
828
+ This will use the full set of OWASP Secure Headers and their recommended values. Some of these might not be appropriate for your application.
829
+
830
+ ###### Subset of OWASP Secure Headers
831
+
832
+ ```yml
833
+ methodResponse:
834
+ - statusCode: 200
835
+ responseBody:
836
+ description: Success
837
+ responseModels:
838
+ application/json: "CreateResponse"
839
+ owasp:
840
+ cacheControl: true
841
+ referrerPolicy: true
842
+ ```
843
+
844
+ This will set only the `cacheControl` and `referrerPolicy` response header with the default recommendations.
845
+
846
+ The full list of OWASP Secure Headers you can set are:
847
+
848
+ * cacheControl - Cache-Control,
849
+ * clearSiteData - Clear-Site-Data,
850
+ * contentSecurityPolicy - Content-Security-Policy,
851
+ * crossOriginEmbedderPolicy - Cross-Origin-Embedder-Policy,
852
+ * crossOriginOpenerPolicy - Cross-Origin-Opener-Policy,
853
+ * crossOriginResourcePolicy - Cross-Origin-Resource-Policy,
854
+ * permissionsPolicy - Permissions-Policy,
855
+ * pragma - Pragma,
856
+ * referrerPolicy - Referrer-Policy,
857
+ * strictTransportSecurity - Strict-Transport-Security,
858
+ * xContentTypeOptions - X-Content-Type-Options,
859
+ * xFrameOptions - X-Frame-Options,
860
+ * xPermittedCrossDomainPolicies - X-Permitted-Cross-Domain-Policies
861
+
862
+ ###### Subset of OWASP Secure Headers with user defined values
863
+
864
+ If you wish to override the OWASP Secure Headers, you can write your `methodResponse` like:
865
+
866
+ ```yml
867
+ methodResponse:
868
+ - statusCode: 200
869
+ responseBody:
870
+ description: Success
871
+ responseModels:
872
+ application/json: "CreateResponse"
873
+ owasp:
874
+ cacheControl:
875
+ value: no-store
876
+ ```
877
+
878
+ This will set the `Cache-Control` Response Header to have a value of "no-store" rather than any value the OWASP Secure Headers Project recommends.
879
+
732
880
  ## Example configuration
733
881
 
734
882
  Please view the example [serverless.yml](test/serverless-tests/serverless%202/serverless.yml).
@@ -0,0 +1,57 @@
1
+ {
2
+ "last_update_utc": "2023-05-26 12:22:30",
3
+ "headers": [
4
+ {
5
+ "name": "Cache-Control",
6
+ "value": "no-store, max-age=0"
7
+ },
8
+ {
9
+ "name": "Clear-Site-Data",
10
+ "value": "\"cache\",\"cookies\",\"storage\""
11
+ },
12
+ {
13
+ "name": "Content-Security-Policy",
14
+ "value": "default-src 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content"
15
+ },
16
+ {
17
+ "name": "Cross-Origin-Embedder-Policy",
18
+ "value": "require-corp"
19
+ },
20
+ {
21
+ "name": "Cross-Origin-Opener-Policy",
22
+ "value": "same-origin"
23
+ },
24
+ {
25
+ "name": "Cross-Origin-Resource-Policy",
26
+ "value": "same-origin"
27
+ },
28
+ {
29
+ "name": "Permissions-Policy",
30
+ "value": "accelerometer=(),ambient-light-sensor=(),autoplay=(),battery=(),camera=(),display-capture=(),document-domain=(),encrypted-media=(),fullscreen=(),gamepad=(),geolocation=(),gyroscope=(),layout-animations=(self),legacy-image-formats=(self),magnetometer=(),microphone=(),midi=(),oversized-images=(self),payment=(),picture-in-picture=(),publickey-credentials-get=(),speaker-selection=(),sync-xhr=(self),unoptimized-images=(self),unsized-media=(self),usb=(),screen-wake-lock=(),web-share=(),xr-spatial-tracking=()"
31
+ },
32
+ {
33
+ "name": "Pragma",
34
+ "value": "no-cache"
35
+ },
36
+ {
37
+ "name": "Referrer-Policy",
38
+ "value": "no-referrer"
39
+ },
40
+ {
41
+ "name": "Strict-Transport-Security",
42
+ "value": "max-age=31536000 ; includeSubDomains"
43
+ },
44
+ {
45
+ "name": "X-Content-Type-Options",
46
+ "value": "nosniff"
47
+ },
48
+ {
49
+ "name": "X-Frame-Options",
50
+ "value": "deny"
51
+ },
52
+ {
53
+ "name": "X-Permitted-Cross-Domain-Policies",
54
+ "value": "none"
55
+ }
56
+ ]
57
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-openapi-documenter",
3
- "version": "0.0.62",
3
+ "version": "0.0.64",
4
4
  "description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -6,6 +6,7 @@ const { v4: uuid } = require('uuid')
6
6
  const validator = require('oas-validator');
7
7
 
8
8
  const SchemaHandler = require('./schemaHandler')
9
+ const oWASP = require('./owasp')
9
10
 
10
11
  class DefinitionGenerator {
11
12
  constructor(serverless, options = {}) {
@@ -41,7 +42,7 @@ class DefinitionGenerator {
41
42
 
42
43
  this.DEFAULT_CORS_HEADERS = {
43
44
  'Access-Control-Allow-Origin': {
44
- description: 'The Access-Control-Allow-Origin response header indicates whether the response can be shared with requesting code from the given origin.',
45
+ description: 'The Access-Control-Allow-Origin response header indicates whether the response can be shared with requesting code from the given [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin). - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin)',
45
46
  schema: {
46
47
  type: 'string',
47
48
  default: '*',
@@ -49,7 +50,7 @@ class DefinitionGenerator {
49
50
  }
50
51
  },
51
52
  'Access-Control-Allow-Credentials': {
52
- description: `The Access-Control-Allow-Credentials response header tells browsers whether to expose the response to the frontend JavaScript code when the request's credentials mode (Request.credentials) is include`,
53
+ description: `The Access-Control-Allow-Credentials response header tells browsers whether to expose the response to the frontend JavaScript code when the request's credentials mode ([Request.credentials](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)) is include. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials)`,
53
54
  schema: {
54
55
  type: 'boolean',
55
56
  default: true
@@ -68,6 +69,8 @@ class DefinitionGenerator {
68
69
  async parse() {
69
70
  this.createInfo()
70
71
 
72
+ await oWASP.getLatest()
73
+
71
74
  await this.schemaHandler.addModelsToOpenAPI()
72
75
  .catch(err => {
73
76
  throw err
@@ -338,11 +341,31 @@ class DefinitionGenerator {
338
341
  if (Object.keys(documentation).includes('deprecated'))
339
342
  obj.deprecated = documentation.deprecated
340
343
 
341
- if (documentation.requestBody)
342
- obj.requestBody = await this.createRequestBody(documentation)
344
+ if (documentation.requestBody || this.currentEvent?.request?.schemas) {
345
+ const requestModel = {}
346
+ if (documentation.requestBody) {
347
+ Object.assign(
348
+ requestModel,
349
+ {
350
+ description: documentation.requestBody.description,
351
+ models: documentation.requestModels
352
+ }
353
+ )
354
+ } else {
355
+ Object.assign(
356
+ requestModel,
357
+ {
358
+ description: '',
359
+ models: this.currentEvent?.request?.schemas
360
+ }
361
+ )
362
+ }
363
+
364
+ obj.requestBody = await this.createRequestBody(requestModel)
343
365
  .catch(err => {
344
366
  throw err
345
367
  })
368
+ }
346
369
 
347
370
  if (documentation.methodResponses)
348
371
  obj.responses = await this.createResponses(documentation)
@@ -379,21 +402,44 @@ class DefinitionGenerator {
379
402
  })
380
403
  }
381
404
 
405
+ let owaspHeaders = {}
406
+ if (response.owasp) {
407
+ if (typeof response.owasp === 'boolean') {
408
+ owaspHeaders = await this.createResponseHeaders(oWASP.DEFAULT_OWASP_HEADERS)
409
+ .catch(err => {
410
+ throw err
411
+ })
412
+ } else {
413
+ owaspHeaders = await this.createResponseHeaders(oWASP.getHeaders(response.owasp))
414
+ .catch(err => {
415
+ throw err
416
+ })
417
+ }
418
+ }
382
419
 
383
420
  const corsHeaders = await this.corsHeaders()
384
421
  .catch(err => {
385
422
  throw err;
386
423
  })
387
424
 
388
- if (obj.headers) {
389
- for (const key in corsHeaders) {
425
+ const addHeaders = (headers) => {
426
+ for (const key in headers) {
390
427
  if (!(key in obj.headers) && (obj.headers[key] = {})) {
391
- obj.headers[key] = corsHeaders[key]
428
+ obj.headers[key] = headers[key]
392
429
  }
393
430
  }
431
+ }
432
+
433
+ if (obj.headers) {
434
+ addHeaders(corsHeaders)
435
+ addHeaders(owaspHeaders)
394
436
  } else {
395
- if (Object.keys(corsHeaders).length)
437
+ if (Object.keys(corsHeaders).length) {
396
438
  obj.headers = corsHeaders
439
+ addHeaders(owaspHeaders)
440
+ } else {
441
+ obj.headers = owaspHeaders
442
+ }
397
443
  }
398
444
 
399
445
  Object.assign(responses, { [response.statusCode]: obj })
@@ -462,13 +508,13 @@ class DefinitionGenerator {
462
508
  return obj
463
509
  }
464
510
 
465
- async createRequestBody(documentation) {
511
+ async createRequestBody(requestBodyDetails) {
466
512
  const obj = {
467
- description: documentation.requestBody.description,
468
- required: documentation.requestBody.required || false,
513
+ description: requestBodyDetails.description,
514
+ required: false
469
515
  }
470
516
 
471
- obj.content = await this.createMediaTypeObject(documentation.requestModels, 'requestBody')
517
+ obj.content = await this.createMediaTypeObject(requestBodyDetails.models)
472
518
  .catch(err => {
473
519
  throw err
474
520
  })
@@ -522,6 +568,25 @@ class DefinitionGenerator {
522
568
  Object.assign(mediaTypeObj, { [contentKey]: obj })
523
569
  }
524
570
  }
571
+
572
+ if (Object.keys(mediaTypeObj).length === 0) {
573
+ for (const contentKey of Object.keys(models)) {
574
+ const obj = {}
575
+ const schema = (models[contentKey]?.schema) ? models[contentKey].schema : models[contentKey]
576
+ const name = (models[contentKey]?.name) ? models[contentKey].name : uuid()
577
+ const schemaRef = await this.schemaHandler.createSchema(name, schema)
578
+ .catch(err => {
579
+ throw err
580
+ })
581
+
582
+ obj.schema = {
583
+ $ref: schemaRef
584
+ }
585
+
586
+ Object.assign(mediaTypeObj, { [contentKey]: obj })
587
+ }
588
+ }
589
+
525
590
  return mediaTypeObj
526
591
  }
527
592
 
package/src/owasp.js ADDED
@@ -0,0 +1,141 @@
1
+ 'use strict'
2
+
3
+ const https = require('https')
4
+
5
+ const defaultOWASP = require('../json/owasp.json')
6
+
7
+ /**
8
+ * @typedef {Object} Header
9
+ * @property {string} name - The name of the header
10
+ * @property {string} value - The default value of the header
11
+ */
12
+
13
+ /**
14
+ * @typedef {Object} OWASPHeaders
15
+ * @property {string} last_update_utc - When the headers were last updated in UTC
16
+ * @property {Array.<Header>} headers - An array of headers
17
+ */
18
+
19
+ class OWASP {
20
+ constructor() {
21
+ this.DEFAULT_OWASP_HEADERS = {
22
+ "Cache-Control": {
23
+ description: 'The Cache-Control HTTP header field holds directives (instructions) — in both requests and responses — that control [caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching) in browsers and shared caches (e.g. Proxies, CDNs). - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)',
24
+ },
25
+ "Clear-Site-Data": {
26
+ description: 'The Clear-Site-Data header clears browsing data (cookies, storage, cache) associated with the requesting website. It allows web developers to have more control over the data stored by a client browser for their origins. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data)',
27
+ },
28
+ "Content-Security-Policy": {
29
+ description: 'The HTTP Content-Security-Policy response header allows website administrators to control resources the user agent is allowed to load for a given page. With a few exceptions, policies mostly involve specifying server origins and script endpoints. This helps guard against cross-site scripting attacks ([Cross-site scripting](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting)). - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)',
30
+ },
31
+ "Cross-Origin-Embedder-Policy": {
32
+ description: 'The HTTP Cross-Origin-Embedder-Policy (COEP) response header configures embedding cross-origin resources into the document. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy)',
33
+ },
34
+ "Cross-Origin-Opener-Policy": {
35
+ description: 'The HTTP Cross-Origin-Opener-Policy (COOP) response header allows you to ensure a top-level document does not share a browsing context group with cross-origin documents. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy)',
36
+ },
37
+ "Cross-Origin-Resource-Policy": {
38
+ description: 'Cross-Origin Resource Policy is a policy set by the Cross-Origin-Resource-Policy HTTP header that lets websites and applications opt in to protection against certain requests from other origins (such as those issued with elements like <script> and <img>), to mitigate speculative side-channel attacks, like Spectre, as well as Cross-Site Script Inclusion attacks. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy)',
39
+ },
40
+ "Permissions-Policy": {
41
+ description: 'The HTTP Permissions-Policy header provides a mechanism to allow and deny the use of browser features in a document or within any [<iframe>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) elements in the document. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy)',
42
+ },
43
+ "Pragma": {
44
+ description: 'The Pragma HTTP/1.0 general header is an implementation-specific header that may have various effects along the request-response chain. This header serves for backwards compatibility with the HTTP/1.0 caches that do not have a [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) HTTP/1.1 header. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Pragma)',
45
+ deprecated: true,
46
+ },
47
+ "Referrer-Policy": {
48
+ description: 'The Referrer-Policy [HTTP header](https://developer.mozilla.org/en-US/docs/Glossary/HTTP_header) controls how much [referrer information](https://developer.mozilla.org/en-US/docs/Web/Security/Referer_header:_privacy_and_security_concerns) (sent with the [Referer](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer) header) should be included with requests. Aside from the HTTP header, you can [set this policy in HTML](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#integration_with_html). - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)',
49
+ },
50
+ "Strict-Transport-Security": {
51
+ description: 'The HTTP Strict-Transport-Security response header (often abbreviated as [HSTS](https://developer.mozilla.org/en-US/docs/Glossary/HSTS)) informs browsers that the site should only be accessed using HTTPS, and that any future attempts to access it using HTTP should automatically be converted to HTTPS. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security)',
52
+ },
53
+ "X-Content-Type-Options": {
54
+ description: 'The X-Content-Type-Options response HTTP header is a marker used by the server to indicate that the [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) advertised in the [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) headers should be followed and not be changed. The header allows you to avoid [MIME type sniffing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#mime_sniffing) by saying that the MIME types are deliberately configured. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options)',
55
+ },
56
+ "X-Frame-Options": {
57
+ description: 'The X-Frame-Options [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) response header can be used to indicate whether or not a browser should be allowed to render a page in a [<frame>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/frame), [<iframe>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe), [<embed>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed) or [<object>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object). Sites can use this to avoid [click-jacking](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#click-jacking) attacks, by ensuring that their content is not embedded into other sites. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options)',
58
+ },
59
+ "X-Permitted-Cross-Domain-Policies": {
60
+ description: "A cross-domain policy file is an XML document that grants a web client, such as Adobe Flash Player or Adobe Acrobat (though not necessarily limited to these), permission to handle data across domains. When clients request content hosted on a particular source domain and that content makes requests directed towards a domain other than its own, the remote domain needs to host a cross-domain policy file that grants access to the source domain, allowing the client to continue the transaction. Normally a meta-policy is declared in the master policy file, but for those who can't write to the root directory, they can also declare a meta-policy using the X-Permitted-Cross-Domain-Policies HTTP response header. - [OWASP Link](https://owasp.org/www-project-secure-headers/#x-permitted-cross-domain-policies)",
61
+ }
62
+ }
63
+
64
+ this.headerMap = {
65
+ cacheControl: 'Cache-Control',
66
+ clearSiteData: 'Clear-Site-Data',
67
+ contentSecurityPolicy: 'Content-Security-Policy',
68
+ crossOriginEmbedderPolicy: 'Cross-Origin-Embedder-Policy',
69
+ crossOriginOpenerPolicy: 'Cross-Origin-Opener-Policy',
70
+ crossOriginResourcePolicy: 'Cross-Origin-Resource-Policy',
71
+ permissionsPolicy: 'Permissions-Policy',
72
+ pragma: 'Pragma',
73
+ referrerPolicy: 'Referrer-Policy',
74
+ strictTransportSecurity: 'Strict-Transport-Security',
75
+ xContentTypeOptions: 'X-Content-Type-Options',
76
+ xFrameOptions: 'X-Frame-Options',
77
+ xPermittedCrossDomainPolicies: 'X-Permitted-Cross-Domain-Policies'
78
+ }
79
+ }
80
+
81
+ async getLatest() {
82
+ const headerJSON = await new Promise((resolve, reject) => {
83
+ const req = https.get('https://owasp.org/www-project-secure-headers/ci/headers_add.json', (res) => {
84
+ let data = []
85
+
86
+ if (res.statusCode !== 200) {
87
+ resolve(defaultOWASP)
88
+ }
89
+
90
+ res.on('error', (err) => {
91
+ resolve(defaultOWASP)
92
+ })
93
+
94
+ res.on('data', (chunk) => {
95
+ data.push(chunk)
96
+ })
97
+
98
+ res.on('end', () => {
99
+ resolve(JSON.parse(Buffer.concat(data).toString()))
100
+ })
101
+ })
102
+ .on('error', (err) => {
103
+ resolve(defaultOWASP)
104
+ })
105
+
106
+ req.end()
107
+ })
108
+
109
+ this.populateDefaults(headerJSON)
110
+ }
111
+
112
+ /**
113
+ * @funtion populateDefaults
114
+ * @param {OWASPHeaders} headerJSON
115
+ */
116
+ populateDefaults(headerJSON) {
117
+ for (const header of headerJSON.headers) {
118
+ if (this.DEFAULT_OWASP_HEADERS?.[header.name]) {
119
+ Object.assign(this.DEFAULT_OWASP_HEADERS[header.name], {schema: {type: 'string', default: header.value}})
120
+ } else {
121
+ Object.assign(this.DEFAULT_OWASP_HEADERS, {[header.name]: {schema: {type: 'string', default: header.value}}})
122
+ }
123
+ }
124
+ }
125
+
126
+ getHeaders(headerList) {
127
+ const obj = {}
128
+ for (const headerName of Object.keys(headerList)) {
129
+ const defaultHeader = this.DEFAULT_OWASP_HEADERS[this.headerMap[headerName]]
130
+ Object.assign(obj, {[this.headerMap[headerName]]: defaultHeader})
131
+
132
+ if (typeof headerList[headerName] !== 'boolean') {
133
+ obj[this.headerMap[headerName]].schema.default = headerList[headerName].value
134
+ }
135
+ }
136
+
137
+ return obj
138
+ }
139
+ }
140
+
141
+ module.exports = new OWASP()
@@ -9,6 +9,7 @@ const { v4: uuid } = require('uuid')
9
9
 
10
10
  class SchemaHandler {
11
11
  constructor(serverless, openAPI) {
12
+ this.apiGatewayModels = serverless.service?.provider?.apiGateway?.request?.schemas || {}
12
13
  this.documentation = serverless.service.custom.documentation
13
14
  this.openAPI = openAPI
14
15
 
@@ -42,7 +43,12 @@ class SchemaHandler {
42
43
  const standardisedModels = this.documentation?.models?.map(standardModel) || []
43
44
  const standardisedModelsList = this.documentation?.modelsList?.map(standardModel) || []
44
45
 
45
- this.models = standardisedModels.length ? standardisedModels.concat(standardisedModelsList) : standardisedModelsList
46
+ const standardisedGatewayModels = Object.keys(this.apiGatewayModels).flatMap(key => {
47
+ const gatewayModel = this.apiGatewayModels[key]
48
+ return standardModel(gatewayModel)
49
+ }) || []
50
+
51
+ this.models = standardisedModels.concat(standardisedModelsList, standardisedGatewayModels)
46
52
  }
47
53
 
48
54
  async addModelsToOpenAPI() {
@@ -0,0 +1,57 @@
1
+ {
2
+ "last_update_utc": "2023-05-26 12:22:30",
3
+ "headers": [
4
+ {
5
+ "name": "Cache-Control",
6
+ "value": "no-store, max-age=0"
7
+ },
8
+ {
9
+ "name": "Clear-Site-Data",
10
+ "value": "\"cache\",\"cookies\",\"storage\""
11
+ },
12
+ {
13
+ "name": "Content-Security-Policy",
14
+ "value": "default-src 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content"
15
+ },
16
+ {
17
+ "name": "Cross-Origin-Embedder-Policy",
18
+ "value": "credentialless"
19
+ },
20
+ {
21
+ "name": "Cross-Origin-Opener-Policy",
22
+ "value": "same-origin"
23
+ },
24
+ {
25
+ "name": "Cross-Origin-Resource-Policy",
26
+ "value": "same-origin"
27
+ },
28
+ {
29
+ "name": "Permissions-Policy",
30
+ "value": "accelerometer=(),ambient-light-sensor=(),autoplay=(),battery=(),camera=(),display-capture=(),document-domain=(),encrypted-media=(),fullscreen=(),gamepad=(),geolocation=(),gyroscope=(),layout-animations=(self),legacy-image-formats=(self),magnetometer=(),microphone=(),midi=(),oversized-images=(self),payment=(),picture-in-picture=(),publickey-credentials-get=(),speaker-selection=(),sync-xhr=(self),unoptimized-images=(self),unsized-media=(self),usb=(),screen-wake-lock=(),web-share=(),xr-spatial-tracking=()"
31
+ },
32
+ {
33
+ "name": "Pragma",
34
+ "value": "no-cache"
35
+ },
36
+ {
37
+ "name": "Referrer-Policy",
38
+ "value": "no-referrer"
39
+ },
40
+ {
41
+ "name": "Strict-Transport-Security",
42
+ "value": "max-age=31536000 ; includeSubDomains"
43
+ },
44
+ {
45
+ "name": "X-Content-Type-Options",
46
+ "value": "nosniff"
47
+ },
48
+ {
49
+ "name": "X-Frame-Options",
50
+ "value": "deny"
51
+ },
52
+ {
53
+ "name": "X-Permitted-Cross-Domain-Policies",
54
+ "value": "none"
55
+ }
56
+ ]
57
+ }
@@ -0,0 +1,113 @@
1
+ 'use strict'
2
+
3
+ const expect = require('chai').expect
4
+ const nock = require('nock')
5
+
6
+ const owasp = require('../../src/owasp')
7
+
8
+ const owaspJSON = require('../../json/owasp.json')
9
+ const newOWASPJSON = require('../json/newOWASP.json')
10
+
11
+ describe(`owasp`, function () {
12
+ describe(`getLatest`, function () {
13
+ it(`populates the defaults from the included OWASP release when the online version can not be reached`, async function() {
14
+ nock('https://owasp.org')
15
+ .get('/www-project-secure-headers/ci/headers_add.json')
16
+ .reply(404, {})
17
+
18
+ await owasp.getLatest()
19
+ .catch(err => {
20
+ console.error(err)
21
+ expect(err).to.be.undefined
22
+ })
23
+
24
+ expect(owasp.DEFAULT_OWASP_HEADERS['Permissions-Policy']).to.have.property('schema')
25
+ const permissionsPolicyDefault = owaspJSON.headers.filter(obj => obj.name === 'Permissions-Policy')
26
+ expect(owasp.DEFAULT_OWASP_HEADERS['Permissions-Policy'].schema.default).to.be.equal(permissionsPolicyDefault[0].value)
27
+ expect(Object.keys(owasp.DEFAULT_OWASP_HEADERS).length).to.be.equal(13)
28
+ });
29
+
30
+ it(`populates the defaults with information from a new OWASP release`, async function() {
31
+ nock('https://owasp.org')
32
+ .get('/www-project-secure-headers/ci/headers_add.json')
33
+ .reply(200, newOWASPJSON)
34
+
35
+ await owasp.getLatest()
36
+ .catch(err => {
37
+ console.error(err)
38
+ expect(err).to.be.undefined
39
+ })
40
+
41
+ expect(owasp.DEFAULT_OWASP_HEADERS['Cross-Origin-Embedder-Policy']).to.have.property('schema')
42
+ const newCrossOriginEmbedderPolicy = newOWASPJSON.headers.filter(obj => obj.name === 'Cross-Origin-Embedder-Policy')
43
+ expect(owasp.DEFAULT_OWASP_HEADERS['Cross-Origin-Embedder-Policy'].schema.default).to.be.equal(newCrossOriginEmbedderPolicy[0].value)
44
+ expect(Object.keys(owasp.DEFAULT_OWASP_HEADERS).length).to.be.equal(13)
45
+ });
46
+
47
+ it(`does not remove any defaults not contained in a new release`, async function() {
48
+ const newOWASPJSONMissing = JSON.parse(JSON.stringify(newOWASPJSON))
49
+
50
+ const headers = newOWASPJSONMissing.headers.filter(obj => obj.name !== 'Pragma')
51
+ newOWASPJSONMissing.headers = headers
52
+
53
+ nock('https://owasp.org')
54
+ .get('/www-project-secure-headers/ci/headers_add.json')
55
+ .reply(200, newOWASPJSONMissing)
56
+
57
+ await owasp.getLatest()
58
+ .catch(err => {
59
+ console.error(err)
60
+ expect(err).to.be.undefined
61
+ })
62
+
63
+ expect(owasp.DEFAULT_OWASP_HEADERS).to.have.property('Pragma')
64
+ expect(Object.keys(owasp.DEFAULT_OWASP_HEADERS).length).to.be.equal(13)
65
+ });
66
+
67
+ it(`adds any properties contained in a new release`, async function() {
68
+ const newOWASPJSONAdded = JSON.parse(JSON.stringify(newOWASPJSON))
69
+ newOWASPJSONAdded.headers.push({name: 'x-added', value: 'true'})
70
+
71
+ nock('https://owasp.org')
72
+ .get('/www-project-secure-headers/ci/headers_add.json')
73
+ .reply(200, newOWASPJSONAdded)
74
+
75
+ await owasp.getLatest()
76
+ .catch(err => {
77
+ console.error(err)
78
+ expect(err).to.be.undefined
79
+ })
80
+
81
+ expect(owasp.DEFAULT_OWASP_HEADERS).to.have.property('x-added')
82
+ expect(owasp.DEFAULT_OWASP_HEADERS['x-added']).to.have.property('schema')
83
+ expect(owasp.DEFAULT_OWASP_HEADERS['x-added'].schema.default).to.be.equal('true')
84
+ expect(Object.keys(owasp.DEFAULT_OWASP_HEADERS).length).to.be.equal(14)
85
+ });
86
+ });
87
+
88
+ describe(`getHeaders`, function () {
89
+ it(`brings back default headers from a list`, function() {
90
+ const headerOptions = {cacheControl: true, xFrameOptions: true}
91
+ const headers = owasp.getHeaders(headerOptions)
92
+
93
+ expect(Object.keys(headers).length).to.be.equal(2)
94
+ });
95
+
96
+ it(`brings back default headers from a list with new schema defaults when values are provided`, function() {
97
+ const headerOptions = {
98
+ referrerPolicy: {
99
+ value: 'true'
100
+ },
101
+ crossOriginOpenerPolicy: {
102
+ value: 'strict'
103
+ }
104
+ }
105
+
106
+ const headers = owasp.getHeaders(headerOptions)
107
+
108
+ expect(Object.keys(headers).length).to.be.equal(2)
109
+
110
+ expect(headers['Cross-Origin-Opener-Policy'].schema.default === 'strict')
111
+ });
112
+ });
113
+ });