Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sdk): pass server error code to consumer #866

Merged
merged 1 commit into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (i *IssuerInitiatedInteraction) RequestCredentialWithPreAuth(
) (*verifiable.CredentialsArray, error) {
credentials, _, err := i.requestCredentialWithPreAuth(vm, opts)
if err != nil {
return nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
return nil, err
}

return toGomobileCredentials(credentials), nil
Expand All @@ -120,7 +120,7 @@ func (i *IssuerInitiatedInteraction) RequestCredentialWithPreAuthV2(
) (*verifiable.CredentialsArrayV2, error) {
credentials, configIDs, err := i.requestCredentialWithPreAuth(vm, opts)
if err != nil {
return nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
return nil, err
}

return toGomobileCredentialsV2(credentials, configIDs), nil
Expand Down
4 changes: 4 additions & 0 deletions cmd/wallet-sdk-gomobile/walleterror/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ type Error struct {
Details string `json:"details"`
// ID of Open Telemetry root trace. Can be used to trace API calls. Only present in certain errors.
TraceID string `json:"trace_id"`
// Server error code.
ServerCode string `json:"server_code,omitempty"`
// Server error message.
ServerMessage string `json:"server_message,omitempty"`
}

// Parse used to parse exception message on mobile side.
Expand Down
12 changes: 7 additions & 5 deletions cmd/wallet-sdk-gomobile/wrapper/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@ func convertToGomobileError(err error, trace *otel.Trace) *walleterror.Error {
mergedErrorMessage := higherLevelErrorMessage + walletError.ParentError

return &walleterror.Error{
Code: walletError.Code,
Category: walletError.Category,
Message: walletError.Message,
Details: mergedErrorMessage,
TraceID: traceID,
Code: walletError.Code,
Category: walletError.Category,
Message: walletError.Message,
Details: mergedErrorMessage,
TraceID: traceID,
ServerCode: walletError.ServerCode,
ServerMessage: walletError.ServerMessage,
}
}

Expand Down
48 changes: 34 additions & 14 deletions pkg/openid4ci/interaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,61 +543,81 @@ func (i *interaction) getVCsFromCredentialResponses(
return vcs, nil
}

//nolint:funlen
func processCredentialErrorResponse(statusCode int, respBytes []byte) error {
detailedErr := fmt.Errorf("received status code [%d] with body [%s] from issuer's credential endpoint",
statusCode, string(respBytes))

var errorResponse errorResponse
var errResponse errorResponse

err := json.Unmarshal(respBytes, &errorResponse)
err := json.Unmarshal(respBytes, &errResponse)
if err != nil {
return walleterror.NewExecutionError(ErrorModule,
OtherCredentialRequestErrorCode,
OtherCredentialRequestError,
detailedErr)
}

switch errorResponse.Error {
switch errResponse.Error {
case "invalid_request":
return walleterror.NewExecutionError(ErrorModule,
InvalidCredentialRequestErrorCode,
InvalidCredentialRequestError,
detailedErr)
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription))
case "invalid_token":
return walleterror.NewExecutionError(ErrorModule,
InvalidTokenErrorCode,
InvalidTokenError,
detailedErr)
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription))
case "unsupported_credential_format":
return walleterror.NewExecutionError(ErrorModule,
UnsupportedCredentialFormatErrorCode,
UnsupportedCredentialFormatError,
detailedErr)
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription))
case "unsupported_credential_type":
return walleterror.NewExecutionError(ErrorModule,
UnsupportedCredentialTypeErrorCode,
UnsupportedCredentialTypeError,
detailedErr)
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription))
case "invalid_or_missing_proof":
return walleterror.NewExecutionError(ErrorModule,
InvalidOrMissingProofErrorCode,
InvalidOrMissingProofError,
detailedErr)
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription))
case "expired_ack_id":
return walleterror.NewExecutionError(ErrorModule,
AcknowledgmentExpiredErrorCode,
AcknowledgmentExpiredError,
detailedErr)
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription))
case "invalid_proof":
return NewInvalidProofError(walleterror.NewExecutionError(ErrorModule,
AcknowledgmentExpiredErrorCode,
AcknowledgmentExpiredError,
detailedErr), errorResponse.CNonce, errorResponse.CNonceExpiresIn)
return NewInvalidProofError(
walleterror.NewExecutionError(ErrorModule,
AcknowledgmentExpiredErrorCode,
AcknowledgmentExpiredError,
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription),
),
errResponse.CNonce, errResponse.CNonceExpiresIn)
default:
return walleterror.NewExecutionError(ErrorModule,
OtherCredentialRequestErrorCode,
OtherCredentialRequestError,
detailedErr)
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription))
}
}

