package webui import ( "net/http" "net/http/httptest" "net/url" "strconv" "strings" "testing" "mailgoserver/internal/db" "mailgoserver/internal/mailstore" ) // createMailboxFor mirrors setupTwoTenants' sender helper, for a mailbox instead. func createMailboxFor(t *testing.T, app *App, email string, domainID int64) *db.Mailbox { t.Helper() hash, err := db.HashPassword("password123") if err != nil { t.Fatal(err) } dek := mailstore.GenerateDEK() wrapped, nonce, err := app.Mailstore.WrapDEK(dek) if err != nil { t.Fatal(err) } id, err := app.DB.CreateMailbox(email, hash, domainID, 5*1024*1024*1024, wrapped, nonce) if err != nil { t.Fatal(err) } mbox, err := app.DB.GetMailboxByID(id) if err != nil || mbox == nil { t.Fatalf("expected mailbox to exist: %v", err) } return mbox } func TestScopedAdminCannotAccessOtherTenantMailboxByID(t *testing.T) { app := newTestApp(t) mux := app.Mux() domainA, domainB, _, _ := setupTwoTenants(t, app) mailboxB := createMailboxFor(t, app, "carol@"+domainB.DomainName, domainB.ID) cookie := scopedLogin(t, app, "tenant-a-admin", []int64{domainA.ID}) req := httptest.NewRequest(http.MethodGet, Prefix+"/mailboxes/"+strconv.FormatInt(mailboxB.ID, 10)+"/edit", nil) req.AddCookie(cookie) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) if rec.Code != http.StatusNotFound { t.Fatalf("expected 404 for a mailbox outside scope, got %d", rec.Code) } form := url.Values{} req2 := httptest.NewRequest(http.MethodPost, Prefix+"/mailboxes/"+strconv.FormatInt(mailboxB.ID, 10)+"/delete", strings.NewReader(form.Encode())) req2.Header.Set("Content-Type", "application/x-www-form-urlencoded") req2.AddCookie(cookie) rec2 := httptest.NewRecorder() mux.ServeHTTP(rec2, req2) if rec2.Code != http.StatusNotFound { t.Fatalf("expected 404 disabling a mailbox outside scope, got %d", rec2.Code) } stillActive, err := app.DB.GetMailboxByID(mailboxB.ID) if err != nil || stillActive == nil || !stillActive.IsActive { t.Fatal("mailbox outside scope must not have been modified") } } func TestScopedAdminCannotCreateMailboxOnUnownedDomain(t *testing.T) { app := newTestApp(t) mux := app.Mux() domainA, domainB, _, _ := setupTwoTenants(t, app) cookie := scopedLogin(t, app, "tenant-a-admin", []int64{domainA.ID}) form := url.Values{ "local_part": {"mallory"}, "domain_id": {strconv.FormatInt(domainB.ID, 10)}, // not theirs "password": {"password123"}, } req := httptest.NewRequest(http.MethodPost, Prefix+"/mailboxes/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.StatusNotFound { t.Fatalf("expected 404 creating a mailbox on an unowned domain, got %d", rec.Code) } if m, _ := app.DB.GetMailboxByEmail("mallory@" + domainB.DomainName); m != nil { t.Fatal("mailbox must not have been created on a domain outside the admin's scope") } } func TestScopedAdminCanManageOwnMailboxAppPasswords(t *testing.T) { app := newTestApp(t) mux := app.Mux() domainA, _, _, _ := setupTwoTenants(t, app) mailboxA := createMailboxFor(t, app, "dave@"+domainA.DomainName, domainA.ID) cookie := scopedLogin(t, app, "tenant-a-admin", []int64{domainA.ID}) form := url.Values{"label": {"laptop"}} req := httptest.NewRequest(http.MethodPost, Prefix+"/mailboxes/"+strconv.FormatInt(mailboxA.ID, 10)+"/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("expected redirect after creating an app password, got %d: %s", rec.Code, rec.Body.String()) } passwords, err := app.DB.ListAppPasswordsForMailbox(mailboxA.ID) if err != nil || len(passwords) != 1 { t.Fatalf("expected exactly one app password, got %d (err=%v)", len(passwords), err) } if len(passwords[0].PasswordHash) == 0 { t.Fatal("expected a password hash to be stored") } } func TestScopedAdminCannotCreateAliasOnUnownedDomain(t *testing.T) { app := newTestApp(t) mux := app.Mux() domainA, domainB, _, _ := setupTwoTenants(t, app) mailboxA := createMailboxFor(t, app, "dave@"+domainA.DomainName, domainA.ID) cookie := scopedLogin(t, app, "tenant-a-admin", []int64{domainA.ID}) form := url.Values{ "local_part": {"evilalias"}, "domain_id": {strconv.FormatInt(domainB.ID, 10)}, // not theirs } req := httptest.NewRequest(http.MethodPost, Prefix+"/mailboxes/"+strconv.FormatInt(mailboxA.ID, 10)+"/aliases/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.StatusNotFound { t.Fatalf("expected 404 creating an alias on an unowned domain, got %d", rec.Code) } if a, _ := app.DB.GetAliasByEmail("evilalias@" + domainB.DomainName); a != nil { t.Fatal("alias must not have been created on a domain outside the admin's scope") } } func TestAppPasswordCannotBeRevokedFromAnotherMailbox(t *testing.T) { app := newTestApp(t) domainA, domainB, _, _ := setupTwoTenants(t, app) mailboxA := createMailboxFor(t, app, "dave@"+domainA.DomainName, domainA.ID) mailboxB := createMailboxFor(t, app, "carol@"+domainB.DomainName, domainB.ID) pwID, err := app.DB.CreateAppPassword(mailboxB.ID, "carol's laptop", "irrelevant-hash", nil) if err != nil { t.Fatal(err) } // Attempting to remove mailboxB's app password while scoped to mailboxA must be a // no-op — RemoveAppPassword is scoped by (id, mailboxID) precisely to prevent this. if err := app.DB.RemoveAppPassword(pwID, mailboxA.ID); err != nil { t.Fatal(err) } remaining, err := app.DB.ListAppPasswordsForMailbox(mailboxB.ID) if err != nil || len(remaining) != 1 { t.Fatalf("expected mailboxB's app password to survive a delete scoped to mailboxA, got %d remaining (err=%v)", len(remaining), err) } }