@tetsujs/openapi 0.4.1 → 0.5.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/src/page.ts CHANGED
@@ -12,6 +12,13 @@
12
12
  * a parameter, so a self-hosted copy is one option away. Nothing is
13
13
  * fetched unless this function is called.
14
14
  *
15
+ * What the CDN serves runs on the application's origin, with its cookies.
16
+ * The default renderers are therefore pinned to an exact version and
17
+ * carry the hash of that file: a CDN, or a package release, that serves
18
+ * something else is refused by the browser rather than run. A page on an
19
+ * origin with a session is still somebody else's application in the
20
+ * user's session — which is why `docs()` can serve the document alone.
21
+ *
15
22
  * @module
16
23
  */
17
24
 
@@ -22,6 +29,20 @@ export type DocsUi = "scalar" | "swagger-ui" | "redoc";
22
29
  export interface DocsAssets {
23
30
  readonly script: string;
24
31
  readonly style?: string;
32
+
33
+ /**
34
+ * Subresource Integrity hashes of the two files, for a copy that is not
35
+ * on the application's own origin: the browser runs the file only if it
36
+ * hashes to this. Absent, the files are loaded as they are.
37
+ *
38
+ * One line computes one:
39
+ * `curl -s <url> | openssl dgst -sha384 -binary | openssl base64 -A`,
40
+ * prefixed with `sha384-`.
41
+ */
42
+ readonly integrity?: {
43
+ readonly script?: string;
44
+ readonly style?: string;
45
+ };
25
46
  }
26
47
 
27
48
  /** What the page needs to know. */
@@ -39,17 +60,42 @@ export interface DocsPageOptions {
39
60
  readonly assets?: DocsAssets;
40
61
  }
41
62
 
63
+ /**
64
+ * The renderers by default: an exact version, the file by its full path,
65
+ * and the hash of that file.
66
+ *
67
+ * The full path because a bare package URL is not a file — jsDelivr
68
+ * answers it with a build of its own, whose bytes differ from the file's.
69
+ * Moving to a newer version means a new hash, computed as
70
+ * {@link DocsAssets.integrity} describes.
71
+ */
42
72
  const defaults: Record<DocsUi, DocsAssets> = {
43
73
  scalar: {
44
- script: "https://cdn.jsdelivr.net/npm/@scalar/api-reference",
74
+ script:
75
+ "https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.72.1/dist/browser/standalone.js",
76
+ integrity: {
77
+ script:
78
+ "sha384-U11tb2XnKvmwt8RlTvnwUnYgrN+ur4Xyh9htLhjajWNR/Oyl5AX5DEz00qRmlrmK",
79
+ },
45
80
  },
46
81
  "swagger-ui": {
47
82
  script:
48
- "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
49
- style: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css",
83
+ "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.0/swagger-ui-bundle.js",
84
+ style: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.0/swagger-ui.css",
85
+ integrity: {
86
+ script:
87
+ "sha384-YDALVcy8kj8yltLBVi1vBiBAUqdxvus673gM8XKwiy6aDUJFXivF/KCufekjYbVf",
88
+ style:
89
+ "sha384-Ov4/wv3j2bmct8cDc5X4ngJZohVPzEmc6uDPH8WeljUxO5vtoykvMEfbu9Vh6RaW",
90
+ },
50
91
  },
51
92
  redoc: {
52
- script: "https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js",
93
+ script:
94
+ "https://cdn.jsdelivr.net/npm/redoc@2.5.4/bundles/redoc.standalone.js",
95
+ integrity: {
96
+ script:
97
+ "sha384-w447zOpYfw/1Tv/5AK9NfHTlQIqE3RVR6KY62jCyy9zNDgO64cMwGGP1Fj0zJVf5",
98
+ },
53
99
  },
54
100
  };
55
101
 
@@ -84,12 +130,12 @@ export function docsPage(options: DocsPageOptions): string {
84
130
  '<meta name="viewport" content="width=device-width, initial-scale=1" />',
85
131
  `<title>${title}</title>`,
86
132
  assets.style
87
- ? `<link rel="stylesheet" href="${escapeHtml(assets.style)}" />`
133
+ ? `<link rel="stylesheet" href="${escapeHtml(assets.style)}"${verified(assets.integrity?.style)} />`
88
134
  : "",
89
135
  "</head>",
90
136
  "<body>",
91
137
  body(options.ui, url),
92
- `<script src="${escapeHtml(assets.script)}"></script>`,
138
+ `<script src="${escapeHtml(assets.script)}"${verified(assets.integrity?.script)}></script>`,
93
139
  bootstrap(options.ui, url),
94
140
  "</body>",
95
141
  "</html>",
@@ -122,6 +168,19 @@ function bootstrap(ui: DocsUi, url: string): string {
122
168
  ].join("\n");
123
169
  }
124
170
 
171
+ /**
172
+ * The attributes that make the browser check a file against its hash.
173
+ *
174
+ * `crossorigin` comes with it: a file from another origin is checked only
175
+ * when fetched with CORS, and without the attribute the browser refuses
176
+ * it outright rather than skip the check.
177
+ */
178
+ function verified(integrity: string | undefined): string {
179
+ return integrity
180
+ ? ` integrity="${escapeHtml(integrity)}" crossorigin="anonymous"`
181
+ : "";
182
+ }
183
+
125
184
  /**
126
185
  * Escapes the values that reach the markup.
127
186
  *