package auth import ( "context" "errors" "testing" "time" "golang.org/x/crypto/bcrypt" "vocat/internal/store" ) func newTestService(t *testing.T) *Service { t.Helper() database, err := store.Open(context.Background(), ":memory:") if err != nil { t.Fatalf("store.Open() error = %v", err) } t.Cleanup(func() { _ = database.Close() }) service, err := New(database, Options{ SessionTTL: time.Hour, BcryptCost: bcrypt.MinCost, }) if err != nil { t.Fatalf("New() error = %v", err) } if err := service.EnsureAdmin(context.Background(), "admin", "correct-password"); err != nil { t.Fatalf("EnsureAdmin() error = %v", err) } return service } func TestLoginAuthenticateCSRFAndLogout(t *testing.T) { ctx := context.Background() service := newTestService(t) if _, err := service.Login(ctx, "admin", "wrong-password"); !errors.Is(err, ErrInvalidCredentials) { t.Fatalf("Login() error = %v, want ErrInvalidCredentials", err) } credentials, err := service.Login(ctx, "admin", "correct-password") if err != nil { t.Fatalf("Login() error = %v", err) } session, err := service.Authenticate(ctx, credentials.SessionToken) if err != nil { t.Fatalf("Authenticate() error = %v", err) } if session.Principal.Username != "admin" { t.Fatalf("Principal = %+v", session.Principal) } if _, err := service.ValidateCSRF(ctx, credentials.SessionToken, "wrong"); !errors.Is(err, ErrInvalidCSRF) { t.Fatalf("ValidateCSRF() error = %v, want ErrInvalidCSRF", err) } if _, err := service.ValidateCSRF(ctx, credentials.SessionToken, credentials.CSRFToken); err != nil { t.Fatalf("ValidateCSRF() error = %v", err) } _, csrfToken, err := service.CSRFToken( ctx, credentials.SessionToken, credentials.CSRFToken, ) if err != nil { t.Fatalf("CSRFToken() error = %v", err) } if csrfToken != credentials.CSRFToken { t.Fatal("CSRFToken() rotated an already valid token") } if err := service.Logout(ctx, credentials.SessionToken); err != nil { t.Fatalf("Logout() error = %v", err) } if _, err := service.Authenticate(ctx, credentials.SessionToken); !errors.Is(err, ErrUnauthorized) { t.Fatalf("Authenticate() after logout error = %v, want ErrUnauthorized", err) } } func TestEnsureAdminRevokesSessionOnPasswordChange(t *testing.T) { ctx := context.Background() service := newTestService(t) credentials, err := service.Login(ctx, "admin", "correct-password") if err != nil { t.Fatal(err) } if err := service.EnsureAdmin(ctx, "admin", "new-password"); err != nil { t.Fatal(err) } if _, err := service.Authenticate(ctx, credentials.SessionToken); !errors.Is(err, ErrUnauthorized) { t.Fatalf("old session error = %v, want ErrUnauthorized", err) } if _, err := service.Login(ctx, "admin", "new-password"); err != nil { t.Fatalf("login with new password: %v", err) } }