package auth import ( "errors" "path/filepath" "testing" "time" ) func TestSeedAdminAuthenticatesAndRequiresPasswordChange(t *testing.T) { store := openTestStore(t, t.TempDir()) user, err := store.Authenticate(SeedAdminEmail, SeedAdminPassword) if err != nil { t.Fatalf("Authenticate(seed admin) error = %v", err) } if user.Role != RoleAdmin { t.Errorf("seed admin role = %q, want %q", user.Role, RoleAdmin) } if !user.MustChangePassword { t.Error("seed admin MustChangePassword = false, want true") } if _, err := store.Authenticate(SeedAdminEmail, "wrong-password"); !errors.Is(err, ErrInvalidCredentials) { t.Errorf("Authenticate(wrong password) error = %v, want ErrInvalidCredentials", err) } } func TestPasswordHashingAndChangePassword(t *testing.T) { password := "Start1234!" hash, err := HashPassword(password) if err != nil { t.Fatalf("HashPassword() error = %v", err) } if hash == password { t.Error("HashPassword() returned the plaintext password") } if !CheckPassword(hash, password) { t.Error("CheckPassword() = false for the password used to create the hash") } if CheckPassword(hash, "different-password") { t.Error("CheckPassword() = true for a different password") } store := openTestStore(t, t.TempDir()) user, err := store.Authenticate(SeedAdminEmail, SeedAdminPassword) if err != nil { t.Fatal(err) } if err := store.ChangePassword(user.ID, SeedAdminPassword, SeedAdminPassword); !errors.Is(err, ErrPasswordUnchanged) { t.Errorf("ChangePassword(same password) error = %v, want ErrPasswordUnchanged", err) } const newPassword = "A-new-password-123!" if err := store.ChangePassword(user.ID, SeedAdminPassword, newPassword); err != nil { t.Fatalf("ChangePassword() error = %v", err) } if _, err := store.Authenticate(SeedAdminEmail, SeedAdminPassword); !errors.Is(err, ErrInvalidCredentials) { t.Errorf("Authenticate(old password) error = %v, want ErrInvalidCredentials", err) } updatedUser, err := store.Authenticate(SeedAdminEmail, newPassword) if err != nil { t.Fatalf("Authenticate(new password) error = %v", err) } if updatedUser.MustChangePassword { t.Error("MustChangePassword remains true after a password change") } } func TestSessionsPersistAcrossStoreRestartAndSlideExpiry(t *testing.T) { now := time.Date(2026, time.September, 24, 12, 0, 0, 0, time.UTC) clock := func() time.Time { return now } databasePath := filepath.Join(t.TempDir(), "webfilebrowser.db") store, err := Open(databasePath, Options{Clock: clock, SessionLifetime: 7 * 24 * time.Hour}) if err != nil { t.Fatalf("Open() error = %v", err) } user, err := store.Authenticate(SeedAdminEmail, SeedAdminPassword) if err != nil { t.Fatal(err) } token, err := store.CreateSession(user.ID) if err != nil { t.Fatalf("CreateSession() error = %v", err) } if err := store.Close(); err != nil { t.Fatal(err) } now = now.Add(6 * 24 * time.Hour) reopenedStore, err := Open(databasePath, Options{Clock: clock, SessionLifetime: 7 * 24 * time.Hour}) if err != nil { t.Fatalf("Open() after restart error = %v", err) } t.Cleanup(func() { _ = reopenedStore.Close() }) if _, err := reopenedStore.LookupSession(token); err != nil { t.Fatalf("LookupSession() after restart error = %v", err) } now = now.Add(2 * 24 * time.Hour) if _, err := reopenedStore.LookupSession(token); err != nil { t.Fatalf("LookupSession() after sliding expiry error = %v", err) } } func openTestStore(t *testing.T, directory string) *Store { t.Helper() store, err := Open(filepath.Join(directory, "webfilebrowser.db"), Options{}) if err != nil { t.Fatalf("Open() error = %v", err) } t.Cleanup(func() { _ = store.Close() }) return store }