package webui import ( "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" "mailgoserver/internal/db" ) func TestAppPasswordExpiryPresets(t *testing.T) { loc := time.UTC now := time.Now() t.Run("never expires by default", func(t *testing.T) { got, err := appPasswordExpiry("", "", loc) if err != nil || got != nil { t.Fatalf("got (%v, %v), want (nil, nil)", got, err) } }) t.Run("7 day preset", func(t *testing.T) { got, err := appPasswordExpiry("7d", "", loc) if err != nil || got == nil { t.Fatalf("got (%v, %v), want a non-nil expiry", got, err) } wantAround := now.Add(7 * 24 * time.Hour) if diff := got.Sub(wantAround); diff < -time.Minute || diff > time.Minute { t.Fatalf("expiry %v not within a minute of %v", got, wantAround) } }) t.Run("custom date is end of day", func(t *testing.T) { got, err := appPasswordExpiry("custom", "2030-01-15", loc) if err != nil || got == nil { t.Fatalf("got (%v, %v), want a non-nil expiry", got, err) } want := time.Date(2030, 1, 15, 23, 59, 59, 0, loc) if !got.Equal(want) { t.Fatalf("got %v, want %v", got, want) } }) t.Run("custom without a date errors", func(t *testing.T) { if _, err := appPasswordExpiry("custom", "", loc); err == nil { t.Fatal("expected an error for a missing custom date") } }) t.Run("invalid preset errors", func(t *testing.T) { if _, err := appPasswordExpiry("bogus", "", loc); err == nil { t.Fatal("expected an error for an invalid preset") } }) } // TestAddAppPasswordExpiredIsRejectedByAuth confirms an app password created with a // past expiry (simulated directly at the DB layer, since the UI can only pick future // dates) can no longer authenticate, even while still marked active. func TestAddAppPasswordExpiredIsRejectedByAuth(t *testing.T) { app := newTestApp(t) mailboxes, _ := app.DB.ListMailboxes() if len(mailboxes) == 0 { t.Fatal("no seeded mailbox") } mbox := mailboxes[0].Mailbox past := time.Now().Add(-time.Hour) hash, err := db.HashPassword("some-secret-app-password-value") if err != nil { t.Fatal(err) } if _, err := app.DB.CreateAppPassword(mbox.ID, "expired", hash, &past); err != nil { t.Fatal(err) } got, err := app.DB.VerifyMailboxAppPassword(mbox.Email, "some-secret-app-password-value") if err != nil { t.Fatal(err) } if got != nil { t.Fatal("expired app password must not authenticate") } } // TestAddAppPasswordSetsRevealCookieNotFlash confirms the create handler no longer // puts the plaintext secret in the toast-driven Flash cookie (easy to miss, no copy // button) and instead sets the dedicated one-time reveal cookie the list page renders // as a modal. func TestAddAppPasswordSetsRevealCookieNotFlash(t *testing.T) { app := newTestApp(t) mux := app.Mux() cookie := loginSession(t, app) mailboxes, _ := app.DB.ListMailboxes() 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.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("add app password: status=%d body=%s", rec.Code, rec.Body.String()) } var revealCookie, flashCookie *http.Cookie for _, c := range rec.Result().Cookies() { switch c.Name { case appPasswordRevealCookieName: revealCookie = c case flashCookieName: flashCookie = c } } if revealCookie == nil || revealCookie.Value == "" { t.Fatal("expected a non-empty app password reveal cookie") } if flashCookie != nil && flashCookie.Value != "" { t.Fatalf("flash cookie should not carry the secret, got %q", flashCookie.Value) } // 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.AddCookie(cookie) req2.AddCookie(revealCookie) rec2 := httptest.NewRecorder() mux.ServeHTTP(rec2, req2) if rec2.Code != http.StatusOK { t.Fatalf("apppasswords list: status=%d", rec2.Code) } if !strings.Contains(rec2.Body.String(), "appPasswordRevealModal") { t.Fatal("expected the reveal modal markup in the response") } }