@ferrule-io/ok-fine 0.3.8 → 0.3.9
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/dist/http/ui.js +47 -33
- package/dist/server.js +8 -2
- package/dist/ui/assets/highlight-DkgVr4Tn.js +153 -0
- package/dist/ui/assets/{index-0b2NNRC5.js → index-C2IYVK_7.js} +33 -33
- package/dist/ui/index.html +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -1
- package/dist/ui/assets/highlight-CW19vWvw.js +0 -153
package/dist/http/ui.js
CHANGED
|
@@ -17,6 +17,9 @@ const CONTENT_TYPES = {
|
|
|
17
17
|
".txt": "text/plain; charset=utf-8",
|
|
18
18
|
};
|
|
19
19
|
const IMMUTABLE = "public, max-age=31536000, immutable";
|
|
20
|
+
const PERMISSIONS_POLICY = "camera=(), display-capture=(), geolocation=(), microphone=()";
|
|
21
|
+
const SVG_CSP = "default-src 'none'; style-src 'unsafe-inline'; sandbox";
|
|
22
|
+
const DEFAULT_CSP = "frame-ancestors 'none'";
|
|
20
23
|
/** Unique origins of the string http(s) URLs among `endpoints`; ignores anything else. */
|
|
21
24
|
export function endpointOrigins(...endpoints) {
|
|
22
25
|
const origins = new Set();
|
|
@@ -67,50 +70,61 @@ export async function registerUiRoutes(app, options) {
|
|
|
67
70
|
app.log.warn({ dir: options.dir }, "web UI assets not found; /ui disabled");
|
|
68
71
|
return false;
|
|
69
72
|
}
|
|
70
|
-
const
|
|
73
|
+
const htmlCsp = [
|
|
71
74
|
"default-src 'self'",
|
|
72
75
|
"script-src 'self'",
|
|
73
|
-
// Shiki emits inline style attributes.
|
|
76
|
+
// Shiki emits inline style attributes for code block syntax highlighting.
|
|
74
77
|
"style-src 'self' 'unsafe-inline'",
|
|
75
|
-
"img-src 'self' data:
|
|
78
|
+
"img-src 'self' data:",
|
|
76
79
|
"font-src 'self' data:",
|
|
77
80
|
`connect-src 'self'${options.connectSrc.map((origin) => ` ${origin}`).join("")}`,
|
|
78
81
|
"object-src 'none'",
|
|
79
82
|
"base-uri 'none'",
|
|
80
83
|
"form-action 'none'",
|
|
81
84
|
"frame-ancestors 'none'",
|
|
85
|
+
...(options.publicBaseUrl.startsWith("https://") ? ["upgrade-insecure-requests"] : []),
|
|
82
86
|
].join("; ");
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
await app.register(async (ui) => {
|
|
88
|
+
ui.addHook("onSend", async (_req, reply, payload) => {
|
|
89
|
+
reply.header("x-content-type-options", "nosniff");
|
|
90
|
+
reply.header("x-frame-options", "DENY");
|
|
91
|
+
reply.header("referrer-policy", "no-referrer");
|
|
92
|
+
reply.header("cross-origin-opener-policy", "same-origin");
|
|
93
|
+
reply.header("permissions-policy", PERMISSIONS_POLICY);
|
|
94
|
+
const contentType = reply.getHeader("content-type");
|
|
95
|
+
if (typeof contentType === "string" && contentType.startsWith("image/svg+xml")) {
|
|
96
|
+
reply.header("content-security-policy", SVG_CSP);
|
|
97
|
+
}
|
|
98
|
+
else if (!reply.hasHeader("content-security-policy")) {
|
|
99
|
+
reply.header("content-security-policy", DEFAULT_CSP);
|
|
100
|
+
}
|
|
101
|
+
return payload;
|
|
102
|
+
});
|
|
103
|
+
const sendHtml = (reply) => reply
|
|
104
|
+
.header("content-type", "text/html; charset=utf-8")
|
|
105
|
+
.header("cache-control", "no-cache")
|
|
106
|
+
.header("content-security-policy", htmlCsp)
|
|
107
|
+
.send(index.body);
|
|
108
|
+
ui.get("/", async (_req, reply) => reply.redirect("/ui/", 302));
|
|
109
|
+
ui.get("/ui", async (_req, reply) => reply.redirect("/ui/", 301));
|
|
110
|
+
ui.get("/ui/config.json", async (_req, reply) => reply.header("cache-control", "no-store").send(options.clientConfig));
|
|
111
|
+
ui.get("/ui/*", async (req, reply) => {
|
|
112
|
+
const rel = req.params["*"];
|
|
113
|
+
if (rel === "" || rel === "index.html")
|
|
114
|
+
return sendHtml(reply);
|
|
115
|
+
const asset = assets.get(rel);
|
|
116
|
+
if (asset) {
|
|
117
|
+
return reply.header("content-type", asset.type).header("cache-control", asset.cache).send(asset.body);
|
|
118
|
+
}
|
|
119
|
+
const last = rel.slice(rel.lastIndexOf("/") + 1);
|
|
120
|
+
if (/\.[A-Za-z0-9]+$/.test(last)) {
|
|
121
|
+
return reply
|
|
122
|
+
.code(404)
|
|
123
|
+
.header("cache-control", "no-cache")
|
|
124
|
+
.send({ error: { code: "not_found", message: "not found" } });
|
|
125
|
+
}
|
|
97
126
|
return sendHtml(reply);
|
|
98
|
-
|
|
99
|
-
if (asset) {
|
|
100
|
-
return reply
|
|
101
|
-
.header("content-type", asset.type)
|
|
102
|
-
.header("cache-control", asset.cache)
|
|
103
|
-
.header("x-content-type-options", "nosniff")
|
|
104
|
-
.send(asset.body);
|
|
105
|
-
}
|
|
106
|
-
const last = rel.slice(rel.lastIndexOf("/") + 1);
|
|
107
|
-
if (/\.[A-Za-z0-9]+$/.test(last)) {
|
|
108
|
-
return reply
|
|
109
|
-
.code(404)
|
|
110
|
-
.header("x-content-type-options", "nosniff")
|
|
111
|
-
.send({ error: { code: "not_found", message: "not found" } });
|
|
112
|
-
}
|
|
113
|
-
return sendHtml(reply);
|
|
127
|
+
});
|
|
114
128
|
});
|
|
115
129
|
app.log.info({ url: `${options.publicBaseUrl}/ui/` }, "web UI enabled");
|
|
116
130
|
return true;
|
package/dist/server.js
CHANGED
|
@@ -150,7 +150,10 @@ export async function startServer(config, options = {}) {
|
|
|
150
150
|
}
|
|
151
151
|
const host = parseHostHeader(req.headers.host);
|
|
152
152
|
if (!host || !allowedHosts[host]) {
|
|
153
|
-
return reply
|
|
153
|
+
return reply
|
|
154
|
+
.code(403)
|
|
155
|
+
.header("x-content-type-options", "nosniff")
|
|
156
|
+
.send({
|
|
154
157
|
error: {
|
|
155
158
|
code: "forbidden",
|
|
156
159
|
message: `Host header '${req.headers.host ?? ""}' is not allowed in AUTH_MODE=none`,
|
|
@@ -173,7 +176,10 @@ export async function startServer(config, options = {}) {
|
|
|
173
176
|
}
|
|
174
177
|
}
|
|
175
178
|
if (!originAllowed) {
|
|
176
|
-
return reply
|
|
179
|
+
return reply
|
|
180
|
+
.code(403)
|
|
181
|
+
.header("x-content-type-options", "nosniff")
|
|
182
|
+
.send({
|
|
177
183
|
error: {
|
|
178
184
|
code: "forbidden",
|
|
179
185
|
message: `Origin '${rawOrigin ?? ""}' is not allowed in AUTH_MODE=none`,
|