first commit
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
package webui
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
"mailgoserver/internal/db"
|
||||
"mailgoserver/internal/dkim"
|
||||
"mailgoserver/internal/toolbox"
|
||||
)
|
||||
|
||||
func newTestApp(t *testing.T) *App {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
dbPath := filepath.Join(dir, "test.db")
|
||||
database, err := db.Open(dbPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { database.Close() })
|
||||
|
||||
// Seed one of everything so every page has real data to render.
|
||||
domainID, err := database.CreateDomain("example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hash, _ := db.HashPassword("testpass123")
|
||||
senderID, err := database.CreateSender("test@example.com", hash, domainID, true, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = database.CreateWhitelistedIP("127.0.0.1", domainID, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dkimMgr := dkim.New(database, 1024)
|
||||
ok, err := dkimMgr.GenerateDKIMKeypair("example.com", "sel1", false)
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("dkim gen: ok=%v err=%v", ok, err)
|
||||
}
|
||||
key, err := database.GetActiveDKIMKeyByDomainID(domainID)
|
||||
if err != nil || key == nil {
|
||||
t.Fatalf("get active dkim key: %v %v", key, err)
|
||||
}
|
||||
|
||||
logID, err := database.InsertEmailLog(db.EmailLog{
|
||||
MessageID: "abc123@example.com", Timestamp: time.Now(), PeerIP: "127.0.0.1",
|
||||
MailFrom: "test@example.com", ToAddress: "rcpt@example.org", Subject: "hi",
|
||||
EmailHeaders: "From: test@example.com\nTo: rcpt@example.org", MessageBody: "hello",
|
||||
Status: "relayed", DKIMSigned: true, Username: "test@example.com",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := database.InsertEmailRecipientLog(db.EmailRecipientLog{EmailLogID: logID, Recipient: "rcpt@example.org", RecipientType: "to", Status: "success"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
attFile := filepath.Join(dir, "att.txt")
|
||||
os.WriteFile(attFile, []byte("attachment data"), 0o644)
|
||||
if err := database.InsertEmailAttachment(db.EmailAttachment{EmailLogID: logID, Filename: "att.txt", ContentType: "text/plain", FilePath: attFile, Size: 15}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := database.LogAuthAttempt("sender", "test@example.com", "127.0.0.1", true, "ok"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cfg := ini.Empty()
|
||||
serverSec, _ := cfg.NewSection("Server")
|
||||
serverSec.NewKey("smtp_port", "4025")
|
||||
serverSec.NewKey("smtp_tls_port", "40465")
|
||||
serverSec.NewKey("bind_ip", "0.0.0.0")
|
||||
serverSec.NewKey("time_zone", "UTC")
|
||||
serverSec.NewKey("hostname", "mail.example.com")
|
||||
serverSec.NewKey("helo_hostname", "mail.example.com")
|
||||
serverSec.NewKey("server_banner", "")
|
||||
dbSec, _ := cfg.NewSection("Database")
|
||||
dbSec.NewKey("database_url", "sqlite:///server_data/smtp_server.db")
|
||||
logSec, _ := cfg.NewSection("Logging")
|
||||
logSec.NewKey("log_level", "INFO")
|
||||
logSec.NewKey("hide_info_aiosmtpd", "true")
|
||||
relaySec, _ := cfg.NewSection("Relay")
|
||||
relaySec.NewKey("relay_timeout", "30")
|
||||
tlsSec, _ := cfg.NewSection("TLS")
|
||||
tlsSec.NewKey("tls_cert_file", "ssl_certs/server.crt")
|
||||
tlsSec.NewKey("tls_key_file", "ssl_certs/server.key")
|
||||
dkimSec, _ := cfg.NewSection("DKIM")
|
||||
dkimSec.NewKey("dkim_key_size", "2048")
|
||||
dkimSec.NewKey("spf_server_ip", "192.168.1.1")
|
||||
attSec, _ := cfg.NewSection("Attachments")
|
||||
attSec.NewKey("attachments_path", filepath.Join(dir, "attachments"))
|
||||
|
||||
configPath := filepath.Join(dir, "settings.ini")
|
||||
cfg.SaveTo(configPath)
|
||||
|
||||
app, err := New(database, dkimMgr, cfg, configPath, toolbox.GetLogger("test"), func() bool { return true })
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
|
||||
_ = senderID
|
||||
_ = key
|
||||
return app
|
||||
}
|
||||
|
||||
// loginSession creates a fully-verified admin session (no MFA enrolled) and returns
|
||||
// its cookie, for tests that need to hit routes behind requireAuth.
|
||||
func loginSession(t *testing.T, app *App) *http.Cookie {
|
||||
t.Helper()
|
||||
hash, err := db.HashPassword("test-password-123!")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
userID, err := app.DB.CreateAdminUser("test-admin", hash, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token, err := app.DB.CreateSession(userID, true, time.Hour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return &http.Cookie{Name: sessionCookieName, Value: token}
|
||||
}
|
||||
|
||||
func TestAllPagesRender(t *testing.T) {
|
||||
app := newTestApp(t)
|
||||
mux := app.Mux()
|
||||
cookie := loginSession(t, app)
|
||||
|
||||
domains, _ := app.DB.ListDomains()
|
||||
senders, _ := app.DB.ListSenders()
|
||||
ips, _ := app.DB.ListWhitelistedIPs()
|
||||
keys, _ := app.DB.ListActiveDKIMKeysWithDomain()
|
||||
logs, _ := app.DB.ListEmailLogsPage(0, 10)
|
||||
if len(domains) == 0 || len(senders) == 0 || len(ips) == 0 || len(keys) == 0 || len(logs) == 0 {
|
||||
t.Fatalf("seed data missing: domains=%d senders=%d ips=%d keys=%d logs=%d", len(domains), len(senders), len(ips), len(keys), len(logs))
|
||||
}
|
||||
|
||||
pagesToCheck := []string{
|
||||
"/",
|
||||
"/account",
|
||||
"/domains", "/domains/add", "/domains/" + itoa(domains[0].ID) + "/edit",
|
||||
"/senders", "/senders/add", "/senders/" + itoa(senders[0].ID) + "/edit",
|
||||
"/ips", "/ips/add", "/ips/" + itoa(ips[0].ID) + "/edit",
|
||||
"/dkim", "/dkim/" + itoa(keys[0].ID) + "/edit",
|
||||
"/logs", "/logs?type=emails", "/logs?type=auth",
|
||||
"/settings",
|
||||
"/msg/content/" + itoa(logs[0].ID),
|
||||
"/admins", "/admins/add",
|
||||
}
|
||||
|
||||
for _, path := range pagesToCheck {
|
||||
req := httptest.NewRequest(http.MethodGet, Prefix+path, nil)
|
||||
req.AddCookie(cookie)
|
||||
rec := httptest.NewRecorder()
|
||||
mux.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Errorf("GET %s: status %d, body: %s", path, rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnauthenticatedRequestsRedirectToLogin(t *testing.T) {
|
||||
app := newTestApp(t)
|
||||
mux := app.Mux()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, Prefix+"/domains", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
mux.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusFound {
|
||||
t.Fatalf("expected redirect to login, got status %d", rec.Code)
|
||||
}
|
||||
loc := rec.Header().Get("Location")
|
||||
if !strings.HasPrefix(loc, Prefix+"/login") {
|
||||
t.Fatalf("expected redirect to login page, got %q", loc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstLoginForcedBeforeDashboard(t *testing.T) {
|
||||
app := newTestApp(t)
|
||||
mux := app.Mux()
|
||||
|
||||
hash, err := db.HashPassword("Password123!")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
userID, err := app.DB.CreateAdminUser("admin", hash, true) // must_change_password
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token, err := app.DB.CreateSession(userID, true, time.Hour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cookie := &http.Cookie{Name: sessionCookieName, Value: token}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, Prefix+"/domains", nil)
|
||||
req.AddCookie(cookie)
|
||||
rec := httptest.NewRecorder()
|
||||
mux.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusFound || rec.Header().Get("Location") != Prefix+"/first-login" {
|
||||
t.Fatalf("expected redirect to /first-login, got status %d location %q", rec.Code, rec.Header().Get("Location"))
|
||||
}
|
||||
}
|
||||
|
||||
func itoa(id int64) string {
|
||||
return strconv.FormatInt(id, 10)
|
||||
}
|
||||
Reference in New Issue
Block a user