Expand Down
16 changes: 12 additions & 4 deletions pkg/openid4ci/issuerinitiatedinteraction.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,22 +625,30 @@ func tokenErrorResponseHandler(statusCode int, respBody []byte) error {
return walleterror.NewExecutionError(ErrorModule,
InvalidTokenRequestErrorCode,
InvalidTokenRequestError,
detailedErr)
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription))
case "invalid_grant":
return walleterror.NewExecutionError(ErrorModule,
InvalidGrantErrorCode,
InvalidGrantError,
detailedErr)
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription))
case "invalid_client":
return walleterror.NewExecutionError(ErrorModule,
InvalidClientErrorCode,
InvalidClientError,
detailedErr)
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription))
default:
return walleterror.NewExecutionError(ErrorModule,
OtherTokenResponseErrorCode,
OtherTokenRequestError,
detailedErr)
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription))
}
}

Expand Down
3 changes: 2 additions & 1 deletion pkg/openid4ci/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ type batchCredentialRequest struct {
}

type errorResponse struct {
Error string `json:"error,omitempty"`
Error string `json:"error,omitempty"`
ErrorDescription string `json:"error_description,omitempty"`
// containing a nonce to be used to create a proof of possession of key material
// when requesting a Credential.
CNonce string `json:"c_nonce"`
Expand Down
3 changes: 2 additions & 1 deletion pkg/openid4vp/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ const (
)

type errorResponse struct {
Error string `json:"error,omitempty"`
Error string `json:"error,omitempty"`
ErrorDescription string `json:"error_description,omitempty"`
}

type msEntraErrorResponse struct {
Expand Down
4 changes: 3 additions & 1 deletion pkg/openid4vp/openid4vp.go
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,9 @@ func processAuthorizationErrorResponse(statusCode int, respBytes []byte) error {
return walleterror.NewExecutionError(ErrorModule,
OtherAuthorizationResponseErrorCode,
OtherAuthorizationResponseError,
detailedErr)
detailedErr,
walleterror.WithServerErrorCode(errResponse.Error),
walleterror.WithServerErrorMessage(errResponse.ErrorDescription))
}
}

Expand Down
46 changes: 41 additions & 5 deletions pkg/walleterror/walleterror.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ type Error struct {
Message string
// The full underlying error.
ParentError string
// A short code provided by the server to represent the specific error.
ServerCode string
// A descriptive message from the server that explains the error in more detail.
ServerMessage string
}

// NewValidationError creates validation error.
Expand All @@ -37,12 +41,44 @@ func NewValidationError(module string, code int, category string, parentError er
}
}

type serverErrorOpts struct {
code string
message string
}

// ServerErrorOpt is a functional option used to customize server-related error fields.
type ServerErrorOpt func(opts *serverErrorOpts)

// NewExecutionError creates execution error.
func NewExecutionError(module string, code int, scenario string, cause error) *Error {
return &Error{
Code: getErrorCode(module, executionError, code),
Category: scenario,
ParentError: cause.Error(),
func NewExecutionError(module string, code int, scenario string, cause error, opts ...ServerErrorOpt) *Error {
errOpts := &serverErrorOpts{}

for _, opt := range opts {
opt(errOpts)
}

err := &Error{
Code: getErrorCode(module, executionError, code),
Category: scenario,
ParentError: cause.Error(),
ServerCode: errOpts.code,
ServerMessage: errOpts.message,
}

return err
}

// WithServerErrorCode sets the server error code.
func WithServerErrorCode(code string) ServerErrorOpt {
return func(opts *serverErrorOpts) {
opts.code = code
}
}

// WithServerErrorMessage sets the server error message.
func WithServerErrorMessage(message string) ServerErrorOpt {
return func(opts *serverErrorOpts) {
opts.message = message
}
}

Expand Down
2 changes: 1 addition & 1 deletion test/integration/fixtures/.env
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

# vc services
VC_REST_IMAGE=ghcr.io/trustbloc-cicd/vc-server
VC_REST_IMAGE_TAG=v1.12.1-snapshot-8d5b3bf
VC_REST_IMAGE_TAG=v1.12.2-snapshot-4a9fa1a

# Remote JSON-LD context provider
CONTEXT_PROVIDER_URL=https://file-server.trustbloc.local:10096/ld-contexts.json
Expand Down
Loading