feat(op): Server interface (#447)
* first draft of a new server interface * allow any response type * complete interface docs * refelct the format from the proposal * intermediate commit with some methods implemented * implement remaining token grant type methods * implement remaining server methods * error handling * rewrite auth request validation * define handlers, routes * input validation and concrete handlers * check if client credential client is authenticated * copy and modify the routes test for the legacy server * run integration tests against both Server and Provider * remove unuse ValidateAuthRequestV2 function * unit tests for error handling * cleanup tokenHandler * move server routest test * unit test authorize * handle client credentials in VerifyClient * change code exchange route test * finish http unit tests * review server interface docs and spelling * add withClient unit test * server options * cleanup unused GrantType method * resolve typo comments * make endpoints pointers to enable/disable them * jwt profile base work * jwt: correct the test expect --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
parent
daf82a5e04
commit
0f8a0585bf
28 changed files with 3654 additions and 126 deletions
101
pkg/op/error.go
101
pkg/op/error.go
|
@ -1,6 +1,9 @@
|
|||
package op
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
|
@ -66,3 +69,101 @@ func RequestError(w http.ResponseWriter, r *http.Request, err error, logger *slo
|
|||
logger.Log(r.Context(), e.LogLevel(), "request error", "oidc_error", e)
|
||||
httphelper.MarshalJSONWithStatus(w, e, status)
|
||||
}
|
||||
|
||||
// TryErrorRedirect tries to handle an error by redirecting a client.
|
||||
// If this attempt fails, an error is returned that must be returned
|
||||
// to the client instead.
|
||||
func TryErrorRedirect(ctx context.Context, authReq ErrAuthRequest, parent error, encoder httphelper.Encoder, logger *slog.Logger) (*Redirect, error) {
|
||||
e := oidc.DefaultToServerError(parent, parent.Error())
|
||||
logger = logger.With("oidc_error", e)
|
||||
|
||||
if authReq == nil {
|
||||
logger.Log(ctx, e.LogLevel(), "auth request")
|
||||
return nil, AsStatusError(e, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if logAuthReq, ok := authReq.(LogAuthRequest); ok {
|
||||
logger = logger.With("auth_request", logAuthReq)
|
||||
}
|
||||
|
||||
if authReq.GetRedirectURI() == "" || e.IsRedirectDisabled() {
|
||||
logger.Log(ctx, e.LogLevel(), "auth request: not redirecting")
|
||||
return nil, AsStatusError(e, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
e.State = authReq.GetState()
|
||||
var responseMode oidc.ResponseMode
|
||||
if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok {
|
||||
responseMode = rm.GetResponseMode()
|
||||
}
|
||||
url, err := AuthResponseURL(authReq.GetRedirectURI(), authReq.GetResponseType(), responseMode, e, encoder)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "auth response URL", "error", err)
|
||||
return nil, AsStatusError(err, http.StatusBadRequest)
|
||||
}
|
||||
logger.Log(ctx, e.LogLevel(), "auth request redirect", "url", url)
|
||||
return NewRedirect(url), nil
|
||||
}
|
||||
|
||||
// StatusError wraps an error with a HTTP status code.
|
||||
// The status code is passed to the handler's writer.
|
||||
type StatusError struct {
|
||||
parent error
|
||||
statusCode int
|
||||
}
|
||||
|
||||
// NewStatusError sets the parent and statusCode to a new StatusError.
|
||||
// It is recommended for parent to be an [oidc.Error].
|
||||
//
|
||||
// Typically implementations should only use this to signal something
|
||||
// very specific, like an internal server error.
|
||||
// If a returned error is not a StatusError, the framework
|
||||
// will set a statusCode based on what the standard specifies,
|
||||
// which is [http.StatusBadRequest] for most of the time.
|
||||
// If the error encountered can described clearly with a [oidc.Error],
|
||||
// do not use this function, as it might break standard rules!
|
||||
func NewStatusError(parent error, statusCode int) StatusError {
|
||||
return StatusError{
|
||||
parent: parent,
|
||||
statusCode: statusCode,
|
||||
}
|
||||
}
|
||||
|
||||
// AsStatusError unwraps a StatusError from err
|
||||
// and returns it unmodified if found.
|
||||
// If no StatuError was found, a new one is returned
|
||||
// with statusCode set to it as a default.
|
||||
func AsStatusError(err error, statusCode int) (target StatusError) {
|
||||
if errors.As(err, &target) {
|
||||
return target
|
||||
}
|
||||
return NewStatusError(err, statusCode)
|
||||
}
|
||||
|
||||
func (e StatusError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", http.StatusText(e.statusCode), e.parent.Error())
|
||||
}
|
||||
|
||||
func (e StatusError) Unwrap() error {
|
||||
return e.parent
|
||||
}
|
||||
|
||||
func (e StatusError) Is(err error) bool {
|
||||
var target StatusError
|
||||
if !errors.As(err, &target) {
|
||||
return false
|
||||
}
|
||||
return errors.Is(e.parent, target.parent) &&
|
||||
e.statusCode == target.statusCode
|
||||
}
|
||||
|
||||
// WriteError asserts for a StatusError containing an [oidc.Error].
|
||||
// If no StatusError is found, the status code will default to [http.StatusBadRequest].
|
||||
// If no [oidc.Error] was found in the parent, the error type defaults to [oidc.ServerError].
|
||||
func WriteError(w http.ResponseWriter, r *http.Request, err error, logger *slog.Logger) {
|
||||
statusError := AsStatusError(err, http.StatusBadRequest)
|
||||
e := oidc.DefaultToServerError(statusError.parent, statusError.parent.Error())
|
||||
|
||||
logger.Log(r.Context(), e.LogLevel(), "request error", "oidc_error", e)
|
||||
httphelper.MarshalJSONWithStatus(w, e, statusError.statusCode)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue