"use strict"; (() => { let certificateApiState = null, certificateApiLoading = false; let certificateApiEditingKey = null, certificateApiDetailsKey = null; function certificateApiCredentials() { const values = certificateApiState?.credentials; if (Array.isArray(values)) return values; if (certificateApiState?.configured && certificateApiState?.key_id) return [{ key_id: certificateApiState.key_id, comment: "", created_at: certificateApiState.created_at, last_used_at: certificateApiState.last_used_at }]; return []; } function clearCertificateApiSecret() { $("certificate-api-secret").textContent = ""; $("certificate-api-secret-wrap").hidden = true; } function showCertificateApiSecret(token) { $("certificate-api-secret").textContent = token; $("certificate-api-secret-wrap").hidden = !token; } function resetCertificateApiForm() { certificateApiEditingKey = null; $("certificate-api-key-id").value = ""; $("certificate-api-key-id").readOnly = false; $("certificate-api-comment").value = ""; $("certificate-api-certificate-ids").value = ""; $("certificate-api-allow-deploy").checked = false; $("certificate-api-token").value = ""; $("certificate-api-token-field").hidden = false; $("certificate-api-edit-note").hidden = true; $("certificate-api-cancel-edit").hidden = true; const submit = $("certificate-api-save"); submit.dataset.i18n = "certificateApiRotate"; submit.textContent = t("certificateApiRotate"); certificateApiExample(); } function beginCertificateApiEdit(credential) { certificateApiEditingKey = credential.key_id; certificateApiDetailsKey = credential.key_id; $("certificate-api-key-id").value = credential.key_id; $("certificate-api-key-id").readOnly = true; $("certificate-api-comment").value = credential.comment || ""; $("certificate-api-certificate-ids").value = (credential.certificate_ids || []).join("\n"); $("certificate-api-allow-deploy").checked = !!credential.allow_deploy; $("certificate-api-token").value = ""; $("certificate-api-token-field").hidden = true; $("certificate-api-edit-note").hidden = false; $("certificate-api-cancel-edit").hidden = false; const submit = $("certificate-api-save"); submit.dataset.i18n = "certificateApiSave"; submit.textContent = t("certificateApiSave"); certificateApiExample(); $("certificate-api-form").scrollIntoView?.({ block: "start", behavior: "smooth" }); $("certificate-api-comment").focus?.(); } function setCertificateApiCredential(credential) { if (!certificateApiState) return; const entries = certificateApiCredentials(); certificateApiState.credentials = entries.some(item => item.key_id === credential.key_id) ? entries.map(item => item.key_id === credential.key_id ? credential : item) : [...entries, credential]; certificateApiState.configured = certificateApiState.credentials.length > 0; } function certificateApiExample() { const key = $("certificate-api-key-id").value.trim() || certificateApiCredentials()[0]?.key_id || "certbot-main"; const enteredIds = $("certificate-api-certificate-ids").value.split(/[\n,]+/).map(value => value.trim()).filter(Boolean); const selected = enteredIds[0] || certificateApiCredentials()[0]?.certificate_ids?.find(value => value !== "*") || "CERTIFICATE_ID"; const reload = `curl --fail --show-error --request POST \ --header 'Authorization: Bearer $RUSTPROXY_CERT_TOKEN' \ --header 'X-RustProxy-Certificate-Key: ${key}' \ ${location.origin}/api/certificates/${selected}/reload`; if (!$("certificate-api-allow-deploy").checked) { $("certificate-api-curl").textContent = reload; return; } $("certificate-api-curl").textContent = `${reload} # Optional higher-privilege deploy for the same allowlisted certificate IDs: curl --fail --show-error --request POST \ --header 'Authorization: Bearer $RUSTPROXY_CERT_TOKEN' \ --header 'X-RustProxy-Certificate-Key: ${key}' \ --header 'Content-Type: application/json' \ --data @deploy.json \ ${location.origin}/api/certificates/deploy`; } function certificateApiDetailNode(credential) { const detail = el("div", undefined, "certificate-api-details"); const item = (label, value) => { const line = el("div", undefined, "certificate-api-detail-line"); line.append(el("span", `${label}:`, "fine"), el("strong", String(value), "mono")); detail.append(line); }; item(t("certificateApiKeyId"), credential.key_id); item(t("certificateApiComment"), credential.comment || "-"); item(t("certificateApiCreatedAt"), formatDateTime(Number(credential.created_at) * 1000)); item(t("certificateApiLastUsed"), credential.last_used_at ? formatDateTime(Number(credential.last_used_at) * 1000) : t("certificateApiNeverUsed")); item(t("certificateApiPermissions"), credential.allow_deploy ? t("certificateApiScopeDeployReload") : t("certificateApiScopeReloadOnly")); item(t("certificateApiCertificateIds"), (credential.certificate_ids || []).join(", ")); item(t("certificateApiTokenLabel"), t("certificateApiTokenHidden")); return detail; } function renderCertificateApi() { const state = certificateApiState, target = $("certificate-api-credentials"); if (!state) { $("certificate-api-status").textContent = t("certificateApiUnknown"); if (target) target.replaceChildren(); certificateApiExample(); return; } const credentials = certificateApiCredentials(), max = Number(state.max_credentials) || 32; $("certificate-api-status").textContent = credentials.length ? t("certificateApiConfiguredCount").replace("{count}", credentials.length).replace("{max}", max) : t("certificateApiDisabled"); $("certificate-api-revoke-all").disabled = !credentials.length; if (target) { target.replaceChildren(); for (const credential of credentials) { const container = el("div", undefined, "certificate-api-credential-item"); const row = el("div", undefined, "certificate-api-credential-row"); const info = el("div", undefined, "certificate-api-credential-info"); info.append(el("b", credential.key_id || "-", "mono")); if (credential.comment) info.append(el("span", credential.comment, "fine")); const scope = credential.allow_deploy ? t("certificateApiScopeDeployReload") : t("certificateApiScopeReloadOnly"); const ids = Array.isArray(credential.certificate_ids) ? credential.certificate_ids.join(", ") : "-"; info.append(el("span", `${scope} ยท ${t("certificateApiAllowedIds")}: ${ids}`, "fine mono")); const lastUsed = credential.last_used_at ? formatDateTime(Number(credential.last_used_at) * 1000) : t("certificateApiNeverUsed"); info.append(el("span", `${t("certificateApiLastUsed")}: ${lastUsed}`, "fine")); const actions = el("div", undefined, "certificate-api-row-actions"); const path = `/api/certificate-api/${encodeURIComponent(credential.key_id)}`; const preview = action(t(certificateApiDetailsKey === credential.key_id ? "certificateApiHide" : "certificateApiView"), async () => { if (certificateApiDetailsKey === credential.key_id) { certificateApiDetailsKey = null; renderCertificateApi(); return; } preview.disabled = true; clearCertificateApiSecret(); try { const data = await api(path); setCertificateApiCredential(data.credential); certificateApiDetailsKey = credential.key_id; renderCertificateApi(); } catch (error) { toast(error.message, true); preview.disabled = false; } }); const edit = action(t("certificateApiEdit"), async () => { edit.disabled = true; clearCertificateApiSecret(); try { const data = await api(path); setCertificateApiCredential(data.credential); beginCertificateApiEdit(data.credential); renderCertificateApi(); } catch (error) { toast(error.message, true); edit.disabled = false; } }); const regenerate = action(t("certificateApiRegenerate"), async () => { if (!await appConfirm(t("certificateApiRegenerateConfirm").replace("{key}", credential.key_id), { danger: true })) return; regenerate.disabled = true; clearCertificateApiSecret(); try { const data = await api(`${path}/regenerate`, { method: "POST" }); setCertificateApiCredential(data.credential); // Never put the bearer token into the persistent credential list/state. showCertificateApiSecret(data.token || ""); renderCertificateApi(); toast(t("certificateApiRegenerated")); } catch (error) { toast(error.message, true); regenerate.disabled = false; } }); const remove = action(t("certificateApiRevoke"), async () => { if (!await appConfirm(t("certificateApiRevokeOneConfirm").replace("{key}", credential.key_id), { danger: true })) return; remove.disabled = true; try { await api(path, { method: "DELETE" }); certificateApiState.credentials = certificateApiCredentials().filter(value => value.key_id !== credential.key_id); certificateApiState.configured = certificateApiState.credentials.length > 0; if (certificateApiEditingKey === credential.key_id) resetCertificateApiForm(); if (certificateApiDetailsKey === credential.key_id) certificateApiDetailsKey = null; clearCertificateApiSecret(); renderCertificateApi(); toast(t("certificateApiRevoked")); } catch (error) { toast(error.message, true); remove.disabled = false; } }); remove.classList.add("danger"); actions.append(preview, edit, regenerate, remove); row.append(info, actions); container.append(row); if (certificateApiDetailsKey === credential.key_id) container.append(certificateApiDetailNode(credential)); target.append(container); } } certificateApiExample(); } async function loadCertificateApi(force = false) { if (!authenticated || certificateApiLoading || (certificateApiState && !force)) return; certificateApiLoading = true; try { certificateApiState = await api("/api/certificate-api"); renderCertificateApi(); } catch (error) { $("certificate-api-status").textContent = error.message; } finally { certificateApiLoading = false; } } $("certificate-api-form").addEventListener("submit", async event => { event.preventDefault(); const button = $("certificate-api-save"); button.disabled = true; try { const keyId = $("certificate-api-key-id").value.trim(); const comment = $("certificate-api-comment").value.trim(); const certificateIds = $("certificate-api-certificate-ids").value.split(/[\n,]+/).map(value => value.trim()).filter(Boolean); if (!certificateIds.length) throw fieldError($("certificate-api-certificate-ids"), t("certificateApiCertificateIdsRequired")); const allowDeploy = $("certificate-api-allow-deploy").checked; if (certificateApiEditingKey) { const result = await api(`/api/certificate-api/${encodeURIComponent(certificateApiEditingKey)}`, { method: "PATCH", body: JSON.stringify({ comment, certificate_ids: certificateIds, allow_deploy: allowDeploy }) }); setCertificateApiCredential(result.credential); resetCertificateApiForm(); renderCertificateApi(); toast(t("certificateApiSaved")); } else { const token = $("certificate-api-token").value.trim(); const result = await api("/api/certificate-api", { method: "PUT", body: JSON.stringify({ key_id: keyId || null, token: token || null, comment, certificate_ids: certificateIds, allow_deploy: allowDeploy }) }); certificateApiState = { ...result }; delete certificateApiState.token; delete certificateApiState.credential; resetCertificateApiForm(); showCertificateApiSecret(result.token || ""); renderCertificateApi(); toast(t("certificateApiRotated")); } } catch (error) { showFieldError(error, $("certificate-api-form")); toast(error.message, true); } finally { button.disabled = false; } }); $("certificate-api-cancel-edit").addEventListener("click", resetCertificateApiForm); $("certificate-api-revoke-all").addEventListener("click", async () => { if (!await appConfirm(t("certificateApiRevokeAllConfirm"), { danger: true })) return; const button = $("certificate-api-revoke-all"); button.disabled = true; try { await api("/api/certificate-api", { method: "DELETE" }); certificateApiState = { configured: false, credentials: [], max_credentials: certificateApiState?.max_credentials || 32, scopes: certificateApiState?.scopes || [] }; certificateApiDetailsKey = null; resetCertificateApiForm(); clearCertificateApiSecret(); renderCertificateApi(); toast(t("certificateApiRevoked")); } catch (error) { toast(error.message, true); } finally { button.disabled = false; } }); $("certificate-api-key-id").addEventListener("input", certificateApiExample); $("certificate-api-certificate-ids").addEventListener("input", certificateApiExample); $("certificate-api-allow-deploy").addEventListener("change", certificateApiExample); window.rustProxyCertificateApi = { render: renderCertificateApi, load: loadCertificateApi, reset() { certificateApiState = null; certificateApiLoading = false; certificateApiDetailsKey = null; resetCertificateApiForm(); clearCertificateApiSecret(); $("certificate-api-credentials").replaceChildren(); } }; renderCertificateApi(); })();