/* Mail and password-reset administration view. */ class MailAdmin { /** * goal : Connect mail settings, test delivery and TLS controls to the API. * pre : The mail administration form exists in the document. * post : Save, test and TLS-change events are handled by this controller. * result : A directly usable mail administration controller. */ constructor(api, translate) { this.api = api; this.translate = translate; this.form = document.getElementById("mail-settings-form"); this.status = document.getElementById("mail-settings-status"); this.testButton = document.getElementById("mail-test-button"); this.tls = document.getElementById("mail-smtp-tls"); this.acceptUntrusted = document.getElementById( "mail-smtp-accept-untrusted-certificates"); this.form.addEventListener("submit", (event) => this.#save(event)); this.testButton.addEventListener("click", () => this.#test()); this.tls.addEventListener("change", () => this.#syncTlsControls()); } /** * goal : Load stored mail settings and initialize the test recipient. * pre : The current session has administrator permission. * post : All form controls represent the stored server configuration. * result : A promise that resolves after the form has been initialized. */ async load(currentUserEmail = "") { const settings = await this.api("/api/admin/mail-settings"); document.getElementById("mail-public-url").value = settings.publicUrl || window.location.origin; document.getElementById("mail-smtp-host").value = settings.smtpHost || ""; document.getElementById("mail-smtp-port").value = settings.smtpPort || "587"; document.getElementById("mail-smtp-from").value = settings.smtpFrom || ""; document.getElementById("mail-smtp-user").value = settings.smtpUser || ""; document.getElementById("mail-smtp-password").value = ""; this.tls.checked = settings.smtpTls !== false; this.acceptUntrusted.checked = settings.smtpAcceptUntrustedCertificates === true; document.getElementById("mail-reset-limit").value = settings.resetLimit || "2"; document.getElementById("mail-test-recipient").value = currentUserEmail; document.getElementById("mail-password-help").textContent = settings.hasPassword ? this.translate( "smtp-password-kept", "A password is stored; leave empty to keep it.") : ""; this.status.textContent = ""; this.acceptUntrusted.disabled = !this.tls.checked; } /** Read the mail form using the public server API field names. */ #formData() { return { publicUrl: document.getElementById("mail-public-url").value, smtpHost: document.getElementById("mail-smtp-host").value, smtpPort: document.getElementById("mail-smtp-port").value, smtpFrom: document.getElementById("mail-smtp-from").value, smtpUser: document.getElementById("mail-smtp-user").value, smtpPassword: document.getElementById("mail-smtp-password").value, smtpTls: this.tls.checked, smtpAcceptUntrustedCertificates: this.acceptUntrusted.checked, resetLimit: document.getElementById("mail-reset-limit").value }; } /** Store the current mail settings and retain an omitted password. */ async #save(event) { event.preventDefault(); try { await this.api("/api/admin/mail-settings", { method: "PUT", body: JSON.stringify(this.#formData()) }); document.getElementById("mail-smtp-password").value = ""; document.getElementById("mail-password-help").textContent = this.translate( "smtp-password-kept", "A password is stored; leave empty to keep it."); this.status.textContent = this.translate("saved", "Saved"); } catch (error) { this.status.textContent = error.message; } } /** Send one test message using the unsaved values currently in the form. */ async #test() { const recipient = document.getElementById("mail-test-recipient").value.trim(); this.testButton.disabled = true; this.status.textContent = this.translate( "sending-test-mail", "Sending test email…"); try { await this.api("/api/admin/mail-settings/test", { method: "POST", body: JSON.stringify({ ...this.#formData(), recipient }) }); this.status.textContent = this.translate( "test-mail-sent", "Test email accepted by SMTP server"); } catch (error) { console.error("SMTP test failed", error); this.status.textContent = this.#smtpErrorMessage(error); } finally { this.testButton.disabled = false; } } /** Translate known TLS failures and retain other server error messages. */ #smtpErrorMessage(error) { const message = error && error.message ? error.message : String(error); const certificateFailure = message.includes("certificate verify failed") || message.includes("TLS certificate verification failed"); if (certificateFailure) { return this.translate( "smtp-certificate-verification-failed", "The SMTP server certificate could not be verified. Install a valid certificate, or select the local-server exception if this is a trusted local SMTP server."); } if (message.includes("no protocols available")) { return this.translate( "smtp-no-modern-tls", "The SMTP connection attempted an obsolete TLS protocol. Install the current Racket Wiki version, which negotiates modern TLS automatically."); } return message; } /** Disable the certificate exception whenever STARTTLS is disabled. */ #syncTlsControls() { this.acceptUntrusted.disabled = !this.tls.checked; if (this.acceptUntrusted.disabled) this.acceptUntrusted.checked = false; } } export { MailAdmin };