From f0eb32fb76259a5cc42f8b6527bc6145bdca038d Mon Sep 17 00:00:00 2001 From: Philip Gough Date: Tue, 10 Feb 2026 12:06:56 +0000 Subject: [PATCH] Handle correct response code for token expired error --- authentication/authentication_test.go | 63 +++++++++++++++++++++++++++ authentication/oidc.go | 6 +++ 2 files changed, 69 insertions(+) diff --git a/authentication/authentication_test.go b/authentication/authentication_test.go index 58dd65d5e..b872de26f 100644 --- a/authentication/authentication_test.go +++ b/authentication/authentication_test.go @@ -2,9 +2,12 @@ package authentication import ( "context" + "errors" "net/http" "testing" + "time" + "github.com/coreos/go-oidc/v3/oidc" "github.com/go-kit/log" grpc_middleware_auth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" "github.com/mitchellh/mapstructure" @@ -109,3 +112,63 @@ func TestNewAuthentication(t *testing.T) { } }) } + +func TestTokenExpiredErrorHandling(t *testing.T) { + // Test the error handling logic for TokenExpiredError + t.Run("TokenExpiredError is correctly identified", func(t *testing.T) { + // Create a TokenExpiredError + expiredErr := &oidc.TokenExpiredError{ + Expiry: time.Now().Add(-time.Hour), // Expired an hour ago + } + + // Test direct error + var tokenExpiredErr *oidc.TokenExpiredError + if !errors.As(expiredErr, &tokenExpiredErr) { + t.Error("errors.As should identify TokenExpiredError") + } + + // Test wrapped error + wrappedErr := &wrappedError{ + msg: "verification failed", + err: expiredErr, + } + + if !errors.As(wrappedErr, &tokenExpiredErr) { + t.Error("errors.As should identify wrapped TokenExpiredError") + } + }) + + t.Run("Other errors are not identified as TokenExpiredError", func(t *testing.T) { + // Test with a generic error + genericErr := errors.New("generic verification error") + + var tokenExpiredErr *oidc.TokenExpiredError + if errors.As(genericErr, &tokenExpiredErr) { + t.Error("errors.As should not identify generic error as TokenExpiredError") + } + + // Test with wrapped generic error + wrappedGenericErr := &wrappedError{ + msg: "verification failed", + err: genericErr, + } + + if errors.As(wrappedGenericErr, &tokenExpiredErr) { + t.Error("errors.As should not identify wrapped generic error as TokenExpiredError") + } + }) +} + +// Helper type to wrap errors for testing. +type wrappedError struct { + msg string + err error +} + +func (e *wrappedError) Error() string { + return e.msg +} + +func (e *wrappedError) Unwrap() error { + return e.err +} diff --git a/authentication/oidc.go b/authentication/oidc.go index 3cccc811c..5166f17cc 100644 --- a/authentication/oidc.go +++ b/authentication/oidc.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/pem" + "errors" "fmt" "net" "net/http" @@ -359,6 +360,11 @@ func (a oidcAuthenticator) checkAuth(ctx context.Context, token string) (context // We log it to allow the possibility of debugging this. level.Debug(a.logger).Log("msg", msg, "err", err) + var tokenExpiredErr *oidc.TokenExpiredError + if errors.As(err, &tokenExpiredErr) { + return ctx, "token is expired", http.StatusForbidden, codes.Unauthenticated + } + // The original HTTP implementation returned StatusInternalServerError. // For gRPC we return Unknown, as we can't really // be sure the problem is internal and not deserving Unauthenticated or InvalidArgument.