zitadel/internal/api/oidc/error.go
Tim Möhlmann c8e0b30e17
fix(oidc): return bad request for base64 errors (#7730)
* fix(oidc): return bad request for base64 errors

We've recently noticed an increased amount of 500: internal server error status returns on zitadel cloud.
The source of these errors appear to be erroneous input in fields that are supposed to be bas64 formatted.

```
time=2024-04-08T14:05:47.600Z level=ERROR msg="request error" oidc_error.parent="ID=OIDC-AhX2u Message=Errors.Internal Parent=(illegal base64 data at input byte 8)" oidc_error.description=Errors.Internal oidc_error.type=server_error status_code=500
```

Within the possible code paths of the token endpoint there are a couple of uses of base64.Encoding.DecodeString of which a returned error was not properly wrapped, but returned as-is.
This causes the oidc error handler to return a 500 with the `OIDC-AhX2u` ID.
We were not able to pinpoint the exact errors that are happening to any one call of `DecodeString`.

This fix wraps all errors from `DecodeString` so that proper 400: bad request is returned with information about the error. Each wrapper now has an unique error ID, so that logs will contain the source of the error as well.

This bug was reported internally by the ops team.

* catch op.ErrInvalidRefreshToken
2024-04-09 08:42:59 +02:00

52 lines
1.4 KiB
Go

package oidc
import (
"errors"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
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(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(err)
newOidcErr := oidc.ErrServerError
if statusCode < 500 {
newOidcErr = oidc.ErrInvalidRequest
}
return op.NewStatusError(
newOidcErr().
WithParent(err).
WithDescription(zError.GetMessage()),
statusCode,
)
}