From ca0ad630d285ac5aa3712d178d3070e8bf9394b5 Mon Sep 17 00:00:00 2001 From: nahakubuilder Date: Sat, 15 Aug 2026 21:51:43 +0100 Subject: [PATCH] change admin menu url to smtp-server --- README.md | 6 ++--- ...4a2b3ee22e4fe7de81b5e85902c26ddbf941a.json | 2 +- graphify-out/graph.json | 2 +- internal/webui/mailbox_apppasswords_test.go | 4 +-- internal/webui/mfa_enforcement_test.go | 2 +- internal/webui/settings_scope_test.go | 20 +++++++------- internal/webui/settings_test.go | 2 +- internal/webui/templates/account.html | 12 ++++----- internal/webui/templates/add_admin.html | 2 +- internal/webui/templates/add_domain.html | 2 +- internal/webui/templates/add_ip.html | 2 +- internal/webui/templates/add_mailbox.html | 2 +- internal/webui/templates/add_sender.html | 2 +- internal/webui/templates/admins.html | 10 +++---- internal/webui/templates/base.html | 10 +++---- internal/webui/templates/blacklist.html | 12 ++++----- internal/webui/templates/dashboard.html | 24 ++++++++--------- internal/webui/templates/dkim.html | 20 +++++++------- internal/webui/templates/domains.html | 12 ++++----- internal/webui/templates/edit_admin.html | 2 +- internal/webui/templates/edit_dkim.html | 4 +-- internal/webui/templates/edit_domain.html | 4 +-- internal/webui/templates/edit_ip.html | 2 +- internal/webui/templates/edit_mailbox.html | 10 +++---- internal/webui/templates/edit_sender.html | 2 +- internal/webui/templates/error.html | 2 +- internal/webui/templates/first_login.html | 2 +- internal/webui/templates/ips.html | 14 +++++----- internal/webui/templates/letsencrypt.html | 14 +++++----- internal/webui/templates/login.html | 6 ++--- internal/webui/templates/login_mfa.html | 12 ++++----- internal/webui/templates/logs.html | 20 +++++++------- internal/webui/templates/mailbox_aliases.html | 6 ++--- .../webui/templates/mailbox_apppasswords.html | 8 +++--- internal/webui/templates/mailbox_lists.html | 10 +++---- internal/webui/templates/mailbox_rules.html | 6 ++--- internal/webui/templates/mailboxes.html | 18 ++++++------- .../webui/templates/mfa_setup_required.html | 14 +++++----- internal/webui/templates/senders.html | 12 ++++----- internal/webui/templates/settings.html | 14 +++++----- internal/webui/templates/sidebar_email.html | 26 +++++++++---------- internal/webui/templates/totp_setup.html | 8 +++--- .../webui/templates/view_message_content.html | 6 ++--- internal/webui/templates/webmail_login.html | 2 +- internal/webui/webui.go | 4 +-- 45 files changed, 188 insertions(+), 188 deletions(-) diff --git a/README.md b/README.md index 1016408..457a4f2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ a Python venv + separate services. drafts, search, keyboard shortcuts, conversation grouping, recipient autocomplete, filter-rule management, and PGP (OpenPGP encrypt/decrypt/sign/verify) and S/MIME (sign/verify) support per mailbox. -- **Admin dashboard** (`/pymta-manager`) — manage domains, senders, mailboxes, DKIM +- **Admin dashboard** (`/smtp-server`) — manage domains, senders, mailboxes, DKIM keys, IP whitelisting, TLS (self-signed/custom, or up to two simultaneous Let's Encrypt certificates — DNS-01 via Cloudflare/Route53/DigitalOcean/Google Cloud DNS, and HTTP-01 for domains you don't manage DNS for, optionally covering the server's own IP @@ -75,11 +75,11 @@ directory, it generates: on every subsequent start — back these up like any other secret (losing the mailstore master key makes all stored mail unrecoverable, even for admins). -From there: log into `/pymta-manager`, add a domain (and complete its DNS ownership +From there: log into `/smtp-server`, add a domain (and complete its DNS ownership verification), add a mailbox or sender, and you're sending/receiving. The bare `/` root redirects to the webmail login (`/webmail/login`) by default, since most visitors are mailbox owners, not admins — there's a "Login as Admin" link from there to -`/pymta-manager`. +`/smtp-server`. Default ports (all configurable in `settings.ini`): SMTP `25`, direct-TLS SMTP `465`, IMAP `143`, direct-TLS IMAP `993` — the real standard mail ports, so binding them diff --git a/graphify-out/cache/semantic/pd5fd89c46bb5/c9fdca732d5d23867a176f849a94a2b3ee22e4fe7de81b5e85902c26ddbf941a.json b/graphify-out/cache/semantic/pd5fd89c46bb5/c9fdca732d5d23867a176f849a94a2b3ee22e4fe7de81b5e85902c26ddbf941a.json index 316d2b3..b4aa984 100644 --- a/graphify-out/cache/semantic/pd5fd89c46bb5/c9fdca732d5d23867a176f849a94a2b3ee22e4fe7de81b5e85902c26ddbf941a.json +++ b/graphify-out/cache/semantic/pd5fd89c46bb5/c9fdca732d5d23867a176f849a94a2b3ee22e4fe7de81b5e85902c26ddbf941a.json @@ -1 +1 @@ -{"nodes": [{"id": "internal_webui_templates_account_account_settings_page", "label": "Account Settings Page", "file_type": "document", "source_file": "internal/webui/templates/account.html", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "internal_webui_templates_account_totp_mfa_section", "label": "TOTP MFA Section", "file_type": "document", "source_file": "internal/webui/templates/account.html", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "internal_webui_templates_account_passkey_management", "label": "Passkey / WebAuthn Management", "file_type": "document", "source_file": "internal/webui/templates/account.html", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Uses the browser navigator.credentials.create() WebAuthn API directly against /pymta-manager/account/passkey/begin and /finish, base64url-encoding challenge/credential buffers by hand since no client library is loaded."}], "edges": [{"source": "internal_webui_templates_account_account_settings_page", "target": "internal_webui_templates_base_base_layout", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "internal/webui/templates/account.html", "source_location": null, "weight": 1.0}, {"source": "internal_webui_templates_account_account_settings_page", "target": "internal_webui_templates_account_totp_mfa_section", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "internal/webui/templates/account.html", "source_location": null, "weight": 1.0}, {"source": "internal_webui_templates_account_account_settings_page", "target": "internal_webui_templates_account_passkey_management", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "internal/webui/templates/account.html", "source_location": null, "weight": 1.0}, {"source": "internal_webui_templates_account_totp_mfa_section", "target": "readme_totp_mfa", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "internal/webui/templates/account.html", "source_location": null, "weight": 1.0}, {"source": "internal_webui_templates_account_passkey_management", "target": "readme_webauthn_passkeys", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "internal/webui/templates/account.html", "source_location": null, "weight": 1.0}], "hyperedges": [{"id": "mfa_second_factor_flow", "label": "Optional MFA / Second-Factor Setup Flow", "nodes": ["readme_admin_dashboard_login", "internal_webui_templates_account_account_settings_page", "internal_webui_templates_account_totp_mfa_section", "internal_webui_templates_account_passkey_management"], "relation": "participate_in", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": "internal/webui/templates/account.html"}]} \ No newline at end of file +{"nodes": [{"id": "internal_webui_templates_account_account_settings_page", "label": "Account Settings Page", "file_type": "document", "source_file": "internal/webui/templates/account.html", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "internal_webui_templates_account_totp_mfa_section", "label": "TOTP MFA Section", "file_type": "document", "source_file": "internal/webui/templates/account.html", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "internal_webui_templates_account_passkey_management", "label": "Passkey / WebAuthn Management", "file_type": "document", "source_file": "internal/webui/templates/account.html", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Uses the browser navigator.credentials.create() WebAuthn API directly against /smtp-server/account/passkey/begin and /finish, base64url-encoding challenge/credential buffers by hand since no client library is loaded."}], "edges": [{"source": "internal_webui_templates_account_account_settings_page", "target": "internal_webui_templates_base_base_layout", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "internal/webui/templates/account.html", "source_location": null, "weight": 1.0}, {"source": "internal_webui_templates_account_account_settings_page", "target": "internal_webui_templates_account_totp_mfa_section", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "internal/webui/templates/account.html", "source_location": null, "weight": 1.0}, {"source": "internal_webui_templates_account_account_settings_page", "target": "internal_webui_templates_account_passkey_management", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "internal/webui/templates/account.html", "source_location": null, "weight": 1.0}, {"source": "internal_webui_templates_account_totp_mfa_section", "target": "readme_totp_mfa", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "internal/webui/templates/account.html", "source_location": null, "weight": 1.0}, {"source": "internal_webui_templates_account_passkey_management", "target": "readme_webauthn_passkeys", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "internal/webui/templates/account.html", "source_location": null, "weight": 1.0}], "hyperedges": [{"id": "mfa_second_factor_flow", "label": "Optional MFA / Second-Factor Setup Flow", "nodes": ["readme_admin_dashboard_login", "internal_webui_templates_account_account_settings_page", "internal_webui_templates_account_totp_mfa_section", "internal_webui_templates_account_passkey_management"], "relation": "participate_in", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": "internal/webui/templates/account.html"}]} \ No newline at end of file diff --git a/graphify-out/graph.json b/graphify-out/graph.json index 4d376ce..92df710 100644 --- a/graphify-out/graph.json +++ b/graphify-out/graph.json @@ -10719,7 +10719,7 @@ "captured_at": null, "author": null, "contributor": null, - "rationale": "Uses the browser navigator.credentials.create() WebAuthn API directly against /pymta-manager/account/passkey/begin and /finish, base64url-encoding challenge/credential buffers by hand since no client library is loaded.", + "rationale": "Uses the browser navigator.credentials.create() WebAuthn API directly against /smtp-server/account/passkey/begin and /finish, base64url-encoding challenge/credential buffers by hand since no client library is loaded.", "id": "internal_webui_templates_account_passkey_management", "community": 16, "community_name": "Admin Dashboard & Deployment Docs", diff --git a/internal/webui/mailbox_apppasswords_test.go b/internal/webui/mailbox_apppasswords_test.go index e565a54..479889c 100644 --- a/internal/webui/mailbox_apppasswords_test.go +++ b/internal/webui/mailbox_apppasswords_test.go @@ -98,7 +98,7 @@ func TestAddAppPasswordSetsRevealCookieNotFlash(t *testing.T) { mboxID := mailboxes[0].ID form := url.Values{"label": {"laptop"}, "expires_preset": {"never"}} - req := httptest.NewRequest(http.MethodPost, "/pymta-manager/mailboxes/"+itoa(mboxID)+"/apppasswords/add", strings.NewReader(form.Encode())) + req := httptest.NewRequest(http.MethodPost, "/smtp-server/mailboxes/"+itoa(mboxID)+"/apppasswords/add", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.AddCookie(cookie) rec := httptest.NewRecorder() @@ -125,7 +125,7 @@ func TestAddAppPasswordSetsRevealCookieNotFlash(t *testing.T) { // Following the redirect (as the browser would) should render the reveal modal // with the secret, and clear the one-time cookie. - req2 := httptest.NewRequest(http.MethodGet, "/pymta-manager/mailboxes/"+itoa(mboxID)+"/apppasswords", nil) + req2 := httptest.NewRequest(http.MethodGet, "/smtp-server/mailboxes/"+itoa(mboxID)+"/apppasswords", nil) req2.AddCookie(cookie) req2.AddCookie(revealCookie) rec2 := httptest.NewRecorder() diff --git a/internal/webui/mfa_enforcement_test.go b/internal/webui/mfa_enforcement_test.go index 43fc816..c15400b 100644 --- a/internal/webui/mfa_enforcement_test.go +++ b/internal/webui/mfa_enforcement_test.go @@ -56,7 +56,7 @@ func TestAdminMFAEnforcementForcesIsolatedSetupThenReleases(t *testing.T) { if !strings.Contains(body, "Two-factor authentication required") { t.Error("expected the MFA-required heading on /mfa-setup") } - if strings.Contains(body, "sidebar") || strings.Contains(body, `href="/pymta-manager/domains"`) { + if strings.Contains(body, "sidebar") || strings.Contains(body, `href="/smtp-server/domains"`) { t.Error("expected no sidebar/navigation on the isolated setup page") } diff --git a/internal/webui/settings_scope_test.go b/internal/webui/settings_scope_test.go index 370ed69..7227467 100644 --- a/internal/webui/settings_scope_test.go +++ b/internal/webui/settings_scope_test.go @@ -24,12 +24,12 @@ func TestScopedAdminCannotAccessServerSettingsOrLetsEncrypt(t *testing.T) { routes := []struct { method, path string }{ - {http.MethodGet, "/pymta-manager/settings"}, - {http.MethodPost, "/pymta-manager/settings_update"}, - {http.MethodGet, "/pymta-manager/letsencrypt"}, - {http.MethodPost, "/pymta-manager/letsencrypt/save"}, - {http.MethodPost, "/pymta-manager/letsencrypt/obtain"}, - {http.MethodGet, "/pymta-manager/api/settings/get_public_ip"}, + {http.MethodGet, "/smtp-server/settings"}, + {http.MethodPost, "/smtp-server/settings_update"}, + {http.MethodGet, "/smtp-server/letsencrypt"}, + {http.MethodPost, "/smtp-server/letsencrypt/save"}, + {http.MethodPost, "/smtp-server/letsencrypt/obtain"}, + {http.MethodGet, "/smtp-server/api/settings/get_public_ip"}, } for _, rt := range routes { req := httptest.NewRequest(rt.method, rt.path, nil) @@ -43,7 +43,7 @@ func TestScopedAdminCannotAccessServerSettingsOrLetsEncrypt(t *testing.T) { // A global admin must still reach these routes normally. globalCookie := loginSession(t, app) - req := httptest.NewRequest(http.MethodGet, "/pymta-manager/settings", nil) + req := httptest.NewRequest(http.MethodGet, "/smtp-server/settings", nil) req.AddCookie(globalCookie) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) @@ -61,7 +61,7 @@ func TestScopedAdminSidebarHidesServerWideLinks(t *testing.T) { domains, _ := app.DB.ListDomains() cookie := scopedLogin(t, app, "scoped-sidebar-admin", []int64{domains[0].ID}) - req := httptest.NewRequest(http.MethodGet, "/pymta-manager/", nil) + req := httptest.NewRequest(http.MethodGet, "/smtp-server/", nil) req.AddCookie(cookie) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) @@ -69,10 +69,10 @@ func TestScopedAdminSidebarHidesServerWideLinks(t *testing.T) { t.Fatalf("dashboard: status=%d body=%s", rec.Code, rec.Body.String()) } body := rec.Body.String() - if strings.Contains(body, `href="/pymta-manager/settings"`) { + if strings.Contains(body, `href="/smtp-server/settings"`) { t.Error("scoped admin's sidebar should not link to Server Settings") } - if strings.Contains(body, `href="/pymta-manager/letsencrypt"`) { + if strings.Contains(body, `href="/smtp-server/letsencrypt"`) { t.Error("scoped admin's sidebar should not link to Let's Encrypt") } } diff --git a/internal/webui/settings_test.go b/internal/webui/settings_test.go index e755eee..953e433 100644 --- a/internal/webui/settings_test.go +++ b/internal/webui/settings_test.go @@ -35,7 +35,7 @@ func TestSettingsUpdateSavesUppercaseDefinedKeys(t *testing.T) { cookie := loginSession(t, app) form := url.Values{"Server.smtp_port": {"2525"}} - req := httptest.NewRequest(http.MethodPost, "/pymta-manager/settings_update", strings.NewReader(form.Encode())) + req := httptest.NewRequest(http.MethodPost, "/smtp-server/settings_update", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.AddCookie(cookie) rec := httptest.NewRecorder() diff --git a/internal/webui/templates/account.html b/internal/webui/templates/account.html index a656472..3ec45ee 100644 --- a/internal/webui/templates/account.html +++ b/internal/webui/templates/account.html @@ -10,7 +10,7 @@

Username: {{.user.Username}}


Change password
-
+
@@ -36,12 +36,12 @@
{{if .user.TOTPEnabled}}

Enabled

- + {{else}}

Not enabled. Add an authenticator app (Google Authenticator, 1Password, etc.) as an optional second factor.

-
+
{{end}} @@ -56,7 +56,7 @@ {{range .passkeys}}
  • {{.Name}} added {{strftime "%Y-%m-%d" .CreatedAt}} -
    +
  • @@ -96,7 +96,7 @@ try { const name = prompt('Name this passkey (e.g. "YubiKey", "MacBook Touch ID"):', 'Passkey') || 'Passkey'; - const beginResp = await fetch('/pymta-manager/account/passkey/begin', { method: 'POST' }); + const beginResp = await fetch('/smtp-server/account/passkey/begin', { method: 'POST' }); if (!beginResp.ok) throw new Error((await beginResp.json()).error || 'Could not start passkey registration'); const options = await beginResp.json(); @@ -119,7 +119,7 @@ }, }; - const finishResp = await fetch('/pymta-manager/account/passkey/finish?name=' + encodeURIComponent(name), { + const finishResp = await fetch('/smtp-server/account/passkey/finish?name=' + encodeURIComponent(name), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!finishResp.ok) throw new Error((await finishResp.json()).error || 'Could not save passkey'); diff --git a/internal/webui/templates/add_admin.html b/internal/webui/templates/add_admin.html index 90f0219..1d698cd 100644 --- a/internal/webui/templates/add_admin.html +++ b/internal/webui/templates/add_admin.html @@ -45,7 +45,7 @@
    - Back + Back
    diff --git a/internal/webui/templates/add_domain.html b/internal/webui/templates/add_domain.html index 357fb53..ad745a4 100644 --- a/internal/webui/templates/add_domain.html +++ b/internal/webui/templates/add_domain.html @@ -24,7 +24,7 @@
    - Back to Domains + Back to Domains
    diff --git a/internal/webui/templates/add_ip.html b/internal/webui/templates/add_ip.html index c95a134..67423b9 100644 --- a/internal/webui/templates/add_ip.html +++ b/internal/webui/templates/add_ip.html @@ -51,7 +51,7 @@
    - Back to IP List + Back to IP List
    diff --git a/internal/webui/templates/add_mailbox.html b/internal/webui/templates/add_mailbox.html index 75d94ac..3b00352 100644 --- a/internal/webui/templates/add_mailbox.html +++ b/internal/webui/templates/add_mailbox.html @@ -32,7 +32,7 @@
    Leave blank to use the domain's default quota.
    - Back to Mailboxes + Back to Mailboxes
    diff --git a/internal/webui/templates/add_sender.html b/internal/webui/templates/add_sender.html index d89115d..e5b6acf 100644 --- a/internal/webui/templates/add_sender.html +++ b/internal/webui/templates/add_sender.html @@ -48,7 +48,7 @@
    - Back to Senders + Back to Senders
    diff --git a/internal/webui/templates/admins.html b/internal/webui/templates/admins.html index 6cfb503..a40974e 100644 --- a/internal/webui/templates/admins.html +++ b/internal/webui/templates/admins.html @@ -4,7 +4,7 @@ {{define "content"}}

    Admins

    - Add Admin + Add Admin
    @@ -40,12 +40,12 @@ {{else}}
    {{if not $u.IsGlobalAdmin}} - + {{end}} -
    +
    -
    +
    @@ -61,7 +61,7 @@

    No delegated admins yet

    Add an admin and assign them the domains they should manage

    - Add Your First Admin + Add Your First Admin
    {{end}} diff --git a/internal/webui/templates/base.html b/internal/webui/templates/base.html index 7933625..874ec79 100644 --- a/internal/webui/templates/base.html +++ b/internal/webui/templates/base.html @@ -13,8 +13,8 @@ } - - + + - +