package auth import ( "context" "net/http" "net/http/httptest" "path/filepath" "testing" "ymhut-box/server/unified-management/internal/config" "ymhut-box/server/unified-management/internal/db" ) func TestBootstrapShowsDefaultPasswordOnlyBeforeChange(t *testing.T) { root := t.TempDir() cfg := &config.Config{ StorageDir: root, Database: config.DatabaseConfig{ Provider: "sqlite", SQLitePath: root + "/test.sqlite", FailoverEnabled: true, HealthIntervalSec: 3600, }, } store, err := db.Open(cfg) if err != nil { t.Fatal(err) } defer store.Close() if err := store.EnsureDefaultAdmin(context.Background()); err != nil { t.Fatal(err) } service := NewService(store) payload, err := service.Bootstrap(context.Background()) if err != nil { t.Fatal(err) } if payload["isDefaultPassword"] != true || payload["defaultPassword"] != "admin" { t.Fatalf("unexpected bootstrap payload: %#v", payload) } if err := store.ChangeAdminPassword(context.Background(), "admin", "admin", "changed-password"); err != nil { t.Fatal(err) } payload, err = service.Bootstrap(context.Background()) if err != nil { t.Fatal(err) } if payload["isDefaultPassword"] != false || payload["defaultPassword"] != "" { t.Fatalf("default password leaked after change: %#v", payload) } } func TestChangeAdminPasswordPersistsAfterReopen(t *testing.T) { root := t.TempDir() dbPath := filepath.Join(root, "test.sqlite") cfg := &config.Config{ StorageDir: root, Database: config.DatabaseConfig{ Provider: "sqlite", SQLitePath: dbPath, FailoverEnabled: true, HealthIntervalSec: 3600, }, } store, err := db.Open(cfg) if err != nil { t.Fatal(err) } if err := store.EnsureDefaultAdmin(context.Background()); err != nil { t.Fatal(err) } if err := store.ChangeAdminPassword(context.Background(), "admin", "admin", "persisted-password"); err != nil { t.Fatal(err) } _ = store.Close() reopened, err := db.Open(cfg) if err != nil { t.Fatal(err) } defer reopened.Close() if _, ok, err := reopened.VerifyAdminPassword(context.Background(), "admin", "persisted-password"); err != nil || !ok { t.Fatalf("new password did not persist, ok=%v err=%v", ok, err) } if _, ok, err := reopened.VerifyAdminPassword(context.Background(), "admin", "admin"); err != nil || ok { t.Fatalf("old password still works, ok=%v err=%v", ok, err) } } func TestLoginLocksAfterRepeatedFailures(t *testing.T) { root := t.TempDir() cfg := &config.Config{ StorageDir: root, Database: config.DatabaseConfig{ Provider: "sqlite", SQLitePath: filepath.Join(root, "test.sqlite"), FailoverEnabled: true, HealthIntervalSec: 3600, }, } store, err := db.Open(cfg) if err != nil { t.Fatal(err) } defer store.Close() if err := store.EnsureDefaultAdmin(context.Background()); err != nil { t.Fatal(err) } service := NewService(store) for i := 0; i < loginMaxFails; i++ { if _, _, ok, err := service.Login(context.Background(), "admin", "wrong", "bad-captcha", "00000", "127.0.0.1"); err != nil || ok { t.Fatalf("failed login %d returned ok=%v err=%v", i, ok, err) } } captcha, err := service.NewCaptcha() if err != nil { t.Fatal(err) } service.mu.Lock() answer := service.captchas[captcha.ID].answer service.mu.Unlock() if _, _, ok, err := service.Login(context.Background(), "admin", "admin", captcha.ID, answer, "127.0.0.1"); err != nil || ok { t.Fatalf("locked login should fail without error, ok=%v err=%v", ok, err) } } func TestSessionCookieUsesSecureForForwardedHTTPS(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/api/admin/auth/login", nil) req.Header.Set("X-Forwarded-Proto", "https") res := httptest.NewRecorder() SetSessionCookieForRequest(res, req, "session-id") cookies := res.Result().Cookies() if len(cookies) != 1 { t.Fatalf("expected one cookie, got %d", len(cookies)) } if !cookies[0].Secure { t.Fatalf("expected secure cookie for forwarded https: %#v", cookies[0]) } }