From 4ab6052119ea9e472ea0797593fa9736bdb8fa15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Gim=C3=A9nez?= Date: Mon, 30 Sep 2024 17:54:16 +0200 Subject: [PATCH 1/7] feat: Unify responses in get credential and get credentials (#801) * feat: Changes in getCredential Response * feat: Changes in getCredentials Response * feat: GetConnection and GetConnections use same Credential Entity * feat: Rename CredentialW3C to Credential and remove old API credential entities --- api/api.yaml | 122 +++++-------------------------- internal/api/api.gen.go | 45 ++---------- internal/api/connections.go | 7 +- internal/api/credentials.go | 51 ++----------- internal/api/credentials_test.go | 88 +++++++++++----------- internal/api/responses.go | 87 +++------------------- pkg/schema/schema.go | 15 ---- 7 files changed, 92 insertions(+), 323 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index 88948fa61..f6cda2c13 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -753,7 +753,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/GetCredentialResponse' + $ref: '#/components/schemas/Credential' '400': $ref: '#/components/responses/400' '401': @@ -1803,71 +1803,6 @@ components: type: string x-omitempty: false - Credential: - type: object - required: - - id - - proofTypes - - createdAt - - expired - - schemaHash - - schemaType - - schemaUrl - - revNonce - - credentialSubject - - revoked - - userID - properties: - id: - type: string - x-go-type: uuid.UUID - x-go-type-import: - name: uuid - path: github.com/google/uuid - example: 8edd8112-c415-11ed-b036-debe37e1cbd6 - proofTypes: - type: array - items: - type: string - example: [ "BJJSignature2021" ] - createdAt: - $ref: '#/components/schemas/TimeUTC' - expiresAt: - $ref: '#/components/schemas/TimeUTC' - expired: - type: boolean - example: true - schemaHash: - type: string - example: "c9b2370371b7fa8b3dab2a5ba81b6838" - schemaType: - type: string - example: "KYCAgeCredential" - schemaUrl: - type: string - example: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v3.json" - revoked: - type: boolean - example: false - revNonce: - type: integer - format: uint64 - example: 2136005230 - credentialSubject: - type: object - x-omitempty: false - example: - birthday: 19960424 - documentType: 2 - id: "did:polygonid:polygon:amoy:2qDDDKmo436EZGCBAvkqZjADYoNRJszkG7UymZeCHQ" - userID: - type: string - example: did:polygonid:polygon:amoy:2qFpPHotk6oyaX1fcrpQFT4BMnmg8YszUwxYtaoGoe - refreshService: - $ref: '#/components/schemas/RefreshService' - displayMethod: - $ref: '#/components/schemas/DisplayMethod' - AuthenticationConnection: type: object required: @@ -1896,58 +1831,35 @@ components: connection: $ref: '#/components/schemas/AuthenticationConnection' - GetCredentialResponse: + Credential: type: object required: - id - - "@context" - - type - - credentialSubject - - credentialStatus - - issuer - - credentialSchema - - proof - proofTypes + - revoked + - schemaHash + - vc properties: id: type: string x-omitempty: false - "@context": - type: array - x-omitempty: false - items: - type: string - type: - type: array - x-omitempty: false - items: - type: string - expirationDate: - $ref: '#/components/schemas/TimeUTC' - issuanceDate: - $ref: '#/components/schemas/TimeUTC' - credentialSubject: - type: object - x-omitempty: false - credentialStatus: - type: null - issuer: - type: string - x-omitempty: false - credentialSchema: - $ref: '#/components/schemas/CredentialSchema' - x-omitempty: false proofTypes: type: array items: type: string example: [ "BJJSignature2021" ] - proof: - type: null - displayMethod: - $ref: '#/components/schemas/DisplayMethod' - refreshService: - $ref: '#/components/schemas/RefreshService' + revoked: + type: boolean + example: false + schemaHash: + type: string + example: "c9b2370371b7fa8b3dab2a5ba81b6838" + vc: + type: object + x-go-type: verifiable.W3CCredential + x-go-type-import: + name: verifiable + path: "github.com/iden3/go-schema-processor/v2/verifiable" QrCodeLinkShortResponse: type: object diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 0b624f402..2e6ab8aa6 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -13,6 +13,7 @@ import ( "github.com/go-chi/chi/v5" uuid "github.com/google/uuid" + verifiable "github.com/iden3/go-schema-processor/v2/verifiable" protocol "github.com/iden3/iden3comm/v2/protocol" "github.com/oapi-codegen/runtime" strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" @@ -265,20 +266,11 @@ type CreateLinkRequest struct { // Credential defines model for Credential. type Credential struct { - CreatedAt TimeUTC `json:"createdAt"` - CredentialSubject map[string]interface{} `json:"credentialSubject"` - DisplayMethod *DisplayMethod `json:"displayMethod,omitempty"` - Expired bool `json:"expired"` - ExpiresAt *TimeUTC `json:"expiresAt"` - Id uuid.UUID `json:"id"` - ProofTypes []string `json:"proofTypes"` - RefreshService *RefreshService `json:"refreshService,omitempty"` - RevNonce uint64 `json:"revNonce"` - Revoked bool `json:"revoked"` - SchemaHash string `json:"schemaHash"` - SchemaType string `json:"schemaType"` - SchemaUrl string `json:"schemaUrl"` - UserID string `json:"userID"` + Id string `json:"id"` + ProofTypes []string `json:"proofTypes"` + Revoked bool `json:"revoked"` + SchemaHash string `json:"schemaHash"` + Vc verifiable.W3CCredential `json:"vc"` } // CredentialLinkQrCodeResponse defines model for CredentialLinkQrCodeResponse. @@ -290,12 +282,6 @@ type CredentialLinkQrCodeResponse struct { UniversalLink string `json:"universalLink"` } -// CredentialSchema defines model for CredentialSchema. -type CredentialSchema struct { - Id string `json:"id"` - Type string `json:"type"` -} - // CredentialSubject defines model for CredentialSubject. type CredentialSubject = map[string]interface{} @@ -341,23 +327,6 @@ type GetConnectionResponse struct { // GetConnectionsResponse defines model for GetConnectionsResponse. type GetConnectionsResponse = []GetConnectionResponse -// GetCredentialResponse defines model for GetCredentialResponse. -type GetCredentialResponse struct { - Context []string `json:"@context"` - CredentialSchema CredentialSchema `json:"credentialSchema"` - CredentialStatus interface{} `json:"credentialStatus"` - CredentialSubject map[string]interface{} `json:"credentialSubject"` - DisplayMethod *DisplayMethod `json:"displayMethod,omitempty"` - ExpirationDate *TimeUTC `json:"expirationDate"` - Id string `json:"id"` - IssuanceDate *TimeUTC `json:"issuanceDate"` - Issuer string `json:"issuer"` - Proof interface{} `json:"proof"` - ProofTypes []string `json:"proofTypes"` - RefreshService *RefreshService `json:"refreshService,omitempty"` - Type []string `json:"type"` -} - // GetIdentitiesResponse defines model for GetIdentitiesResponse. type GetIdentitiesResponse struct { Blockchain string `json:"blockchain"` @@ -4034,7 +4003,7 @@ type GetCredentialResponseObject interface { VisitGetCredentialResponse(w http.ResponseWriter) error } -type GetCredential200JSONResponse GetCredentialResponse +type GetCredential200JSONResponse Credential func (response GetCredential200JSONResponse) VisitGetCredentialResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") diff --git a/internal/api/connections.go b/internal/api/connections.go index c28cf8683..58b61ed79 100644 --- a/internal/api/connections.go +++ b/internal/api/connections.go @@ -17,7 +17,6 @@ import ( "github.com/polygonid/sh-id-platform/internal/core/services" "github.com/polygonid/sh-id-platform/internal/log" "github.com/polygonid/sh-id-platform/internal/sqltools" - "github.com/polygonid/sh-id-platform/pkg/schema" ) // GetConnections returns the list of connections of a determined issuer @@ -163,13 +162,13 @@ func (s *Server) GetConnection(ctx context.Context, request GetConnectionRequest return GetConnection500JSONResponse{N500JSONResponse{"There was an error retrieving the connection"}}, nil } - w3credentials, err := schema.FromClaimsModelToW3CCredential(credentials) + resp, err := connectionResponse(conn, credentials) if err != nil { - log.Debug(ctx, "get connection internal server error converting credentials to w3c", "err", err, "req", request) + log.Error(ctx, "get connection internal server error converting credentials to w3c", "err", err) return GetConnection500JSONResponse{N500JSONResponse{"There was an error parsing the credential of the given connection"}}, nil } - return GetConnection200JSONResponse(connectionResponse(conn, w3credentials, credentials)), nil + return GetConnection200JSONResponse(resp), nil } // DeleteConnectionCredentials deletes all the credentials of the given connection diff --git a/internal/api/credentials.go b/internal/api/credentials.go index 78c68cbff..e52ed5a79 100644 --- a/internal/api/credentials.go +++ b/internal/api/credentials.go @@ -240,7 +240,7 @@ func (s *Server) GetCredentials(ctx context.Context, request GetCredentialsReque log.Error(ctx, "creating credentials response", "err", err, "req", request) return GetCredentials500JSONResponse{N500JSONResponse{"Invalid claim format"}}, nil } - response[i] = credentialResponse(w3c, credential) + response[i] = toGetCredential200Response(w3c, credential) } resp := GetCredentials200JSONResponse{ @@ -363,48 +363,13 @@ func toVerifiableDisplayMethod(s *DisplayMethod) *verifiable.DisplayMethod { } } -func toGetCredential200Response(w3cCredential *verifiable.W3CCredential, cred *domain.Claim) GetCredentialResponse { - var claimExpiration, claimIssuanceDate *TimeUTC - if w3cCredential.Expiration != nil { - claimExpiration = common.ToPointer(TimeUTC(*w3cCredential.Expiration)) - } - if w3cCredential.IssuanceDate != nil { - claimIssuanceDate = common.ToPointer(TimeUTC(*w3cCredential.IssuanceDate)) - } - - var refreshService *RefreshService - if w3cCredential.RefreshService != nil { - refreshService = &RefreshService{ - Id: w3cCredential.RefreshService.ID, - Type: RefreshServiceType(w3cCredential.RefreshService.Type), - } - } - - var displayMethod *DisplayMethod - if w3cCredential.DisplayMethod != nil { - displayMethod = &DisplayMethod{ - Id: w3cCredential.DisplayMethod.ID, - Type: DisplayMethodType(w3cCredential.DisplayMethod.Type), - } - } - - return GetCredentialResponse{ - Context: w3cCredential.Context, - CredentialSchema: CredentialSchema{ - w3cCredential.CredentialSchema.ID, - w3cCredential.CredentialSchema.Type, - }, - CredentialStatus: w3cCredential.CredentialStatus, - CredentialSubject: w3cCredential.CredentialSubject, - ExpirationDate: claimExpiration, - Id: w3cCredential.ID, - IssuanceDate: claimIssuanceDate, - Issuer: w3cCredential.Issuer, - Proof: w3cCredential.Proof, - ProofTypes: getProofs(cred), - Type: w3cCredential.Type, - RefreshService: refreshService, - DisplayMethod: displayMethod, +func toGetCredential200Response(w3cCredential *verifiable.W3CCredential, cred *domain.Claim) Credential { + return Credential{ + Vc: *w3cCredential, + Id: cred.ID.String(), + Revoked: cred.Revoked, + SchemaHash: cred.SchemaHash, + ProofTypes: getProofs(cred), } } diff --git a/internal/api/credentials_test.go b/internal/api/credentials_test.go index 4043dcd55..4bad0fab3 100644 --- a/internal/api/credentials_test.go +++ b/internal/api/credentials_test.go @@ -829,31 +829,33 @@ func TestServer_GetCredential(t *testing.T) { expected: expected{ httpCode: http.StatusOK, response: GetCredential200JSONResponse{ - Context: []string{"https://www.w3.org/2018/credentials/v1", "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/iden3credential-v2.json-ld", "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld"}, - CredentialSchema: CredentialSchema{ - "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v3.json", - "JsonSchemaValidator2018", - }, - CredentialStatus: verifiable.CredentialStatus{ - ID: fmt.Sprintf("http://localhost/v2/%s/credentials/revocation/status/%d", idStr, claim.RevNonce), - Type: "SparseMerkleTreeProof", - RevocationNonce: uint64(claim.RevNonce), - }, - CredentialSubject: map[string]interface{}{ - "id": "did:polygonid:polygon:mumbai:2qE1BZ7gcmEoP2KppvFPCZqyzyb5tK9T6Gec5HFANQ", - "birthday": float64(19960424), - "documentType": float64(2), - "type": "KYCAgeCredential", - }, - Id: fmt.Sprintf("http://localhost/api/v2/credentials/%s", claim.ID), - IssuanceDate: common.ToPointer(TimeUTC(time.Now())), - Issuer: idStr, - Type: []string{"VerifiableCredential", "KYCAgeCredential"}, - RefreshService: &RefreshService{ - Id: "https://refresh-service.xyz", - Type: RefreshServiceType(verifiable.Iden3RefreshService2023), - }, ProofTypes: []string{"Iden3SparseMerkleTreeProof"}, + Vc: verifiable.W3CCredential{ + Context: []string{"https://www.w3.org/2018/credentials/v1", "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/iden3credential-v2.json-ld", "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld"}, + CredentialSchema: verifiable.CredentialSchema{ + ID: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v3.json", + Type: "JsonSchemaValidator2018", + }, + CredentialStatus: verifiable.CredentialStatus{ + ID: fmt.Sprintf("http://localhost/v2/%s/credentials/revocation/status/%d", idStr, claim.RevNonce), + Type: "SparseMerkleTreeProof", + RevocationNonce: uint64(claim.RevNonce), + }, + CredentialSubject: map[string]interface{}{ + "id": "did:polygonid:polygon:mumbai:2qE1BZ7gcmEoP2KppvFPCZqyzyb5tK9T6Gec5HFANQ", + "birthday": float64(19960424), + "documentType": float64(2), + "type": "KYCAgeCredential", + }, + ID: fmt.Sprintf("http://localhost/api/v2/credentials/%s", claim.ID), + IssuanceDate: common.ToPointer(time.Now()), + Issuer: idStr, + Type: []string{"VerifiableCredential", "KYCAgeCredential"}, + RefreshService: &verifiable.RefreshService{ + ID: "https://refresh-service.xyz", + Type: verifiable.Iden3RefreshService2023, + }, + }, }, }, }, @@ -871,9 +873,9 @@ func TestServer_GetCredential(t *testing.T) { switch v := tc.expected.response.(type) { case GetCredential200JSONResponse: - var response GetCredentialResponse + var response Credential assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) - validateCredential(t, response, GetCredentialResponse(v)) + validateCredential(t, response, Credential(v)) case GetCredential400JSONResponse: var response GetCredential404JSONResponse @@ -1540,7 +1542,7 @@ func TestServer_GetRevocationStatusV2(t *testing.T) { } } -func validateCredential(t *testing.T, resp, tc GetCredentialResponse) { +func validateCredential(t *testing.T, resp, tc Credential) { t.Helper() var responseCredentialStatus verifiable.CredentialStatus @@ -1559,34 +1561,34 @@ func validateCredential(t *testing.T, resp, tc GetCredentialResponse) { Y string `json:"y"` } - assert.Equal(t, resp.Id, tc.Id) - assert.Equal(t, len(resp.Context), len(tc.Context)) - assert.EqualValues(t, resp.Context, tc.Context) - assert.EqualValues(t, resp.CredentialSchema, tc.CredentialSchema) - assert.InDelta(t, time.Time(*resp.IssuanceDate).UnixMilli(), time.Time(*tc.IssuanceDate).UnixMilli(), 1000) - assert.Equal(t, resp.Type, tc.Type) - assert.Equal(t, resp.ExpirationDate, tc.ExpirationDate) - assert.Equal(t, resp.Issuer, tc.Issuer) - assert.Equal(t, resp.RefreshService, tc.RefreshService) - credentialSubjectType, ok := tc.CredentialSubject["type"] + assert.Equal(t, resp.Vc.ID, tc.Vc.ID) + assert.Equal(t, len(resp.Vc.Context), len(tc.Vc.Context)) + assert.EqualValues(t, resp.Vc.Context, tc.Vc.Context) + assert.EqualValues(t, resp.Vc.CredentialSchema, tc.Vc.CredentialSchema) + assert.InDelta(t, resp.Vc.IssuanceDate.UnixMilli(), tc.Vc.IssuanceDate.UnixMilli(), 1000) + assert.Equal(t, resp.Vc.Type, tc.Vc.Type) + assert.Equal(t, resp.Vc.Expiration, tc.Vc.Expiration) + assert.Equal(t, resp.Vc.Issuer, tc.Vc.Issuer) + assert.Equal(t, resp.Vc.RefreshService, tc.Vc.RefreshService) + credentialSubjectType, ok := tc.Vc.CredentialSubject["type"] require.True(t, ok) assert.Contains(t, credentialSubjectTypes, credentialSubjectType) if credentialSubjectType == "AuthBJJCredential" { var responseCredentialSubject, tcCredentialSubject credentialBJJSubject - assert.NoError(t, mapstructure.Decode(resp.CredentialSubject, &responseCredentialSubject)) - assert.NoError(t, mapstructure.Decode(tc.CredentialSubject, &tcCredentialSubject)) + assert.NoError(t, mapstructure.Decode(resp.Vc.CredentialSubject, &responseCredentialSubject)) + assert.NoError(t, mapstructure.Decode(tc.Vc.CredentialSubject, &tcCredentialSubject)) assert.EqualValues(t, responseCredentialSubject, tcCredentialSubject) } else { var responseCredentialSubject, tcCredentialSubject credentialKYCSubject - assert.NoError(t, mapstructure.Decode(resp.CredentialSubject, &responseCredentialSubject)) - assert.NoError(t, mapstructure.Decode(tc.CredentialSubject, &tcCredentialSubject)) + assert.NoError(t, mapstructure.Decode(resp.Vc.CredentialSubject, &responseCredentialSubject)) + assert.NoError(t, mapstructure.Decode(tc.Vc.CredentialSubject, &tcCredentialSubject)) assert.EqualValues(t, responseCredentialSubject, tcCredentialSubject) } assert.Equal(t, resp.ProofTypes, tc.ProofTypes) - assert.NoError(t, mapstructure.Decode(resp.CredentialStatus, &responseCredentialStatus)) + assert.NoError(t, mapstructure.Decode(resp.Vc.CredentialStatus, &responseCredentialStatus)) responseCredentialStatus.ID = strings.Replace(responseCredentialStatus.ID, "%3A", ":", -1) - credentialStatusTC, ok := tc.CredentialStatus.(verifiable.CredentialStatus) + credentialStatusTC, ok := tc.Vc.CredentialStatus.(verifiable.CredentialStatus) require.True(t, ok) assert.EqualValues(t, responseCredentialStatus, credentialStatusTC) } diff --git a/internal/api/responses.go b/internal/api/responses.go index c4ec3fad8..18584f0ec 100644 --- a/internal/api/responses.go +++ b/internal/api/responses.go @@ -2,8 +2,6 @@ package api import ( "net/http" - "strings" - "time" "github.com/iden3/go-schema-processor/v2/verifiable" @@ -132,61 +130,6 @@ func getProofs(credential *domain.Claim) []string { return proofs } -func credentialResponse(w3c *verifiable.W3CCredential, credential *domain.Claim) Credential { - var expiresAt *TimeUTC - expired := false - if w3c.Expiration != nil { - if time.Now().UTC().After(w3c.Expiration.UTC()) { - expired = true - } - expiresAt = common.ToPointer(TimeUTC(*w3c.Expiration)) - } - - proofs := getProofs(credential) - - var refreshService *RefreshService - if w3c.RefreshService != nil { - refreshService = &RefreshService{ - Id: w3c.RefreshService.ID, - Type: RefreshServiceType(w3c.RefreshService.Type), - } - } - - var displayService *DisplayMethod - if w3c.DisplayMethod != nil { - displayService = &DisplayMethod{ - Id: w3c.DisplayMethod.ID, - Type: DisplayMethodType(w3c.DisplayMethod.Type), - } - } - - return Credential{ - CredentialSubject: w3c.CredentialSubject, - CreatedAt: TimeUTC(*w3c.IssuanceDate), - Expired: expired, - ExpiresAt: expiresAt, - Id: credential.ID, - ProofTypes: proofs, - RevNonce: uint64(credential.RevNonce), - Revoked: credential.Revoked, - SchemaHash: credential.SchemaHash, - SchemaType: shortType(credential.SchemaType), - SchemaUrl: credential.SchemaURL, - UserID: credential.OtherIdentifier, - RefreshService: refreshService, - DisplayMethod: displayService, - } -} - -func shortType(id string) string { - parts := strings.Split(id, "#") - l := len(parts) - if l == 0 { - return "" - } - return parts[l-1] -} - func schemaResponse(s *domain.Schema) Schema { hash, _ := s.Hash.MarshalText() return Schema{ @@ -210,39 +153,33 @@ func schemaCollectionResponse(schemas []domain.Schema) []Schema { return res } -func connectionResponse(conn *domain.Connection, w3cs []*verifiable.W3CCredential, credentials []*domain.Claim) GetConnectionResponse { - credResp := make([]Credential, len(w3cs)) - if w3cs != nil { - for i := range credentials { - credResp[i] = credentialResponse(w3cs[i], credentials[i]) +func connectionResponse(conn *domain.Connection, credentials []*domain.Claim) (GetConnectionResponse, error) { + credResp := make([]Credential, len(credentials)) + for i := range credentials { + w3Cred, err := schema.FromClaimModelToW3CCredential(*credentials[i]) + if err != nil { + return GetConnectionResponse{}, err } + credResp[i] = toGetCredential200Response(w3Cred, credentials[i]) } - return GetConnectionResponse{ CreatedAt: TimeUTC(conn.CreatedAt), Id: conn.ID.String(), UserID: conn.UserDID.String(), IssuerID: conn.IssuerDID.String(), Credentials: credResp, - } + }, nil } func connectionsResponse(conns []domain.Connection) (GetConnectionsResponse, error) { resp := make([]GetConnectionResponse, 0) - var err error for _, conn := range conns { - var w3creds []*verifiable.W3CCredential - var connCreds domain.Credentials - if conn.Credentials != nil { - connCreds = *conn.Credentials - w3creds, err = schema.FromClaimsModelToW3CCredential(connCreds) - if err != nil { - return nil, err - } + connResp, err := connectionResponse(&conn, *conn.Credentials) + if err != nil { + return GetConnectionsResponse{}, err } - resp = append(resp, connectionResponse(&conn, w3creds, connCreds)) + resp = append(resp, connResp) } - return resp, nil } diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 7a3abca30..866d6ff6b 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -77,21 +77,6 @@ func FromClaimModelToW3CCredential(claim domain.Claim) (*verifiable.W3CCredentia return &cred, nil } -// FromClaimsModelToW3CCredential JSON-LD response base on claim -func FromClaimsModelToW3CCredential(credentials domain.Credentials) ([]*verifiable.W3CCredential, error) { - w3Credentials := make([]*verifiable.W3CCredential, len(credentials)) - for i := range credentials { - w3Cred, err := FromClaimModelToW3CCredential(*credentials[i]) - if err != nil { - return nil, err - } - - w3Credentials[i] = w3Cred - } - - return w3Credentials, nil -} - // Process data and schema and create Index and Value slots func Process(ctx context.Context, loader loader.DocumentLoader, schemaURL string, credential verifiable.W3CCredential, options *processor.CoreClaimOptions) (*core.Claim, error) { var parser processor.Parser From 94a4e82446035e5b8e79e1400350b293d0105563 Mon Sep 17 00:00:00 2001 From: Martin Saporiti Date: Tue, 1 Oct 2024 17:41:27 -0300 Subject: [PATCH 2/7] chore: add schema cache to w3c doc loader --- cmd/notifications/main.go | 3 +- cmd/pending_publisher/main.go | 2 +- cmd/platform/main.go | 2 +- internal/api/main_test.go | 2 +- internal/config/config.go | 7 +--- internal/core/services/main_test.go | 2 +- internal/jsonschema/schema_test.go | 2 +- internal/loader/loader.go | 4 +-- internal/loader/w3CDocumentLoader_test.go | 2 +- internal/loader/w3c_loader.go | 39 +++++++++++++++++++++-- 10 files changed, 47 insertions(+), 18 deletions(-) diff --git a/cmd/notifications/main.go b/cmd/notifications/main.go index 06739a863..14741a3d6 100644 --- a/cmd/notifications/main.go +++ b/cmd/notifications/main.go @@ -144,8 +144,7 @@ func newCredentialsService(ctx context.Context, cfg *config.Configuration, stora rhsFactory := reverse_hash.NewFactory(*networkResolver, reverse_hash.DefaultRHSTimeOut) revocationStatusResolver := revocation_status.NewRevocationStatusResolver(*networkResolver) - // TODO: Cache only if cfg.APIUI.SchemaCache == true - schemaLoader := loader.NewDocumentLoader(cfg.IPFS.GatewayURL) + schemaLoader := loader.NewDocumentLoader(cfg.IPFS.GatewayURL, cfg.SchemaCache) mtService := services.NewIdentityMerkleTrees(mtRepository) qrService := services.NewQrStoreService(cachex) diff --git a/cmd/pending_publisher/main.go b/cmd/pending_publisher/main.go index bcf532951..95aa047b9 100644 --- a/cmd/pending_publisher/main.go +++ b/cmd/pending_publisher/main.go @@ -71,7 +71,7 @@ func main() { }(storage) // TODO: Cache only if cfg.APIUI.SchemaCache == true - schemaLoader := loader.NewDocumentLoader(cfg.IPFS.GatewayURL) + schemaLoader := loader.NewDocumentLoader(cfg.IPFS.GatewayURL, cfg.SchemaCache) vaultCfg := providers.Config{ UserPassAuthEnabled: cfg.KeyStore.VaultUserPassAuthEnabled, diff --git a/cmd/platform/main.go b/cmd/platform/main.go index 21eaab682..3801ccd0e 100644 --- a/cmd/platform/main.go +++ b/cmd/platform/main.go @@ -71,7 +71,7 @@ func main() { } // TODO: Cache only if cfg.APIUI.SchemaCache == true - schemaLoader := loader.NewDocumentLoader(cfg.IPFS.GatewayURL) + schemaLoader := loader.NewDocumentLoader(cfg.IPFS.GatewayURL, cfg.SchemaCache) vaultCfg := providers.Config{ UserPassAuthEnabled: cfg.KeyStore.VaultUserPassAuthEnabled, diff --git a/internal/api/main_test.go b/internal/api/main_test.go index 3863c6753..422beb06a 100644 --- a/internal/api/main_test.go +++ b/internal/api/main_test.go @@ -121,7 +121,7 @@ func TestMain(m *testing.M) { cfg.ServerUrl = "https://testing.env" cfg.Ethereum = cfgForTesting.Ethereum cfg.UniversalLinks = config.UniversalLinks{BaseUrl: "https://testing.env"} - schemaLoader = loader.NewDocumentLoader(ipfsGatewayURL) + schemaLoader = loader.NewDocumentLoader(ipfsGatewayURL, false) m.Run() } diff --git a/internal/config/config.go b/internal/config/config.go index e450ed49f..2e8943e66 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -44,7 +44,7 @@ type Configuration struct { ServerUrl string `env:"ISSUER_SERVER_URL" envDefault:"http://localhost"` ServerPort int `env:"ISSUER_SERVER_PORT" envDefault:"3001"` PublishingKeyPath string `env:"ISSUER_PUBLISH_KEY_PATH" envDefault:"pbkey"` - SchemaCache *bool `env:"ISSUER_SCHEMA_CACHE" envDefault:"false"` + SchemaCache bool `env:"ISSUER_SCHEMA_CACHE" envDefault:"false"` OnChainCheckStatusFrequency time.Duration `env:"ISSUER_ONCHAIN_CHECK_STATUS_FREQUENCY"` NetworkResolverPath string `env:"ISSUER_RESOLVER_PATH"` NetworkResolverFile *string `env:"ISSUER_RESOLVER_FILE"` @@ -310,11 +310,6 @@ func checkEnvVars(ctx context.Context, cfg *Configuration) error { return errors.New("ISSUER_CACHE_URL value is missing") } - if cfg.SchemaCache == nil { - log.Info(ctx, "ISSUER_SCHEMA_CACHE is missing and the server set up it as false") - cfg.SchemaCache = common.ToPointer(false) - } - if cfg.MediaTypeManager.Enabled == nil { log.Info(ctx, "ISSUER_MEDIA_TYPE_MANAGER_ENABLED is missing and the server set up it as true") cfg.MediaTypeManager.Enabled = common.ToPointer(true) diff --git a/internal/core/services/main_test.go b/internal/core/services/main_test.go index 59e3a63f0..db59c56a6 100644 --- a/internal/core/services/main_test.go +++ b/internal/core/services/main_test.go @@ -103,7 +103,7 @@ func TestMain(m *testing.M) { cachex = cache.NewMemoryCache() - docLoader = loader.NewDocumentLoader(ipfsGatewayURL) + docLoader = loader.NewDocumentLoader(ipfsGatewayURL, false) cfg.Ethereum = cfgForTesting.Ethereum cfg.ServerUrl = "http://localhost:3001" m.Run() diff --git a/internal/jsonschema/schema_test.go b/internal/jsonschema/schema_test.go index 68d4e1caa..f2c36278f 100644 --- a/internal/jsonschema/schema_test.go +++ b/internal/jsonschema/schema_test.go @@ -12,7 +12,7 @@ import ( func TestValidateCredentialSubject(t *testing.T) { const ipfsGatewayURL = "http://127.0.0.1:8080" ctx := context.Background() - ld := loader.NewDocumentLoader(ipfsGatewayURL) + ld := loader.NewDocumentLoader(ipfsGatewayURL, false) type config struct { name string diff --git a/internal/loader/loader.go b/internal/loader/loader.go index 164450601..b0bfa7071 100644 --- a/internal/loader/loader.go +++ b/internal/loader/loader.go @@ -17,6 +17,6 @@ type Loader interface { type Factory func(url string) Loader // NewDocumentLoader returns a new ld.DocumentLoader that will use http ipfs gateway to download documents -func NewDocumentLoader(ipfsGateway string) ld.DocumentLoader { - return NewW3CDocumentLoader(nil, ipfsGateway) +func NewDocumentLoader(ipfsGateway string, withCache bool) ld.DocumentLoader { + return NewW3CDocumentLoader(nil, ipfsGateway, withCache) } diff --git a/internal/loader/w3CDocumentLoader_test.go b/internal/loader/w3CDocumentLoader_test.go index c6b9a1457..c788998d9 100644 --- a/internal/loader/w3CDocumentLoader_test.go +++ b/internal/loader/w3CDocumentLoader_test.go @@ -7,7 +7,7 @@ import ( ) func TestW3CDocumentLoader_LoadDocument(t *testing.T) { - w3cLoader := NewW3CDocumentLoader(nil, "https://ipfs.io") + w3cLoader := NewW3CDocumentLoader(nil, "https://ipfs.io", false) doc, err := w3cLoader.LoadDocument(W3CCredential2018ContextURL) require.NoError(t, err) diff --git a/internal/loader/w3c_loader.go b/internal/loader/w3c_loader.go index cf10ba3bc..cb03b4d41 100644 --- a/internal/loader/w3c_loader.go +++ b/internal/loader/w3c_loader.go @@ -2,21 +2,56 @@ package loader import ( "strings" + "sync" + "time" "github.com/iden3/go-schema-processor/v2/loaders" shell "github.com/ipfs/go-ipfs-api" "github.com/piprate/json-gold/ld" ) +const defaultSchemaCacheDuration = 30 * time.Minute + // W3CDocumentLoader is a document loader for w3c documents type W3CDocumentLoader struct { l ld.DocumentLoader } +type w3CDocumentLoaderLocalCache struct { + mutex sync.Mutex + data map[string]*ld.RemoteDocument +} + +func newLocalCache() *w3CDocumentLoaderLocalCache { + return &w3CDocumentLoaderLocalCache{ + mutex: sync.Mutex{}, + data: make(map[string]*ld.RemoteDocument), + } +} + +func (c *w3CDocumentLoaderLocalCache) Get(key string) (doc *ld.RemoteDocument, expireTime time.Time, err error) { + doc, ok := c.data[key] + if !ok { + return nil, time.Time{}, nil + } + return doc, time.Now().Add(defaultSchemaCacheDuration), nil +} + +func (c *w3CDocumentLoaderLocalCache) Set(key string, doc *ld.RemoteDocument, expireTime time.Time) error { + c.mutex.Lock() + c.data[key] = doc + c.mutex.Unlock() + return nil +} + // NewW3CDocumentLoader creates a new document loader with a predefined http schema -func NewW3CDocumentLoader(_ *shell.Shell, ipfsGW string) ld.DocumentLoader { +func NewW3CDocumentLoader(_ *shell.Shell, ipfsGW string, withCache bool) ld.DocumentLoader { + if !withCache { + return loaders.NewDocumentLoader(nil, ipfsGW) + } + option := loaders.WithCacheEngine(newLocalCache()) return &W3CDocumentLoader{ - l: loaders.NewDocumentLoader(nil, ipfsGW), + l: loaders.NewDocumentLoader(nil, ipfsGW, option), } } From f07b6436896abc64ae2d0d53746fef64852c7ebf Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 2 Oct 2024 09:26:36 -0300 Subject: [PATCH 3/7] fx: mutex --- internal/loader/w3c_loader.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/loader/w3c_loader.go b/internal/loader/w3c_loader.go index cb03b4d41..ac6a73e74 100644 --- a/internal/loader/w3c_loader.go +++ b/internal/loader/w3c_loader.go @@ -18,13 +18,13 @@ type W3CDocumentLoader struct { } type w3CDocumentLoaderLocalCache struct { - mutex sync.Mutex + mutex sync.RWMutex data map[string]*ld.RemoteDocument } func newLocalCache() *w3CDocumentLoaderLocalCache { return &w3CDocumentLoaderLocalCache{ - mutex: sync.Mutex{}, + mutex: sync.RWMutex{}, data: make(map[string]*ld.RemoteDocument), } } @@ -39,8 +39,8 @@ func (c *w3CDocumentLoaderLocalCache) Get(key string) (doc *ld.RemoteDocument, e func (c *w3CDocumentLoaderLocalCache) Set(key string, doc *ld.RemoteDocument, expireTime time.Time) error { c.mutex.Lock() + defer c.mutex.Unlock() c.data[key] = doc - c.mutex.Unlock() return nil } From 5344f71b1dddd745eb42c5a268ebfdbddd947ba3 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 2 Oct 2024 09:36:26 -0300 Subject: [PATCH 4/7] fx: mutex in read --- internal/loader/w3c_loader.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/loader/w3c_loader.go b/internal/loader/w3c_loader.go index ac6a73e74..e9649c6b5 100644 --- a/internal/loader/w3c_loader.go +++ b/internal/loader/w3c_loader.go @@ -30,6 +30,8 @@ func newLocalCache() *w3CDocumentLoaderLocalCache { } func (c *w3CDocumentLoaderLocalCache) Get(key string) (doc *ld.RemoteDocument, expireTime time.Time, err error) { + c.mutex.RLock() + defer c.mutex.Unlock() doc, ok := c.data[key] if !ok { return nil, time.Time{}, nil From 050a5306bf6d5d78301527b9c306fac2e3c269a9 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 2 Oct 2024 09:42:42 -0300 Subject: [PATCH 5/7] fx: unlock --- internal/loader/w3c_loader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/loader/w3c_loader.go b/internal/loader/w3c_loader.go index e9649c6b5..75bcd6455 100644 --- a/internal/loader/w3c_loader.go +++ b/internal/loader/w3c_loader.go @@ -31,7 +31,7 @@ func newLocalCache() *w3CDocumentLoaderLocalCache { func (c *w3CDocumentLoaderLocalCache) Get(key string) (doc *ld.RemoteDocument, expireTime time.Time, err error) { c.mutex.RLock() - defer c.mutex.Unlock() + defer c.mutex.RUnlock() doc, ok := c.data[key] if !ok { return nil, time.Time{}, nil From 6f16e0bb8d28d90da50f8db7ea5bb6e6da238c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Gim=C3=A9nez?= Date: Wed, 2 Oct 2024 15:48:31 +0200 Subject: [PATCH 6/7] feat: Identity display name cannot be duplicated. (#803) --- api/api.yaml | 2 ++ internal/api/api.gen.go | 9 +++++ internal/api/identity.go | 8 ++++- internal/api/identity_test.go | 36 ++++++++++++++++++- internal/core/services/identity.go | 16 +++++++-- ..._identities_display_name_set_to_unique.sql | 22 ++++++++++++ internal/repositories/identity.go | 11 ++++++ internal/repositories/schema.go | 8 ++--- internal/repositories/schema_test.go | 2 +- 9 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 internal/db/schema/migrations/202410021258180_identities_display_name_set_to_unique.sql diff --git a/api/api.yaml b/api/api.yaml index f6cda2c13..aaee64168 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -194,6 +194,8 @@ paths: $ref: '#/components/responses/401' '403': $ref: '#/components/responses/403' + '409': + $ref: '#/components/responses/409' '500': $ref: '#/components/responses/500' get: diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 2e6ab8aa6..c672ebda2 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -3199,6 +3199,15 @@ func (response CreateIdentity403JSONResponse) VisitCreateIdentityResponse(w http return json.NewEncoder(w).Encode(response) } +type CreateIdentity409JSONResponse struct{ N409JSONResponse } + +func (response CreateIdentity409JSONResponse) VisitCreateIdentityResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(409) + + return json.NewEncoder(w).Encode(response) +} + type CreateIdentity500JSONResponse struct{ N500JSONResponse } func (response CreateIdentity500JSONResponse) VisitCreateIdentityResponse(w http.ResponseWriter) error { diff --git a/internal/api/identity.go b/internal/api/identity.go index 523b6d2c1..b143c92c8 100644 --- a/internal/api/identity.go +++ b/internal/api/identity.go @@ -78,7 +78,6 @@ func (s *Server) CreateIdentity(ctx context.Context, request CreateIdentityReque }, }, nil } - if errors.Is(err, kms.ErrPermissionDenied) { var message string if s.cfg.KeyStore.VaultUserPassAuthEnabled { @@ -94,6 +93,13 @@ func (s *Server) CreateIdentity(ctx context.Context, request CreateIdentityReque }, }, nil } + if errors.Is(err, services.ErrIdentityDisplayNameDuplicated) { + return CreateIdentity409JSONResponse{ + N409JSONResponse{ + Message: fmt.Sprintf("display name field already exists: <%s>", *request.Body.DisplayName), + }, + }, nil + } var customErr *services.PublishingStateError if errors.As(err, &customErr) { diff --git a/internal/api/identity_test.go b/internal/api/identity_test.go index 0b791b76b..3b8274534 100644 --- a/internal/api/identity_test.go +++ b/internal/api/identity_test.go @@ -95,7 +95,7 @@ func TestServer_CreateIdentity(t *testing.T) { Method string `json:"method"` Network string `json:"network"` Type CreateIdentityRequestDidMetadataType `json:"type"` - }{Blockchain: blockchain, Method: method, Network: network, Type: BJJ}, DisplayName: common.ToPointer("my display name"), + }{Blockchain: blockchain, Method: method, Network: network, Type: BJJ}, DisplayName: common.ToPointer("blockchain display name"), CredentialStatusType: &authBJJCredentialStatus, }, expected: expected{ @@ -236,6 +236,40 @@ func TestServer_CreateIdentity(t *testing.T) { } }) } + t.Run("Duplicated display name", func(t *testing.T) { + bodyRequest := CreateIdentityRequest{ + DidMetadata: struct { + Blockchain string `json:"blockchain"` + Method string `json:"method"` + Network string `json:"network"` + Type CreateIdentityRequestDidMetadataType `json:"type"` + }{ + Blockchain: blockchain, + Method: method, + Network: network, + Type: BJJ, + }, + DisplayName: common.ToPointer("Very common display name"), + } + // First request + req, err := http.NewRequest("POST", "/v2/identities", tests.JSONBody(t, bodyRequest)) + req.SetBasicAuth(authOk()) + require.NoError(t, err) + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + require.Equal(t, http.StatusCreated, rr.Code) // First time we expect 201 + + // Second request + req, err = http.NewRequest("POST", "/v2/identities", tests.JSONBody(t, bodyRequest)) + req.SetBasicAuth(authOk()) + require.NoError(t, err) + rr = httptest.NewRecorder() + handler.ServeHTTP(rr, req) + require.Equal(t, http.StatusConflict, rr.Code) // Second time we expect a conflict 409 + var response CreateIdentity409JSONResponse + assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.Equal(t, "display name field already exists: ", response.Message) + }) } func TestServer_GetIdentities(t *testing.T) { diff --git a/internal/core/services/identity.go b/internal/core/services/identity.go index 52765003e..11f23d6d1 100644 --- a/internal/core/services/identity.go +++ b/internal/core/services/identity.go @@ -34,6 +34,7 @@ import ( "github.com/polygonid/sh-id-platform/internal/kms" "github.com/polygonid/sh-id-platform/internal/log" "github.com/polygonid/sh-id-platform/internal/qrlink" + "github.com/polygonid/sh-id-platform/internal/repositories" "github.com/polygonid/sh-id-platform/internal/urn" "github.com/polygonid/sh-id-platform/pkg/credentials/revocation_status" "github.com/polygonid/sh-id-platform/pkg/credentials/signature/circuit/signer" @@ -53,12 +54,17 @@ const ( // ErrWrongDIDMetada - represents an error in the identity metadata var ( - // ErrWrongDIDMetada - represents an error in the identity metadata - ErrWrongDIDMetada = errors.New("wrong DID Metadata") // ErrAssigningMTPProof - represents an error in the identity metadata ErrAssigningMTPProof = errors.New("error assigning the MTP Proof from Auth Claim. If this identity has keyType=ETH you must to publish the state first") + + // ErrIdentityDisplayNameDuplicated - returned when trying to create an identity with a duplicated display name + ErrIdentityDisplayNameDuplicated = errors.New("duplicated identity display name") + // ErrNoClaimsFoundToProcess - means that there are no claims to process ErrNoClaimsFoundToProcess = errors.New("no MTP or revoked claims found to process") + + // ErrWrongDIDMetada - represents an error in the identity metadata + ErrWrongDIDMetada = errors.New("wrong DID Metadata") ) type identity struct { @@ -676,6 +682,9 @@ func (i *identity) createEthIdentity(ctx context.Context, tx db.Querier, hostURL identity.DisplayName = didOptions.DisplayName if err = i.identityRepository.Save(ctx, tx, identity); err != nil { + if errors.Is(err, repositories.ErrDisplayNameDuplicated) { + return nil, nil, ErrIdentityDisplayNameDuplicated + } log.Error(ctx, "saving identity", "err", err) return nil, nil, errors.Join(err, errors.New("can't save identity")) } @@ -784,6 +793,9 @@ func (i *identity) createIdentity(ctx context.Context, tx db.Querier, hostURL st } if err = i.identityRepository.Save(ctx, tx, identity); err != nil { + if errors.Is(err, repositories.ErrDisplayNameDuplicated) { + return nil, nil, ErrIdentityDisplayNameDuplicated + } return nil, nil, fmt.Errorf("can't save identity: %w", err) } diff --git a/internal/db/schema/migrations/202410021258180_identities_display_name_set_to_unique.sql b/internal/db/schema/migrations/202410021258180_identities_display_name_set_to_unique.sql new file mode 100644 index 000000000..14985e857 --- /dev/null +++ b/internal/db/schema/migrations/202410021258180_identities_display_name_set_to_unique.sql @@ -0,0 +1,22 @@ +-- +goose Up +-- +goose StatementBegin +WITH cte AS (SELECT identifier, + display_name, + ROW_NUMBER() OVER (PARTITION BY display_name ORDER BY identifier) AS rn + FROM identities + WHERE display_name IS NOT NULL) +UPDATE identities +SET display_name = identities.display_name || '_' || rn +FROM cte +WHERE identities.identifier = cte.identifier + AND cte.rn > 1; + +ALTER TABLE identities + ADD CONSTRAINT identities_display_name_unique UNIQUE (display_name); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE identities + DROP CONSTRAINT identities_display_name_unique; +-- +goose StatementEnd diff --git a/internal/repositories/identity.go b/internal/repositories/identity.go index 399ea6201..e139add6b 100644 --- a/internal/repositories/identity.go +++ b/internal/repositories/identity.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/iden3/go-iden3-core/v2/w3c" + "github.com/jackc/pgconn" "github.com/polygonid/sh-id-platform/internal/core/domain" "github.com/polygonid/sh-id-platform/internal/core/ports" @@ -16,6 +17,9 @@ import ( // ErrIdentityNotFound - identity not found error var ErrIdentityNotFound = errors.New("identity not found") +// ErrDisplayNameDuplicated - display name already exists error +var ErrDisplayNameDuplicated = errors.New("display name already exists") + type identity struct{} // NewIdentity TODO @@ -26,6 +30,13 @@ func NewIdentity() ports.IndentityRepository { // Save - Create new identity func (i *identity) Save(ctx context.Context, conn db.Querier, identity *domain.Identity) error { _, err := conn.Exec(ctx, `INSERT INTO identities (identifier, address, keyType, display_name) VALUES ($1, $2, $3, $4)`, identity.Identifier, identity.Address, identity.KeyType, identity.DisplayName) + if err != nil { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) && pgErr.Code == duplicateViolationErrorCode { + return ErrDisplayNameDuplicated + } + return err + } return err } diff --git a/internal/repositories/schema.go b/internal/repositories/schema.go index ba7e719fd..264e38807 100644 --- a/internal/repositories/schema.go +++ b/internal/repositories/schema.go @@ -17,11 +17,9 @@ import ( "github.com/polygonid/sh-id-platform/internal/db" ) -const duplicatedEntryPGCode = "23505" - var ( ErrSchemaDoesNotExist = errors.New("schema does not exist") // ErrSchemaDoesNotExist schema does not exist - ErrSchemaDuplicated = errors.New("schema already imported") // ErrSchemaDuplicated schema duplicated + ErrDuplicated = errors.New("schema already imported") // ErrDuplicated schema duplicated ) type dbSchema struct { @@ -68,8 +66,8 @@ func (r *schema) Save(ctx context.Context, s *domain.Schema) error { s.Description) if err != nil { var pgErr *pgconn.PgError - if errors.As(err, &pgErr) && pgErr.Code == duplicatedEntryPGCode { - return ErrSchemaDuplicated + if errors.As(err, &pgErr) && pgErr.Code == duplicateViolationErrorCode { + return ErrDuplicated } return err diff --git a/internal/repositories/schema_test.go b/internal/repositories/schema_test.go index 4e1cbf735..742db2728 100644 --- a/internal/repositories/schema_test.go +++ b/internal/repositories/schema_test.go @@ -86,7 +86,7 @@ func TestCreateSchema(t *testing.T) { } require.NoError(t, store.Save(ctx, schema1)) - assert.ErrorIs(t, ErrSchemaDuplicated, store.Save(ctx, schema1), "cannot have duplicated schemas with the same version for the same issuer and type") + assert.ErrorIs(t, ErrDuplicated, store.Save(ctx, schema1), "cannot have duplicated schemas with the same version for the same issuer and type") schema2 := schema1 schema2.Version = uuid.NewString() From 290b08bddc8392b3702d2c2e5f5f8fee2dabeff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Gim=C3=A9nez?= Date: Tue, 8 Oct 2024 14:44:58 +0200 Subject: [PATCH 7/7] feat: Trim display name (#804) --- internal/api/identity.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/api/identity.go b/internal/api/identity.go index b143c92c8..e3a22cd4f 100644 --- a/internal/api/identity.go +++ b/internal/api/identity.go @@ -36,6 +36,10 @@ func (s *Server) CreateIdentity(ctx context.Context, request CreateIdentityReque }, nil } + if request.Body.DisplayName != nil { + request.Body.DisplayName = common.ToPointer(strings.TrimSpace(*request.Body.DisplayName)) + } + var credentialStatusType verifiable.CredentialStatusType if credentialStatusTypeRequest != nil && *credentialStatusTypeRequest != "" { allowedCredentialStatuses := []string{string(verifiable.Iden3commRevocationStatusV1), string(verifiable.Iden3ReverseSparseMerkleTreeProof), string(verifiable.Iden3OnchainSparseMerkleTreeProof2023)}