package webui import ( "net/http" "net/http/httptest" "net/url" "strconv" "strings" "testing" "time" "github.com/pquerna/otp/totp" "mailgoserver/internal/db" ) // authLogsFor issues an authenticated GET /logs?type=auth and returns the raw body, // used below to check which auth-log rows a given admin session can see. func authLogsFor(t *testing.T, mux http.Handler, cookie *http.Cookie) string { t.Helper() req := httptest.NewRequest(http.MethodGet, Prefix+"/logs?type=auth", nil) req.AddCookie(cookie) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("/logs?type=auth: status=%d", rec.Code) } return rec.Body.String() } // TestAdminLoginLogsAuthAttempts confirms both a failed and a successful admin // dashboard login are recorded to the audit log. func TestAdminLoginLogsAuthAttempts(t *testing.T) { app := newTestApp(t) mux := app.Mux() hash, err := db.HashPassword("correct-horse-battery-1!") if err != nil { t.Fatal(err) } if _, err := app.DB.CreateAdminUser("audituser", hash, false); err != nil { t.Fatal(err) } // Wrong password. form := url.Values{"username": {"audituser"}, "password": {"wrong-password"}} req := httptest.NewRequest(http.MethodPost, Prefix+"/login", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") mux.ServeHTTP(httptest.NewRecorder(), req) // Correct password. form = url.Values{"username": {"audituser"}, "password": {"correct-horse-battery-1!"}} req = httptest.NewRequest(http.MethodPost, Prefix+"/login", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") mux.ServeHTTP(httptest.NewRecorder(), req) logs, err := app.DB.ListRecentAuthLogs(50) if err != nil { t.Fatal(err) } var sawFail, sawSuccess bool for _, l := range logs { if l.AuthType != "admin_login" || l.Identifier != "audituser" { continue } if !l.Success { sawFail = true } else { sawSuccess = true } } if !sawFail { t.Error("expected a failed admin_login entry for the wrong-password attempt") } if !sawSuccess { t.Error("expected a successful admin_login entry for the correct-password attempt") } } // TestAdminMFAEventsLogged confirms enabling/disabling TOTP, and an admin resetting // another admin's MFA, all produce admin_mfa audit entries. func TestAdminMFAEventsLogged(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", false); err != nil { t.Fatal(err) } req := httptest.NewRequest(http.MethodPost, Prefix+"/account/totp/confirm", strings.NewReader(url.Values{"code": {totpCodeFor(t, "JBSWY3DPEHPK3PXP")}}.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.AddCookie(cookie) mux.ServeHTTP(httptest.NewRecorder(), req) req = httptest.NewRequest(http.MethodPost, Prefix+"/account/totp/disable", nil) req.AddCookie(cookie) mux.ServeHTTP(httptest.NewRecorder(), req) targetID, err := app.DB.CreateAdminUser("reset-target", mustHash(t), false) if err != nil { t.Fatal(err) } if err := app.DB.SetAdminTOTPSecret(targetID, "JBSWY3DPEHPK3PXP", true); err != nil { t.Fatal(err) } req = httptest.NewRequest(http.MethodPost, Prefix+"/admins/"+strconv.FormatInt(targetID, 10)+"/reset_mfa", nil) req.AddCookie(cookie) mux.ServeHTTP(httptest.NewRecorder(), req) logs, err := app.DB.ListRecentAuthLogs(50) if err != nil { t.Fatal(err) } var sawEnabled, sawDisabled, sawReset bool for _, l := range logs { if l.AuthType != "admin_mfa" { continue } switch { case strings.Contains(l.Message, "enabled") && l.Identifier == "test-admin": sawEnabled = true case strings.Contains(l.Message, "disabled") && l.Identifier == "test-admin": sawDisabled = true case strings.Contains(l.Message, "reset by admin") && l.Identifier == "reset-target": sawReset = true } } if !sawEnabled { t.Error("expected an admin_mfa entry for TOTP enabled") } if !sawDisabled { t.Error("expected an admin_mfa entry for TOTP disabled") } if !sawReset { t.Error("expected an admin_mfa entry for the admin-initiated reset") } } // TestWebmailLoginLogsAuthAttempts mirrors TestAdminLoginLogsAuthAttempts for the // self-service webmail portal. func TestWebmailLoginLogsAuthAttempts(t *testing.T) { app := newTestApp(t) mux := app.Mux() mailboxes, err := app.DB.ListMailboxes() if err != nil || len(mailboxes) == 0 { t.Fatal("no seeded mailbox") } email := mailboxes[0].Email form := url.Values{"email": {email}, "password": {"wrong-password"}} req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/login", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") mux.ServeHTTP(httptest.NewRecorder(), req) form = url.Values{"email": {email}, "password": {"testpass123"}} req = httptest.NewRequest(http.MethodPost, MailboxPrefix+"/login", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") mux.ServeHTTP(httptest.NewRecorder(), req) logs, err := app.DB.ListRecentAuthLogs(50) if err != nil { t.Fatal(err) } var sawFail, sawSuccess bool for _, l := range logs { if l.AuthType != "webmail_login" || l.Identifier != email { continue } if !l.Success { sawFail = true } else { sawSuccess = true } } if !sawFail { t.Error("expected a failed webmail_login entry for the wrong-password attempt") } if !sawSuccess { t.Error("expected a successful webmail_login entry for the correct-password attempt") } } // TestAdminAuditLogsHiddenFromScopedAdmins confirms admin_login/admin_mfa entries // (identified by admin username, with no domain to attribute them to) are visible // only to global admins, while webmail_login/mailbox_mfa entries (identified by // mailbox email) remain visible to a scoped admin for their own domain. func TestAdminAuditLogsHiddenFromScopedAdmins(t *testing.T) { app := newTestApp(t) mux := app.Mux() domains, err := app.DB.ListDomains() if err != nil || len(domains) == 0 { t.Fatal("no seeded domain") } domainName := domains[0].DomainName if err := app.DB.LogAuthAttempt("admin_login", "some-admin-username", "127.0.0.1", true, "Login successful"); err != nil { t.Fatal(err) } if err := app.DB.LogAuthAttempt("webmail_login", "owner@"+domainName, "127.0.0.1", true, "Login successful"); err != nil { t.Fatal(err) } scopedCookie := scopedLogin(t, app, "scoped-log-viewer", []int64{domains[0].ID}) scopedBody := authLogsFor(t, mux, scopedCookie) if strings.Contains(scopedBody, "some-admin-username") { t.Error("a scoped admin should not see admin_login entries at all") } if !strings.Contains(scopedBody, "owner@"+domainName) { t.Error("a scoped admin should see webmail_login entries for their own domain") } globalCookie := loginSession(t, app) globalBody := authLogsFor(t, mux, globalCookie) if !strings.Contains(globalBody, "some-admin-username") { t.Error("a global admin should see admin_login entries") } } // totpCodeFor generates a valid current TOTP code for a secret — used to drive // account.go's totpSetupConfirm through a real form submission. func totpCodeFor(t *testing.T, secret string) string { t.Helper() code, err := totp.GenerateCode(secret, time.Now()) if err != nil { t.Fatal(err) } return code }