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: Enumerate error codes #1493

Merged
merged 1 commit into from
Oct 24, 2023
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
26 changes: 13 additions & 13 deletions pkg/event/spi/spi.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,27 @@ type EventType string

const (
// VerifierOIDCInteractionInitiated verifier oidc event.
VerifierOIDCInteractionInitiated = "verifier.oidc-interaction-initiated.v1"
VerifierOIDCInteractionInitiated EventType = "verifier.oidc-interaction-initiated.v1"
// VerifierOIDCInteractionQRScanned verifier oidc event.
VerifierOIDCInteractionQRScanned = "verifier.oidc-interaction-qr-scanned.v1"
VerifierOIDCInteractionQRScanned EventType = "verifier.oidc-interaction-qr-scanned.v1"
// VerifierOIDCInteractionSucceeded verifier oidc event.
VerifierOIDCInteractionSucceeded = "verifier.oidc-interaction-succeeded.v1"
VerifierOIDCInteractionSucceeded EventType = "verifier.oidc-interaction-succeeded.v1"
// VerifierOIDCInteractionFailed verifier oidc event.
VerifierOIDCInteractionFailed = "verifier.oidc-interaction-failed.v1"
VerifierOIDCInteractionClaimsRetrieved = "verifier.oidc-interaction-claims-retrieved.v1"
VerifierOIDCInteractionFailed EventType = "verifier.oidc-interaction-failed.v1"
VerifierOIDCInteractionClaimsRetrieved EventType = "verifier.oidc-interaction-claims-retrieved.v1"

// IssuerOIDCInteractionInitiated Issuer oidc event.
IssuerOIDCInteractionInitiated = EventType("issuer.oidc-interaction-initiated.v1")
IssuerOIDCInteractionInitiated EventType = "issuer.oidc-interaction-initiated.v1"
// IssuerOIDCInteractionQRScanned Issuer oidc event.
IssuerOIDCInteractionQRScanned = EventType("issuer.oidc-interaction-qr-scanned.v1")
IssuerOIDCInteractionQRScanned EventType = "issuer.oidc-interaction-qr-scanned.v1"
// IssuerOIDCInteractionSucceeded Issuer oidc event.
IssuerOIDCInteractionSucceeded = EventType("issuer.oidc-interaction-succeeded.v1")
IssuerOIDCInteractionAuthorizationRequestPrepared = EventType("issuer.oidc-interaction-authorization-request-prepared.v1") //nolint
IssuerOIDCInteractionAuthorizationCodeStored = EventType("issuer.oidc-interaction-authorization-code-stored.v1") //nolint
IssuerOIDCInteractionAuthorizationCodeExchanged = EventType("issuer.oidc-interaction-authorization-code-exchanged.v1") //nolint
IssuerOIDCInteractionFailed = EventType("issuer.oidc-interaction-failed.v1")
IssuerOIDCInteractionSucceeded EventType = "issuer.oidc-interaction-succeeded.v1"
IssuerOIDCInteractionAuthorizationRequestPrepared EventType = "issuer.oidc-interaction-authorization-request-prepared.v1" //nolint
IssuerOIDCInteractionAuthorizationCodeStored EventType = "issuer.oidc-interaction-authorization-code-stored.v1" //nolint
IssuerOIDCInteractionAuthorizationCodeExchanged EventType = "issuer.oidc-interaction-authorization-code-exchanged.v1" //nolint
IssuerOIDCInteractionFailed EventType = "issuer.oidc-interaction-failed.v1"

CredentialStatusStatusUpdated = EventType("issuer.credential-status-updated.v1")
CredentialStatusStatusUpdated EventType = "issuer.credential-status-updated.v1" //nolint:gosec
)

// Payload defines payload.
Expand Down
107 changes: 102 additions & 5 deletions pkg/restapi/resterr/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ SPDX-License-Identifier: Apache-2.0
package resterr

import (
"errors"
"fmt"
"net/http"
)

type ErrorCode string

