diff --git a/cmd/wallet-sdk-gomobile/openid4ci/issuerinitiatedinteraction.go b/cmd/wallet-sdk-gomobile/openid4ci/issuerinitiatedinteraction.go index e941794f9..8f6b10df5 100644 --- a/cmd/wallet-sdk-gomobile/openid4ci/issuerinitiatedinteraction.go +++ b/cmd/wallet-sdk-gomobile/openid4ci/issuerinitiatedinteraction.go @@ -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 @@ -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 diff --git a/cmd/wallet-sdk-gomobile/walleterror/error.go b/cmd/wallet-sdk-gomobile/walleterror/error.go index 862e5cd1d..868020efb 100644 --- a/cmd/wallet-sdk-gomobile/walleterror/error.go +++ b/cmd/wallet-sdk-gomobile/walleterror/error.go @@ -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. diff --git a/cmd/wallet-sdk-gomobile/wrapper/error.go b/cmd/wallet-sdk-gomobile/wrapper/error.go index 43080c95a..153b79b8b 100644 --- a/cmd/wallet-sdk-gomobile/wrapper/error.go +++ b/cmd/wallet-sdk-gomobile/wrapper/error.go @@ -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, } } diff --git a/pkg/openid4ci/interaction.go b/pkg/openid4ci/interaction.go index 6c821cace..205d25cd0 100644 --- a/pkg/openid4ci/interaction.go +++ b/pkg/openid4ci/interaction.go @@ -543,13 +543,14 @@ 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, @@ -557,47 +558,66 @@ func processCredentialErrorResponse(statusCode int, respBytes []byte) error { 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)) } } diff --git a/pkg/openid4ci/issuerinitiatedinteraction.go b/pkg/openid4ci/issuerinitiatedinteraction.go index d50b566d1..4cbaa8e39 100644 --- a/pkg/openid4ci/issuerinitiatedinteraction.go +++ b/pkg/openid4ci/issuerinitiatedinteraction.go @@ -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)) } } diff --git a/pkg/openid4ci/models.go b/pkg/openid4ci/models.go index 5cf5bc2d7..55808b9e0 100644 --- a/pkg/openid4ci/models.go +++ b/pkg/openid4ci/models.go @@ -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"` diff --git a/pkg/openid4vp/errors.go b/pkg/openid4vp/errors.go index fa962d1de..9d2d1f953 100644 --- a/pkg/openid4vp/errors.go +++ b/pkg/openid4vp/errors.go @@ -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 { diff --git a/pkg/openid4vp/openid4vp.go b/pkg/openid4vp/openid4vp.go index 8cbdcf671..187425ab8 100644 --- a/pkg/openid4vp/openid4vp.go +++ b/pkg/openid4vp/openid4vp.go @@ -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)) } } diff --git a/pkg/walleterror/walleterror.go b/pkg/walleterror/walleterror.go index 269d10896..9074bbdd9 100644 --- a/pkg/walleterror/walleterror.go +++ b/pkg/walleterror/walleterror.go @@ -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. @@ -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 } } diff --git a/test/integration/fixtures/.env b/test/integration/fixtures/.env index 02a62096e..abcf39158 100644 --- a/test/integration/fixtures/.env +++ b/test/integration/fixtures/.env @@ -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