package services import ( "log/slog" "path/filepath" "testing" "time" ) func TestPasswordHashVerification(t *testing.T) { hash := HashPassword("correct-horse") if !VerifyPasswordHash(hash, "correct-horse") { t.Fatalf("VerifyPasswordHash rejected the correct password") } if VerifyPasswordHash(hash, "wrong-password") { t.Fatalf("VerifyPasswordHash accepted the wrong password") } } func TestBootstrapCreatesAdminAndClosesRegistration(t *testing.T) { auth := newTestAuthService(t) available, err := auth.BootstrapAvailable() if err != nil { t.Fatalf("BootstrapAvailable returned error: %v", err) } if !available { t.Fatalf("BootstrapAvailable = false, want true") } user, err := auth.CreateBootstrapUser("daniel", "daniel@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } if user.Role != UserRoleAdmin { t.Fatalf("role = %q, want admin", user.Role) } if _, err := auth.CreateBootstrapUser("other", "other@example.test", "password123"); err == nil { t.Fatalf("second bootstrap unexpectedly succeeded") } } func TestLoginSessionAndDisabledUser(t *testing.T) { auth := newTestAuthService(t) user, err := auth.CreateBootstrapUser("daniel", "daniel@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } if _, _, err := auth.Login("daniel@example.test", "wrong"); err == nil { t.Fatalf("Login accepted wrong password") } _, token, err := auth.Login("daniel@example.test", "password123") if err != nil { t.Fatalf("Login returned error: %v", err) } sessionUser, _, err := auth.UserForSession(token) if err != nil { t.Fatalf("UserForSession returned error: %v", err) } if sessionUser.ID != user.ID { t.Fatalf("session user = %q, want %q", sessionUser.ID, user.ID) } if err := auth.DisableUser(user.ID, true); err != nil { t.Fatalf("DisableUser returned error: %v", err) } if _, _, err := auth.UserForSession(token); err == nil { t.Fatalf("disabled user session still resolved") } } func TestInviteAcceptsOnceAndResetChangesPassword(t *testing.T) { auth := newTestAuthService(t) admin, err := auth.CreateBootstrapUser("admin", "admin@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } invite, err := auth.CreateInvite("friend@example.test", UserRoleUser, admin.ID, time.Hour) if err != nil { t.Fatalf("CreateInvite returned error: %v", err) } user, err := auth.AcceptInvite(invite.Token, "friend", "password123") if err != nil { t.Fatalf("AcceptInvite returned error: %v", err) } if user.Email != "friend@example.test" { t.Fatalf("email = %q, want friend@example.test", user.Email) } if _, err := auth.AcceptInvite(invite.Token, "friend", "password123"); err == nil { t.Fatalf("AcceptInvite allowed token reuse") } reset, err := auth.CreatePasswordResetInvite(user.ID, admin.ID) if err != nil { t.Fatalf("CreatePasswordResetInvite returned error: %v", err) } if _, err := auth.AcceptInvite(reset.Token, "", "newpassword123"); err != nil { t.Fatalf("AcceptInvite reset returned error: %v", err) } if _, _, err := auth.Login("friend@example.test", "newpassword123"); err != nil { t.Fatalf("Login with reset password returned error: %v", err) } } func newTestAuthService(t *testing.T) *AuthService { t.Helper() root := t.TempDir() upload, err := NewUploadService(1024*1024, filepath.Join(root, "data"), "http://example.test", slog.Default()) if err != nil { t.Fatalf("NewUploadService returned error: %v", err) } t.Cleanup(func() { if err := upload.Close(); err != nil { t.Fatalf("Close returned error: %v", err) } }) auth, err := NewAuthService(upload.DB(), "http://example.test") if err != nil { t.Fatalf("NewAuthService returned error: %v", err) } return auth }