Files
zitadel/internal/api/oidc/error.go
5934e07960 fix: recover from request panics (#11713)
# Which Problems Are Solved

Panics may cause a service discruption by unexpectedly closing an
request's connection. Or in the case of gRPC completely killing the
service.

Allthough panics are still individual bugs that need to be solved, this
PR makes sure a panic is gracefully handled and an understandable error
is returned to the client.

# How the Problems Are Solved

- Recover in the middleware interceptors for the 3 API protocols (HTTP,
gRPC, connect).
- HTTP middleware uses formatted responses for:
  - OIDC errors (JSON formatted response)
  - UI (error page rendering)
  - SCIM
- Upon recovery an alert level log is printed (ERROR+4 for stdlib log
handlers)

# Additional Context

- internal observation

---------

Co-authored-by: Livio Spring <livio@zitadel.com>
Co-authored-by: Livio Spring <livio.a@gmail.com>
2026-03-03 11:55:17 +00:00

56 lines
1.6 KiB
Go

package oidc
import (
"context"
"errors"
"net/http"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
"github.com/zitadel/zitadel/backend/v3/instrumentation/logging"
http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/zerrors"
)
// oidcError ensures [*oidc.Error] and [op.StatusError] types for err.
// It must be used when an error passes the package boundary towards oidc.
// When err is already of the correct type is passed as-is.
// If the err is a Zitadel error, it is transformed with a proper HTTP status code.
// Unknown errors are treated as internal server errors.
func oidcError(ctx context.Context, err error) error {
if err == nil {
return nil
}
if errors.Is(err, op.ErrInvalidRefreshToken) {
err = zerrors.ThrowInvalidArgument(err, "OIDCS-ef2Gi", "Errors.User.RefreshToken.Invalid")
}
var (
sError op.StatusError
oError *oidc.Error
zError *zerrors.ZitadelError
)
if errors.As(err, &sError) || errors.As(err, &oError) {
return err
}
// here we are encountering an error type that is completely unknown to us.
if !errors.As(err, &zError) {
err = zerrors.ThrowInternal(err, "OIDC-AhX2u", "Errors.Internal")
errors.As(err, &zError)
}
statusCode, _ := http_util.ZitadelErrorToHTTPStatusCode(ctx, err)
newOidcErr := oidc.ErrServerError
if statusCode < 500 {
newOidcErr = oidc.ErrInvalidRequest
}
oidcErr := newOidcErr().WithParent(err)
oidcErr.Description = zError.GetMessage()
return op.NewStatusError(oidcErr, statusCode)
}
func writeRecoverError(w http.ResponseWriter, r *http.Request, err error) {
op.WriteError(w, r, err, logging.FromCtx(r.Context()))
}