diff --git a/internal/api/credentials.go b/internal/api/credentials.go index ae4137f7..1802572d 100644 --- a/internal/api/credentials.go +++ b/internal/api/credentials.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "slices" "strings" "time" @@ -76,7 +77,7 @@ func (s *Server) CreateCredential(ctx context.Context, request CreateCredentialR } var credentialStatusType *verifiable.CredentialStatusType - credentialStatusType, err = validateStatusType((*string)(request.Body.CredentialStatusType)) + credentialStatusType, err = s.validateStatusType(ctx, did, (*string)(request.Body.CredentialStatusType)) if err != nil { return CreateCredential400JSONResponse{N400JSONResponse{Message: err.Error()}}, nil } @@ -349,6 +350,30 @@ func (s *Server) GetCredentialOffer(ctx context.Context, request GetCredentialOf }, nil } +// validateStatusType - validate credential status type. +// If credentialStatusTypeRequest is nil or empty, it will return the credential status type from the auth claim (first non revoked). +func (s *Server) validateStatusType(ctx context.Context, did *w3c.DID, credentialStatusTypeRequest *string) (*verifiable.CredentialStatusType, error) { + var credentialStatusType verifiable.CredentialStatusType + if credentialStatusTypeRequest != nil && *credentialStatusTypeRequest != "" { + allowedCredentialStatuses := []string{string(verifiable.Iden3commRevocationStatusV1), string(verifiable.Iden3ReverseSparseMerkleTreeProof), string(verifiable.Iden3OnchainSparseMerkleTreeProof2023)} + if !slices.Contains(allowedCredentialStatuses, *credentialStatusTypeRequest) { + return nil, fmt.Errorf("Invalid Credential Status Type '%s'. Allowed Iden3commRevocationStatusV1.0, Iden3ReverseSparseMerkleTreeProof or Iden3OnchainSparseMerkleTreeProof2023.", *credentialStatusTypeRequest) + } + credentialStatusType = (verifiable.CredentialStatusType)(*credentialStatusTypeRequest) + } else { + credentialStatusType = verifiable.Iden3commRevocationStatusV1 + authClaim, _ := s.claimService.GetAuthClaim(ctx, did) + if authClaim != nil { + credentialStatus, _ := authClaim.GetCredentialStatus() + + if credentialStatus != nil { + credentialStatusType = credentialStatus.Type + } + } + } + return &credentialStatusType, nil +} + func toVerifiableRefreshService(s *RefreshService) *verifiable.RefreshService { if s == nil { return nil diff --git a/internal/api/credentials_test.go b/internal/api/credentials_test.go index f782cfcc..85d8c9ee 100644 --- a/internal/api/credentials_test.go +++ b/internal/api/credentials_test.go @@ -559,20 +559,23 @@ func TestServer_DeleteCredential(t *testing.T) { } func TestServer_GetCredentialQrCode(t *testing.T) { - idStr := "did:polygonid:polygon:mumbai:2qPrv5Yx8s1qAmEnPym68LfT7gTbASGampiGU7TseL" + const ( + method = "polygonid" + blockchain = "polygon" + network = "amoy" + BJJ = "BJJ" + ) idNoClaims := "did:polygonid:polygon:mumbai:2qGjTUuxZKqKS4Q8UmxHUPw55g15QgEVGnj6Wkq8Vk" - identity := &domain.Identity{ - Identifier: idStr, - } - fixture := repositories.NewFixture(storage) - fixture.CreateIdentity(t, identity) - claim := fixture.NewClaim(t, identity.Identifier) - fixture.CreateClaim(t, claim) - server := newTestServer(t, nil) handler := getHandler(context.Background(), server) + identity, err := server.Services.identity.Create(context.Background(), "http://localhost:3001", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) + assert.NoError(t, err) + + claim := fixture.NewClaim(t, identity.Identifier) + fixture.CreateClaim(t, claim) + type expected struct { response GetCredentialOfferResponseObject httpCode int @@ -591,7 +594,7 @@ func TestServer_GetCredentialQrCode(t *testing.T) { { name: "No auth", auth: authWrong, - did: idStr, + did: identity.Identifier, claim: claim.ID, expected: expected{ httpCode: http.StatusUnauthorized, @@ -600,7 +603,7 @@ func TestServer_GetCredentialQrCode(t *testing.T) { { name: "should get an error non existing claimID", auth: authOk, - did: idStr, + did: identity.Identifier, claim: uuid.New(), expected: expected{ response: GetCredentialOffer404JSONResponse{N404JSONResponse{ @@ -636,7 +639,7 @@ func TestServer_GetCredentialQrCode(t *testing.T) { { name: "Happy path, no type", auth: authOk, - did: idStr, + did: identity.Identifier, claim: claim.ID, qrType: nil, expected: expected{ @@ -648,7 +651,7 @@ func TestServer_GetCredentialQrCode(t *testing.T) { { name: "Happy path, type universalLink", auth: authOk, - did: idStr, + did: identity.Identifier, claim: claim.ID, qrType: common.ToPointer("universalLink"), expected: expected{ @@ -660,7 +663,7 @@ func TestServer_GetCredentialQrCode(t *testing.T) { { name: "Happy path, type deeplink", auth: authOk, - did: idStr, + did: identity.Identifier, claim: claim.ID, qrType: common.ToPointer("deepLink"), expected: expected{ @@ -672,7 +675,7 @@ func TestServer_GetCredentialQrCode(t *testing.T) { { name: "Happy path, type raw", auth: authOk, - did: idStr, + did: identity.Identifier, claim: claim.ID, qrType: common.ToPointer("raw"), expected: expected{ @@ -740,29 +743,21 @@ func TestServer_GetCredentialQrCode(t *testing.T) { func TestServer_GetCredential(t *testing.T) { server := newTestServer(t, nil) - idStr := "did:polygonid:polygon:mumbai:2qLduMv2z7hnuhzkcTWesCUuJKpRVDEThztM4tsJUj" idStrWithoutClaims := "did:polygonid:polygon:mumbai:2qGjTUuxZKqKS4Q8UmxHUPw55g15QgEVGnj6Wkq8Vk" - identity := &domain.Identity{ - Identifier: idStr, - } fixture := repositories.NewFixture(storage) - fixture.CreateIdentity(t, identity) + const ( + method = "polygonid" + blockchain = "polygon" + network = "amoy" + BJJ = "BJJ" + ) + + identity, err := server.Services.identity.Create(context.Background(), "http://localhost:3001", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) + assert.NoError(t, err) claim := fixture.NewClaim(t, identity.Identifier) claim.MtProof = true fixture.CreateClaim(t, claim) - - query := repositories.ExecQueryParams{ - Query: `INSERT INTO identity_mts (identifier, type) VALUES - ($1, 0), - ($1, 1), - ($1, 2), - ($1, 3)`, - Arguments: []interface{}{idStr}, - } - - fixture.ExecQuery(t, query) - handler := getHandler(context.Background(), server) type expected struct { @@ -782,7 +777,7 @@ func TestServer_GetCredential(t *testing.T) { { name: "No auth header", auth: authWrong, - did: idStr, + did: identity.Identifier, expected: expected{ httpCode: http.StatusUnauthorized, }, @@ -790,7 +785,7 @@ func TestServer_GetCredential(t *testing.T) { { name: "should get an error non existing claimID", auth: authOk, - did: idStr, + did: identity.Identifier, claimID: uuid.New(), expected: expected{ httpCode: http.StatusNotFound, @@ -826,7 +821,7 @@ func TestServer_GetCredential(t *testing.T) { { name: "should get the credentials", auth: authOk, - did: idStr, + did: identity.Identifier, claimID: claim.ID, expected: expected{ httpCode: http.StatusOK, @@ -839,7 +834,7 @@ func TestServer_GetCredential(t *testing.T) { Type: "JsonSchemaValidator2018", }, CredentialStatus: verifiable.CredentialStatus{ - ID: fmt.Sprintf("http://localhost/v2/%s/credentials/revocation/status/%d", idStr, claim.RevNonce), + ID: fmt.Sprintf("http://localhost/v2/%s/credentials/revocation/status/%d", identity.Identifier, claim.RevNonce), Type: "SparseMerkleTreeProof", RevocationNonce: uint64(claim.RevNonce), }, @@ -851,7 +846,7 @@ func TestServer_GetCredential(t *testing.T) { }, ID: fmt.Sprintf("http://localhost/api/v2/credentials/%s", claim.ID), IssuanceDate: common.ToPointer(time.Now()), - Issuer: idStr, + Issuer: identity.Identifier, Type: []string{"VerifiableCredential", "KYCAgeCredential"}, RefreshService: &verifiable.RefreshService{ ID: "https://refresh-service.xyz", diff --git a/internal/api/identity.go b/internal/api/identity.go index e93584c3..1b8c90f2 100644 --- a/internal/api/identity.go +++ b/internal/api/identity.go @@ -211,6 +211,24 @@ func (s *Server) GetIdentities(ctx context.Context, request GetIdentitiesRequest credStatusType := GetIdentitiesResponseCredentialStatusType(credentialStatus.Type) authBjjCredStatus = &credStatusType } + } else { + // this case handle eth identity without published auth claim + authClaim, err := s.claimService.GetFirstNonRevokedAuthClaim(ctx, did) + if err != nil { + log.Error(ctx, "get identities. Getting first non revoked auth claim", "err", err) + return GetIdentities500JSONResponse{N500JSONResponse{ + Message: err.Error(), + }}, nil + } + credentialStatus, err := authClaim.GetCredentialStatus() + if err != nil { + log.Error(ctx, "get identities. Getting credential status", "err", err) + return GetIdentities500JSONResponse{N500JSONResponse{ + Message: err.Error(), + }}, nil + } + credStatusType := GetIdentitiesResponseCredentialStatusType(credentialStatus.Type) + authBjjCredStatus = &credStatusType } items := strings.Split(identity.Identifier, ":") diff --git a/internal/api/identity_test.go b/internal/api/identity_test.go index 27891dc2..8f7f8133 100644 --- a/internal/api/identity_test.go +++ b/internal/api/identity_test.go @@ -17,11 +17,9 @@ import ( "github.com/stretchr/testify/require" "github.com/polygonid/sh-id-platform/internal/common" - "github.com/polygonid/sh-id-platform/internal/core/domain" "github.com/polygonid/sh-id-platform/internal/core/ports" "github.com/polygonid/sh-id-platform/internal/db/tests" "github.com/polygonid/sh-id-platform/internal/kms" - "github.com/polygonid/sh-id-platform/internal/repositories" ) func TestServer_CreateIdentity(t *testing.T) { @@ -275,14 +273,24 @@ func TestServer_CreateIdentity(t *testing.T) { } func TestServer_GetIdentities(t *testing.T) { + const ( + method = "polygonid" + blockchain = "polygon" + network = "amoy" + BJJ = "BJJ" + ETH = "ETH" + ) server := newTestServer(t, nil) handler := getHandler(context.Background(), server) - identity1 := &domain.Identity{Identifier: "did:polygonid:polygon:mumbai:2qE1ZT16aqEWhh9mX9aqM2pe2ZwV995dTkReeKwCaQ"} - identity2 := &domain.Identity{Identifier: "did:polygonid:polygon:mumbai:2qMHFTHn2SC3XkBEJrR4eH4Yk8jRGg5bzYYG1ZGECa"} - fixture := repositories.NewFixture(storage) - fixture.CreateIdentity(t, identity1) - fixture.CreateIdentity(t, identity2) + identity1, err := server.Services.identity.Create(context.Background(), "http://localhost:3001", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) + assert.NoError(t, err) + + identity2, err := server.Services.identity.Create(context.Background(), "http://localhost:3001", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) + assert.NoError(t, err) + + identity3, err := server.Services.identity.Create(context.Background(), "http://localhost:3001", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: ETH}) + assert.NoError(t, err) type expected struct { httpCode int @@ -321,7 +329,13 @@ func TestServer_GetIdentities(t *testing.T) { var response GetIdentities200JSONResponse assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) assert.Equal(t, tc.expected.httpCode, rr.Code) - assert.True(t, len(response) >= 2) + assert.True(t, len(response) >= 3) + assert.Equal(t, identity1.Identifier, response[len(response)-3].Identifier) + assert.NotNil(t, response[len(response)-3].CredentialStatusType) + assert.Equal(t, identity2.Identifier, response[len(response)-2].Identifier) + assert.NotNil(t, response[len(response)-2].CredentialStatusType) + assert.Equal(t, identity3.Identifier, response[len(response)-1].Identifier) + assert.NotNil(t, response[len(response)-1].CredentialStatusType) } }) } @@ -423,18 +437,15 @@ func TestServer_UpdateIdentity(t *testing.T) { server := newTestServer(t, nil) handler := getHandler(context.Background(), server) - identity := &domain.Identity{Identifier: "did:polygonid:polygon:amoy:2qQ8S2VKdQv7xYgzCn7KW2xgzUWrTRQjoZDYavJHBq"} - fixture := repositories.NewFixture(storage) - fixture.CreateIdentity(t, identity) + const ( + method = "polygonid" + blockchain = "polygon" + network = "amoy" + BJJ = "BJJ" + ) - state := domain.IdentityState{ - Identifier: identity.Identifier, - State: common.ToPointer("state"), - Status: domain.StatusCreated, - ModifiedAt: time.Now(), - CreatedAt: time.Now(), - } - fixture.CreateIdentityStatus(t, state) + identity, err := server.Services.identity.Create(context.Background(), "http://localhost:3001", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) + assert.NoError(t, err) type expected struct { httpCode int diff --git a/internal/core/ports/claim_service.go b/internal/core/ports/claim_service.go index eed92d52..7def70bc 100644 --- a/internal/core/ports/claim_service.go +++ b/internal/core/ports/claim_service.go @@ -223,6 +223,7 @@ type ClaimService interface { GetCredentialQrCode(ctx context.Context, issID *w3c.DID, id uuid.UUID, hostURL string) (*GetCredentialQrCodeResponse, error) Agent(ctx context.Context, req *AgentRequest, mediatype iden3comm.MediaType) (*domain.Agent, error) GetAuthClaim(ctx context.Context, did *w3c.DID) (*domain.Claim, error) + GetFirstNonRevokedAuthClaim(ctx context.Context, did *w3c.DID) (*domain.Claim, error) GetAuthClaimForPublishing(ctx context.Context, did *w3c.DID, state string) (*domain.Claim, error) UpdateClaimsMTPAndState(ctx context.Context, currentState *domain.IdentityState) error Delete(ctx context.Context, issuerDID *w3c.DID, id uuid.UUID) error diff --git a/internal/core/services/claims.go b/internal/core/services/claims.go index 7d1a936a..6ad039de 100644 --- a/internal/core/services/claims.go +++ b/internal/core/services/claims.go @@ -450,6 +450,20 @@ func (c *claim) GetAuthClaim(ctx context.Context, did *w3c.DID) (*domain.Claim, return c.icRepo.FindOneClaimBySchemaHash(ctx, c.storage.Pgx, did, string(authHash)) } +// GetFirstNonRevokedAuthClaim returns the first non-revoked authentication claim for the given DID. The AuthClaim may not be published +func (c *claim) GetFirstNonRevokedAuthClaim(ctx context.Context, did *w3c.DID) (*domain.Claim, error) { + authHash, err := core.AuthSchemaHash.MarshalText() + if err != nil { + return nil, err + } + authClaims, err := c.icRepo.GetAuthCoreClaims(ctx, c.storage.Pgx, did, string(authHash)) + if err != nil { + return nil, err + } + + return authClaims[0], nil +} + func (c *claim) GetAll(ctx context.Context, did w3c.DID, filter *ports.ClaimsFilter) ([]*domain.Claim, uint, error) { claims, total, err := c.icRepo.GetAllByIssuerID(ctx, c.storage.Pgx, did, filter) if err != nil {