//nolint:gosec
const (
SystemError ErrorCode = "system-error"
Unauthorized ErrorCode = "unauthorized"
Expand All @@ -26,9 +28,75 @@ const (
OIDCPreAuthorizeExpectPin ErrorCode = "oidc-pre-authorize-expect-pin"
OIDCPreAuthorizeInvalidPin ErrorCode = "oidc-pre-authorize-invalid-pin"
OIDCPreAuthorizeInvalidClientID ErrorCode = "oidc-pre-authorize-invalid-client-id"
OIDCCredentialFormatNotSupported ErrorCode = "oidc-credential-format-not-supported" //nolint:gosec
OIDCCredentialTypeNotSupported ErrorCode = "oidc-credential-type-not-supported" //nolint:gosec
OIDCCredentialFormatNotSupported ErrorCode = "oidc-credential-format-not-supported"
OIDCCredentialTypeNotSupported ErrorCode = "oidc-credential-type-not-supported"
InvalidOrMissingProofOIDCErr ErrorCode = "invalid_or_missing_proof"

ProfileNotFound ErrorCode = "profile-not-found"
ProfileInactive ErrorCode = "profile-inactive"
TransactionNotFound ErrorCode = "transaction-not-found"
CredentialTemplateNotFound ErrorCode = "credential-template-not-found"
PresentationVerificationFailed ErrorCode = "presentation-verification-failed"
DuplicatePresentationID ErrorCode = "duplicate-presentation-id"
PresentationDefinitionMismatch ErrorCode = "presentation-definition-mismatch"
ClaimsNotReceived ErrorCode = "claims-not-received"
ClaimsNotFound ErrorCode = "claims-not-found"
DataNotFound ErrorCode = "data-not-found"
OpStateKeyDuplication ErrorCode = "op-state-key duplication"
CredentialTemplateNotConfigured ErrorCode = "credential-template-not-configured"
CredentialTemplateIDRequired ErrorCode = "credential-template-id-required"
AuthorizedCodeFlowNotSupported ErrorCode = "authorized-code-flow-not-supported"
ResponseTypeMismatch ErrorCode = "response-type-mismatch"
InvalidScope ErrorCode = "invalid-scope"
CredentialTypeNotSupported ErrorCode = "credential-type-not-supported"
CredentialFormatNotSupported ErrorCode = "credential-format-not-supported"
VCOptionsNotConfigured ErrorCode = "vc-options-not-configured"
InvalidIssuerURL ErrorCode = "invalid-issuer-url"
)

type Component = string

//nolint:gosec
const (
IssuerSvcComponent Component = "issuer.service"
IssuerProfileSvcComponent Component = "issuer.profile-service"
IssueCredentialSvcComponent Component = "issuer.issue-credential-service"
IssuerOIDC4ciSvcComponent Component = "issuer.oidc4ci-service"

VerifierVerifyCredentialSvcComponent Component = "verifier.verify-credential-service"
VerifierOIDC4vpSvcComponent Component = "verifier.oidc4vp-service"
VerifierProfileSvcComponent Component = "verifier.profile-service"
VerifierTxnMgrComponent Component = "verifier.txn-mgr"
VerifierVCSignerComponent Component = "verifier.vc-signer"
VerifierKMSRegistryComponent Component = "verifier.kms-registry"
VerifierPresentationVerifierComponent Component = "verifier.presentation-verifier"
VerifierDataIntegrityVerifier Component = "verifier.data-integrity-verifier"

ClientIDSchemeSvcComponent Component = "client-id-scheme-service"
ClientManagerComponent Component = "client-manager"
WellKnownSvcComponent Component = "well-known-service"
DataProtectorComponent Component = "data-protector"
ClaimDataStoreComponent Component = "claim-data-store"
TransactionStoreComponent Component = "transaction-store"
CryptoJWTSignerComponent Component = "crypto-jwt-signer"
CredentialOfferReferenceStoreComponent Component = "credential-offer-reference-store"
RedisComponent Component = "redis-service"
)

var (
ErrDataNotFound = NewCustomError(DataNotFound, errors.New("data not found"))
ErrOpStateKeyDuplication = NewCustomError(OpStateKeyDuplication, errors.New("op state key duplication"))
ErrProfileInactive = NewCustomError(ProfileInactive, errors.New("profile not active"))
ErrCredentialTemplateNotFound = NewCustomError(CredentialTemplateNotFound, errors.New("credential template not found")) //nolint:lll
ErrCredentialTemplateNotConfigured = NewCustomError(CredentialTemplateNotConfigured, errors.New("credential template not configured")) //nolint:lll
ErrCredentialTemplateIDRequired = NewCustomError(CredentialTemplateIDRequired, errors.New("credential template ID is required")) //nolint:lll
ErrAuthorizedCodeFlowNotSupported = NewCustomError(AuthorizedCodeFlowNotSupported, errors.New("authorized code flow not supported")) //nolint:lll
ErrResponseTypeMismatch = NewCustomError(ResponseTypeMismatch, errors.New("response type mismatch"))
ErrInvalidScope = NewCustomError(InvalidScope, errors.New("invalid scope"))
ErrCredentialTypeNotSupported = NewCustomError(CredentialTypeNotSupported, errors.New("credential type not supported")) //nolint:lll
ErrCredentialFormatNotSupported = NewCustomError(CredentialFormatNotSupported, errors.New("credential format not supported")) //nolint:lll
ErrVCOptionsNotConfigured = NewCustomError(VCOptionsNotConfigured, errors.New("vc options not configured"))
ErrInvalidIssuerURL = NewCustomError(InvalidIssuerURL, errors.New("invalid issuer url"))
)

func (c ErrorCode) Name() string {
Expand All @@ -39,11 +107,11 @@ type CustomError struct {
Code ErrorCode
IncorrectValue string
FailedOperation string
Component string
Component Component
Err error
}

func NewSystemError(component, failedOperation string, err error) *CustomError {
func NewSystemError(component Component, failedOperation string, err error) *CustomError {
return &CustomError{
Code: SystemError,
FailedOperation: failedOperation,
Expand Down Expand Up @@ -86,11 +154,20 @@ func (e *CustomError) Error() string {
if e.Code == SystemError {
return fmt.Sprintf("%s[%s, %s]: %v", SystemError, e.Component, e.FailedOperation, e.Err)
}

if e.Code == Unauthorized {
return fmt.Sprintf("%s: %v", e.Code, e.Err)
}

return fmt.Sprintf("%s[%s]: %v", e.Code, e.IncorrectValue, e.Err)
if e.IncorrectValue != "" {
return fmt.Sprintf("%s[%s]: %v", e.Code, e.IncorrectValue, e.Err)
}

return fmt.Sprintf("%s: %v", e.Code, e.Err)
}

func (e *CustomError) Unwrap() error {
return e.Err
}

func (e *CustomError) HTTPCodeMsg() (int, interface{}) {
Expand All @@ -104,16 +181,25 @@ func (e *CustomError) HTTPCodeMsg() (int, interface{}) {
"operation": e.FailedOperation,
"message": e.Err.Error(),
}

case Unauthorized:
return http.StatusUnauthorized, map[string]interface{}{
"code": Unauthorized.Name(),
"message": e.Err.Error(),
}

case ProfileNotFound:
return http.StatusNotFound, map[string]interface{}{
"code": ProfileNotFound.Name(),
"message": e.Err.Error(),
}

case OIDCError:
return http.StatusBadRequest, map[string]interface{}{
"error": e.Component,
"_raw": e.Err.Error(),
}

case AlreadyExist:
code = http.StatusConflict

Expand Down Expand Up @@ -148,3 +234,14 @@ type RegistrationError struct {
func (e *RegistrationError) Error() string {
return e.Err.Error()
}

// GetErrorDetails extracts the error message, error code and component from the given error. If the error
// is not a CustomError implementation then the error code and component will be empty.
func GetErrorDetails(err error) (string, string, Component) {
var ce *CustomError
if ok := errors.As(err, &ce); ok {
return ce.Err.Error(), string(ce.Code), ce.Component
}

return err.Error(), "", ""
}
35 changes: 35 additions & 0 deletions pkg/restapi/resterr/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package resterr

import (
"errors"
"fmt"
"net/http"
"testing"

Expand Down Expand Up @@ -80,4 +81,38 @@ func TestNewValidationError(t *testing.T) {
requireCode(t, resp, ConditionNotMet.Name())
requireMessage(t, resp, "some error")
})

t.Run("profile not found error", func(t *testing.T) {
err := NewCustomError(ProfileNotFound, errors.New("some error"))
require.Equal(t, "profile-not-found: some error", err.Error())

httpCode, resp := err.HTTPCodeMsg()

require.Equal(t, http.StatusNotFound, httpCode)
requireCode(t, resp, ProfileNotFound.Name())
requireMessage(t, resp, "some error")
})
}

func TestGetErrorDetails(t *testing.T) {
t.Run("custom error", func(t *testing.T) {
e := errors.New("some error")

err := fmt.Errorf("got error: %w",
NewSystemError(TransactionStoreComponent, "getData", e))

errMsg, errCode, errComponent := GetErrorDetails(err)
require.Equal(t, e.Error(), errMsg)
require.Equal(t, string(SystemError), errCode)
require.Equal(t, TransactionStoreComponent, errComponent)
})

t.Run("other error", func(t *testing.T) {
err := errors.New("some error")

errMsg, errCode, errComponent := GetErrorDetails(err)
require.Equal(t, err.Error(), errMsg)
require.Empty(t, errCode)
require.Empty(t, errComponent)
})
}
44 changes: 20 additions & 24 deletions pkg/restapi/v1/issuer/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ import (
var logger = log.New("restapi-issuer")

const (
issuerProfileSvcComponent = "issuer.ProfileService"
oidc4ciSvcComponent = "OIDC4CIService"

defaultCtx = "https://www.w3.org/2018/credentials/v1"
)

Expand Down Expand Up @@ -320,7 +317,7 @@ func (c *Controller) signCredential(
) (*verifiable.Credential, error) {
signedVC, err := c.issueCredentialService.IssueCredential(ctx, credential, profile, opts...)
if err != nil {
return nil, resterr.NewSystemError("IssueCredentialService", "IssueCredential", err)
return nil, resterr.NewSystemError(resterr.IssueCredentialSvcComponent, "IssueCredential", err)
}

return signedVC, nil
Expand Down Expand Up @@ -503,16 +500,14 @@ func (c *Controller) initiateIssuance(

resp, err := c.oidc4ciService.InitiateIssuance(ctx, issuanceReq, profile)
if err != nil {
if errors.Is(err, oidc4ci.ErrCredentialTemplateNotFound) ||
errors.Is(err, oidc4ci.ErrCredentialTemplateIDRequired) {
e := resterr.NewValidationError(resterr.InvalidValue, "credential_template_id", err)

c.sendFailedEvent(ctx, profile.OrganizationID, profile.ID, profile.Version, e)
if errors.Is(err, resterr.ErrCredentialTemplateNotFound) ||
errors.Is(err, resterr.ErrCredentialTemplateIDRequired) {
c.sendFailedEvent(ctx, profile.OrganizationID, profile.ID, profile.Version, err)

return nil, "", e
return nil, "", err
}

e := resterr.NewSystemError(oidc4ciSvcComponent, "InitiateIssuance", err)
e := resterr.NewSystemError(resterr.IssuerOIDC4ciSvcComponent, "InitiateIssuance", err)

c.sendFailedEvent(ctx, profile.OrganizationID, profile.ID, profile.Version, e)

Expand Down Expand Up @@ -541,15 +536,15 @@ func (c *Controller) PushAuthorizationDetails(ctx echo.Context) error {
}

if err = c.oidc4ciService.PushAuthorizationDetails(ctx.Request().Context(), body.OpState, ad); err != nil {
if errors.Is(err, oidc4ci.ErrCredentialTypeNotSupported) {
if errors.Is(err, resterr.ErrCredentialTypeNotSupported) {
return resterr.NewValidationError(resterr.InvalidValue, "authorization_details.type", err)
}

if errors.Is(err, oidc4ci.ErrCredentialFormatNotSupported) {
if errors.Is(err, resterr.ErrCredentialFormatNotSupported) {
return resterr.NewValidationError(resterr.InvalidValue, "authorization_details.format", err)
}

return resterr.NewSystemError(oidc4ciSvcComponent, "PushAuthorizationRequest", err)
return resterr.NewSystemError(resterr.IssuerOIDC4ciSvcComponent, "PushAuthorizationRequest", err)
}

return ctx.NoContent(http.StatusOK)
Expand Down Expand Up @@ -585,12 +580,12 @@ func (c *Controller) prepareClaimDataAuthorizationRequest(
},
)
if err != nil {
return nil, resterr.NewSystemError(oidc4ciSvcComponent, "PrepareClaimDataAuthorizationRequest", err)
return nil, resterr.NewSystemError(resterr.IssuerOIDC4ciSvcComponent, "PrepareClaimDataAuthorizationRequest", err)
}

profile, err := c.profileSvc.GetProfile(resp.ProfileID, resp.ProfileVersion)
if err != nil {
return nil, resterr.NewSystemError(oidc4ciSvcComponent, "PrepareClaimDataAuthorizationRequest", err)
return nil, resterr.NewSystemError(resterr.IssuerOIDC4ciSvcComponent, "PrepareClaimDataAuthorizationRequest", err)
}

return &PrepareClaimDataAuthorizationResponse{
Expand All @@ -611,15 +606,15 @@ func (c *Controller) accessProfile(profileID, profileVersion string) (*profileap
profile, err := c.profileSvc.GetProfile(profileID, profileVersion)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return nil, resterr.NewValidationError(resterr.DoesntExist, "profile",
return nil, resterr.NewCustomError(resterr.ProfileNotFound,
fmt.Errorf("profile with given id %s_%s, doesn't exist", profileID, profileVersion))
}

return nil, resterr.NewSystemError(issuerProfileSvcComponent, "GetProfile", err)
return nil, resterr.NewSystemError(resterr.IssuerProfileSvcComponent, "GetProfile", err)
}

if profile == nil {
return nil, resterr.NewValidationError(resterr.DoesntExist, "profile",
return nil, resterr.NewCustomError(resterr.ProfileNotFound,
fmt.Errorf("profile with given id %s_%s, doesn't exist", profileID, profileVersion))
}

Expand All @@ -634,7 +629,7 @@ func (c *Controller) accessOIDCProfile(profileID, profileVersion, tenantID strin

// Profiles of other organization is not visible.
if profile.OrganizationID != tenantID {
return nil, resterr.NewValidationError(resterr.DoesntExist, "profile",
return nil, resterr.NewCustomError(resterr.ProfileNotFound,
fmt.Errorf("profile with given id %s_%s, doesn't exist", profileID, profileVersion))
}

Expand Down Expand Up @@ -727,7 +722,7 @@ func (c *Controller) PrepareCredential(e echo.Context) error {
return custom
}

return resterr.NewSystemError(oidc4ciSvcComponent, "PrepareCredential", err)
return resterr.NewSystemError(resterr.IssuerOIDC4ciSvcComponent, "PrepareCredential", err)
}

profile, err := c.accessProfile(result.ProfileID, result.ProfileVersion)
Expand All @@ -736,7 +731,7 @@ func (c *Controller) PrepareCredential(e echo.Context) error {
}

if result.Credential == nil {
return resterr.NewSystemError(oidc4ciSvcComponent, "PrepareCredential",
return resterr.NewSystemError(resterr.IssuerOIDC4ciSvcComponent, "PrepareCredential",
errors.New("credentials should not be nil"))
}

Expand Down Expand Up @@ -925,17 +920,18 @@ func (c *Controller) sendFailedEvent(ctx context.Context, orgID, profileID, prof
OrgID: orgID,
ProfileID: profileID,
ProfileVersion: profileVersion,
Error: e.Error(),
}

ep.Error, ep.ErrorCode, ep.ErrorComponent = resterr.GetErrorDetails(e)

payload, err := c.marshal(ep)
if err != nil {
logger.Errorc(ctx, "Error sending event due to marshalling error", log.WithError(err))

return
}

evt := spi.NewEventWithPayload(uuid.NewString(), "source://vcs/issuer", spi.VerifierOIDCInteractionFailed, payload)
evt := spi.NewEventWithPayload(uuid.NewString(), "source://vcs/issuer", spi.IssuerOIDCInteractionFailed, payload)

err = c.eventSvc.Publish(ctx, c.eventTopic, evt)
if err != nil {
Expand Down
Loading