v0.34.15
This commit is contained in:
1 parent
62b7574553
commit
e364392659
47 files changed
+2164
-257
No files matched your search
@@ -33,6 +33,18 @@ function hostTagEditor(parent, hosts = [], onUpdate = () => { }) {
|
||||
return () => { if (input.value.trim()) consume(input.value); if (!values.length) throw new Error(t("vhHostRequired")); return [...values]; };
|
||||
}
|
||||
|
||||
function readForwardProvider(id) {
|
||||
const value = key => str(`${id}-${key}`);
|
||||
return {
|
||||
enabled: str("auth-mode") === id,
|
||||
url: value("url"), login_url: id === "authelia" ? value("login") : "",
|
||||
allow_private: yes(`${id}-private`), redirect_forbidden: yes(`${id}-redirect-forbidden`),
|
||||
connect_timeout_seconds: num(`${id}-connect`), timeout_seconds: num(`${id}-timeout`),
|
||||
request_headers: lines(value("request-headers")), response_headers: lines(value("response-headers")),
|
||||
address_allowlist: lines(value("allowlist")), tls_ca_pem: nullable(`${id}-ca`),
|
||||
};
|
||||
}
|
||||
|
||||
function load(value = defaults(), migrationIds = [], show = true) {
|
||||
draftEpoch++; stagedCertificates = []; uploading = false; backendHealthEditorControl = null;
|
||||
editing = value.id || null; revision = envelope?.revision ?? null; dirty = false; initialized = true;
|
||||
@@ -332,19 +344,18 @@ function load(value = defaults(), migrationIds = [], show = true) {
|
||||
const syncTlsPolicy = () => { for (const input of [tls12, tls13, tlsCiphers, tlsPqc]) input.disabled = !customTlsPolicy.checked; };
|
||||
customTlsPolicy.addEventListener("change", syncTlsPolicy); syncTlsPolicy(); note(g, "vhTlsOverrideHelp"); note(g, "tlsPqcVhostHelp");
|
||||
g = group("vhTinyauth"); const auth = value.tinyauth || {};
|
||||
const authMode = field(g, "auth-mode", "authMode", value.basic_auth?.enabled ? "basic" : auth.enabled ? "tinyauth" : "none", "select", { values: [["none", "authNone"], ["tinyauth", "authTinyauth"], ["basic", "authBasic"]] });
|
||||
const authMode = field(g, "auth-mode", "authMode", value.basic_auth?.enabled ? "basic" : auth.enabled ? "tinyauth" : value.authelia?.enabled ? "authelia" : value.authentik?.enabled ? "authentik" : "none", "select", { values: [["none", "authNone"], ["tinyauth", "authTinyauth"], ["authelia", "authAuthelia"], ["authentik", "authAuthentik"], ["basic", "authBasic"]] });
|
||||
note(g, "authModeHelp");
|
||||
|
||||
const taBody = el("section", undefined, "vh-wide auth-subcard"), basicBody = el("section", undefined, "vh-wide auth-subcard"); g.append(taBody, basicBody);
|
||||
taBody.append(text("h3", "authTinyauth"));
|
||||
const taPrimary = el("div", undefined, "vh-grid"); taBody.append(taPrimary);
|
||||
const authEnable = field(taPrimary, "auth-enabled", "taEnable", auth.enabled, "checkbox");
|
||||
const authUrl = field(taPrimary, "auth-url", "taUrl", auth.url || "", "url", { maxLength: 2048 });
|
||||
const loginUrl = field(taPrimary, "auth-login", "taLogin", auth.login_url || "", "url", { maxLength: 2048 });
|
||||
authUrl.placeholder = "http://10.87.7.99:3000"; loginUrl.placeholder = "https://auth.example.com/login";
|
||||
field(taPrimary, "auth-private", "vhPrivate", auth.allow_private, "checkbox");
|
||||
field(taPrimary, "auth-redirect-forbidden", "taRedirectForbidden", auth.redirect_forbidden !== false, "checkbox");
|
||||
authEnable.closest("label").hidden = true;
|
||||
const taOptions = el("div", undefined, "vh-wide auth-provider-options"); taPrimary.append(taOptions);
|
||||
field(taOptions, "auth-private", "vhPrivate", auth.allow_private, "checkbox");
|
||||
field(taOptions, "auth-redirect-forbidden", "taRedirectForbidden", auth.redirect_forbidden !== false, "checkbox");
|
||||
note(taBody, "taHelp");
|
||||
|
||||
const taAdvanced = el("details", undefined, "vh-wide auth-advanced"); taAdvanced.append(text("summary", "taAdvanced"));
|
||||
@@ -356,14 +367,49 @@ function load(value = defaults(), migrationIds = [], show = true) {
|
||||
field(taAdvancedGrid, "auth-allowlist", "vhAddressAllowlist", (auth.address_allowlist || []).join("\n"), "textarea", { rows: 2, maxLength: 8192 });
|
||||
field(taAdvancedGrid, "auth-ca", "vhCa", auth.tls_ca_pem || "", "textarea", { rows: 2, maxLength: 65536 });
|
||||
|
||||
const forwardEditors = {};
|
||||
for (const id of ["authelia", "authentik"]) {
|
||||
const current = value[id] || {}, title = id === "authelia" ? "authAuthelia" : "authAuthentik";
|
||||
const body = el("section", undefined, "vh-wide auth-subcard"); g.append(body);
|
||||
body.append(text("h3", title));
|
||||
const primary = el("div", undefined, "vh-grid"); body.append(primary);
|
||||
const url = field(primary, `${id}-url`, "forwardAuthUrl", current.url || "", "url", { maxLength: 2048 });
|
||||
url.placeholder = id === "authelia" ? "http://authelia:9091" : "http://authentik:9000";
|
||||
let login = null;
|
||||
if (id === "authelia") {
|
||||
login = field(primary, `${id}-login`, "forwardAuthLogin", current.login_url || "", "url", { maxLength: 2048 });
|
||||
login.placeholder = "https://auth.example.com";
|
||||
}
|
||||
const options = el("div", undefined, "vh-wide auth-provider-options"); primary.append(options);
|
||||
field(options, `${id}-private`, "vhPrivate", current.allow_private, "checkbox");
|
||||
field(options, `${id}-redirect-forbidden`, "taRedirectForbidden", current.redirect_forbidden === true, "checkbox");
|
||||
note(body, id === "authelia" ? "autheliaHelp" : "authentikHelp");
|
||||
const advanced = el("details", undefined, "vh-wide auth-advanced"); advanced.append(text("summary", "forwardAuthAdvanced"));
|
||||
const details = el("div", undefined, "vh-grid"); advanced.append(details); body.append(advanced);
|
||||
field(details, `${id}-connect`, "taConnect", current.connect_timeout_seconds ?? 5, "number", { required: true, min: 1, max: 30 });
|
||||
field(details, `${id}-timeout`, "taTimeout", current.timeout_seconds ?? 10, "number", { required: true, min: 1, max: 60 });
|
||||
field(details, `${id}-request-headers`, "taRequestHeaders", (current.request_headers || ["cookie", "authorization", "user-agent", "accept", "accept-language"]).join("\n"), "textarea", { rows: 3, maxLength: 8192 });
|
||||
const autheliaHeaders = ["remote-user", "remote-groups", "remote-name", "remote-email"];
|
||||
const authentikHeaders = ["x-authentik-username", "x-authentik-email", "x-authentik-name", "x-authentik-groups", "x-authentik-uid", "x-authentik-entitlements"];
|
||||
field(details, `${id}-response-headers`, "taResponseHeaders", (current.response_headers || [...autheliaHeaders, ...authentikHeaders]).join("\n"), "textarea", { rows: 4, maxLength: 8192 });
|
||||
field(details, `${id}-allowlist`, "vhAddressAllowlist", (current.address_allowlist || []).join("\n"), "textarea", { rows: 2, maxLength: 8192 });
|
||||
field(details, `${id}-ca`, "vhCa", current.tls_ca_pem || "", "textarea", { rows: 2, maxLength: 65536 });
|
||||
forwardEditors[id] = { body, url, login };
|
||||
}
|
||||
|
||||
basicBody.append(text("h3", "authBasic"));
|
||||
const basicGrid = el("div", undefined, "vh-grid"); basicBody.append(basicGrid);
|
||||
basicEditor = basicAuthEditor(basicGrid, value.basic_auth || {});
|
||||
const syncAuth = () => {
|
||||
authEnable.checked = authMode.value === "tinyauth";
|
||||
taBody.hidden = !authEnable.checked;
|
||||
const tinyauthSelected = authMode.value === "tinyauth";
|
||||
taBody.hidden = !tinyauthSelected;
|
||||
basicBody.hidden = authMode.value !== "basic";
|
||||
authUrl.required = loginUrl.required = authEnable.checked;
|
||||
authUrl.required = loginUrl.required = tinyauthSelected;
|
||||
for (const [id, editor] of Object.entries(forwardEditors)) {
|
||||
editor.body.hidden = id !== authMode.value;
|
||||
editor.url.required = id === authMode.value;
|
||||
if (editor.login) editor.login.required = id === authMode.value;
|
||||
}
|
||||
};
|
||||
authMode.addEventListener("change", syncAuth); syncAuth();
|
||||
g = group("vhResponsePolicy"); const server = value.server_header, diagnostics = value.diagnostic_headers;
|
||||
@@ -508,7 +554,9 @@ function collect() {
|
||||
content_type_policy: { enabled: yes("content-type-block-enabled"), blocked: lines(str("content-type-block-list")).map(value => value.toLowerCase()) },
|
||||
server_header: str("server-mode") === "inherit" ? null : { mode: str("server-mode"), value: str("server-value") },
|
||||
diagnostic_headers: yes("diag-inherit") ? null : { request_id: yes("diag-request_id") },
|
||||
tinyauth: { enabled: yes("auth-enabled"), url: str("auth-url"), login_url: str("auth-login"), allow_private: yes("auth-private"), redirect_forbidden: yes("auth-redirect-forbidden"), connect_timeout_seconds: num("auth-connect"), timeout_seconds: num("auth-timeout"), request_headers: lines(str("auth-request-headers")), response_headers: lines(str("auth-response-headers")), address_allowlist: lines(str("auth-allowlist")), tls_ca_pem: nullable("auth-ca") }, backends: members, routes: paths, acl, headers,
|
||||
tinyauth: { enabled: str("auth-mode") === "tinyauth", url: str("auth-url"), login_url: str("auth-login"), allow_private: yes("auth-private"), redirect_forbidden: yes("auth-redirect-forbidden"), connect_timeout_seconds: num("auth-connect"), timeout_seconds: num("auth-timeout"), request_headers: lines(str("auth-request-headers")), response_headers: lines(str("auth-response-headers")), address_allowlist: lines(str("auth-allowlist")), tls_ca_pem: nullable("auth-ca") },
|
||||
authelia: readForwardProvider("authelia"), authentik: readForwardProvider("authentik"),
|
||||
backends: members, routes: paths, acl, headers,
|
||||
load_balancing: {
|
||||
algorithm: str("algorithm"), slow_start_seconds: num("slow-start"), keepalive: { enabled: yes("keepalive"), idle_timeout_seconds: num("idle-timeout"), max_idle_per_host: num("max-idle"), tcp_keepalive_seconds: num("tcp-keepalive"), pool_recycle_requests: num("pool-recycle-requests"), pool_recycle_seconds: num("pool-recycle-seconds"), http1_max_buffer_bytes: num("http1-max-buffer") },
|
||||
retry: { max_retries: num("retry-max"), retry_connect_errors: yes("retry-connect"), retry_timeouts: yes("retry-timeouts"), retry_statuses: str("retry-statuses").split(/[\s,]+/).filter(Boolean).map(value => Number(value)) },
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// Shared, anchored Vhost popover for backend quick-view and upstream HTTP
|
||||
// protocol observations. Both consume existing live status without extra probes.
|
||||
let activeVhostBackendPopup = null;
|
||||
function closeVhostBackendPopup(returnFocus = false) {
|
||||
const current = activeVhostBackendPopup;
|
||||
@@ -390,6 +388,8 @@ function render() {
|
||||
const policy = el("div", undefined, "vhost-policy-pills");
|
||||
if (v.basic_auth?.enabled) policy.append(el("small", t("authBasic"), "pill"));
|
||||
if (v.tinyauth?.enabled) policy.append(el("small", "Tinyauth", "pill"));
|
||||
if (v.authelia?.enabled) policy.append(el("small", "Authelia", "pill"));
|
||||
if (v.authentik?.enabled) policy.append(el("small", "authentik", "pill"));
|
||||
if (v.rate_limit?.enabled) policy.append(el("small", t("vhRateEnabled"), "pill"));
|
||||
if (v.acl_profile_id) policy.append(el("small", `${t("vhAclProfile")}: ${v.acl_profile_id}`, "pill"));
|
||||
if (v.force_ssl) policy.append(el("small", t("vhForceSsl"), "pill"));
|
||||
@@ -573,7 +573,6 @@ form.addEventListener("submit", async event => {
|
||||
const value = collect(), next = structuredClone(envelope.config); next.vhosts ||= [];
|
||||
const debugIssues = debugOriginIssues(value.backends); if (debugIssues.length && !(await appConfirm(`${t("vhDebugOriginMismatch")}\n\n${debugIssues.map(member => member.id).join(", ")}`, { danger: true }))) return;
|
||||
const oldVhost = (envelope.config.vhosts || []).find(v => v.id === editing);
|
||||
// A previous acceptance must not silently authorize new hostnames or a different certificate.
|
||||
const certificateScope = item => JSON.stringify({
|
||||
certificate: item?.certificate_id || null,
|
||||
hosts: [...(item?.frontend?.hosts || [])].map(v => v.toLowerCase().replace(/\.$/, "")).sort(),
|
||||
|
||||
Reference in new issue
Block a user