package webui import ( "net/http" "net/http/httptest" "net/url" "strings" "testing" ) // TestSettingsRejectsEnablingAdminMFAWithoutOwnMFA guards against a real self-lockout // bug: enabling enforce_admin_mfa immediately blocks every admin route (including // /settings itself) for any admin without their own MFA configured. Without this // precondition, an admin could flip the toggle on and lock themselves out with no way // back in short of editing the database directly. func TestSettingsRejectsEnablingAdminMFAWithoutOwnMFA(t *testing.T) { app := newTestApp(t) mux := app.Mux() cookie := loginSession(t, app) // loginSession's admin has no MFA configured form := baseSettingsForm() form.Set("Auth.enforce_admin_mfa", "true") req := httptest.NewRequest(http.MethodPost, Prefix+"/settings_update", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.AddCookie(cookie) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) if rec.Code != http.StatusFound { t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String()) } if got := app.Cfg.Section("Auth").Key("enforce_admin_mfa").Value(); got != "false" { t.Fatalf("enforce_admin_mfa = %q, want unchanged (still false) since the admin has no MFA", got) } // The same admin must still be able to reach every other page — the whole point // of rejecting the save is that nothing actually changed. req = httptest.NewRequest(http.MethodGet, Prefix+"/domains", nil) req.AddCookie(cookie) rec = httptest.NewRecorder() mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected /domains still reachable, got %d", rec.Code) } } // TestSettingsAllowsEnablingAdminMFAWithOwnMFA confirms the precondition isn't just a // blanket rejection — an admin who already has MFA set up can turn enforcement on. func TestSettingsAllowsEnablingAdminMFAWithOwnMFA(t *testing.T) { app := newTestApp(t) mux := app.Mux() cookie := loginSession(t, app) sess, err := app.DB.GetSession(cookie.Value) if err != nil || sess == nil { t.Fatal(err) } if err := app.DB.SetAdminTOTPSecret(sess.UserID, "JBSWY3DPEHPK3PXP", true); err != nil { t.Fatal(err) } form := baseSettingsForm() form.Set("Auth.enforce_admin_mfa", "true") req := httptest.NewRequest(http.MethodPost, Prefix+"/settings_update", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.AddCookie(cookie) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) if rec.Code != http.StatusFound { t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String()) } if got := app.Cfg.Section("Auth").Key("enforce_admin_mfa").Value(); got != "true" { t.Fatalf("enforce_admin_mfa = %q, want true", got) } } // baseSettingsForm returns a full, valid settings_update submission (every field // unchanged from newTestApp's fixture) — settingsUpdate iterates every existing ini // key and only touches ones present in the form, but real form submissions always // include every field on the page, so tests should too. func baseSettingsForm() url.Values { return url.Values{ "Server.smtp_port": {"4025"}, "Server.smtp_tls_port": {"40465"}, "Server.web_http_port": {"5000"}, "Server.web_https_port": {"5001"}, "Server.bind_ip": {"0.0.0.0"}, "Server.time_zone": {"UTC"}, "Server.hostname": {"mail.example.com"}, "Server.helo_hostname": {"mail.example.com"}, "Server.server_banner": {""}, "Database.database_url": {"sqlite:///server_data/smtp_server.db"}, "Logging.log_level": {"INFO"}, "Logging.hide_info_aiosmtpd": {"true"}, "Relay.relay_timeout": {"30"}, "TLS.tls_cert_file": {"ssl_certs/server.crt"}, "TLS.tls_key_file": {"ssl_certs/server.key"}, "DKIM.dkim_key_size": {"2048"}, "DKIM.spf_server_ip": {"192.168.1.1"}, "Attachments.attachments_path": {"server_data/attachments"}, "IMAP.imap_port": {"1143"}, "IMAP.imap_tls_port": {"1993"}, "Auth.enforce_admin_mfa": {"false"}, "Auth.enforce_mailbox_mfa": {"false"}, } }