From 7b1df16c1873b64b1f1ffe3e4411791172f05ef6 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 30 Oct 2024 13:10:44 -0300 Subject: [PATCH 01/62] chore: add key endpoint --- api/api.yaml | 23 +++++ internal/api/api.gen.go | 107 ++++++++++++++++++++++++ internal/api/identity.go | 25 ++++++ internal/core/ports/identity_service.go | 1 + internal/core/services/identity.go | 83 +++++++++++++++++- internal/core/services/identity_test.go | 75 +++++++++++++++++ internal/repositories/claim.go | 5 +- internal/repositories/identity.go | 5 +- 8 files changed, 317 insertions(+), 7 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index eebcdd89f..055ba95c2 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -425,6 +425,29 @@ paths: '500': $ref: '#/components/responses/500' + /v2/identities/{identifier}/add-key: + post: + summary: Add a new key to the identity + operationId: AddKey + description: Endpoint to add a new key to the identity + security: + - basicAuth: [ ] + parameters: + - $ref: '#/components/parameters/pathIdentifier' + tags: + - Identity + responses: + '200': + description: Key added successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GenericMessage' + '400': + $ref: '#/components/responses/400' + '500': + $ref: '#/components/responses/500' + #connections: /v2/identities/{identifier}/connections/{id}: get: diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index afdba34dc..498f62376 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -812,6 +812,9 @@ type ServerInterface interface { // Update Identity // (PATCH /v2/identities/{identifier}) UpdateIdentity(w http.ResponseWriter, r *http.Request, identifier PathIdentifier) + // Add a new key to the identity + // (POST /v2/identities/{identifier}/add-key) + AddKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier) // Get Connections // (GET /v2/identities/{identifier}/connections) GetConnections(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetConnectionsParams) @@ -968,6 +971,12 @@ func (_ Unimplemented) UpdateIdentity(w http.ResponseWriter, r *http.Request, id w.WriteHeader(http.StatusNotImplemented) } +// Add a new key to the identity +// (POST /v2/identities/{identifier}/add-key) +func (_ Unimplemented) AddKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier) { + w.WriteHeader(http.StatusNotImplemented) +} + // Get Connections // (GET /v2/identities/{identifier}/connections) func (_ Unimplemented) GetConnections(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetConnectionsParams) { @@ -1400,6 +1409,37 @@ func (siw *ServerInterfaceWrapper) UpdateIdentity(w http.ResponseWriter, r *http handler.ServeHTTP(w, r) } +// AddKey operation middleware +func (siw *ServerInterfaceWrapper) AddKey(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "identifier" ------------- + var identifier PathIdentifier + + err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.AddKey(w, r, identifier) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // GetConnections operation middleware func (siw *ServerInterfaceWrapper) GetConnections(w http.ResponseWriter, r *http.Request) { @@ -2786,6 +2826,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Patch(options.BaseURL+"/v2/identities/{identifier}", wrapper.UpdateIdentity) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/v2/identities/{identifier}/add-key", wrapper.AddKey) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/identities/{identifier}/connections", wrapper.GetConnections) }) @@ -3315,6 +3358,41 @@ func (response UpdateIdentity500JSONResponse) VisitUpdateIdentityResponse(w http return json.NewEncoder(w).Encode(response) } +type AddKeyRequestObject struct { + Identifier PathIdentifier `json:"identifier"` +} + +type AddKeyResponseObject interface { + VisitAddKeyResponse(w http.ResponseWriter) error +} + +type AddKey200JSONResponse GenericMessage + +func (response AddKey200JSONResponse) VisitAddKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type AddKey400JSONResponse struct{ N400JSONResponse } + +func (response AddKey400JSONResponse) VisitAddKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type AddKey500JSONResponse struct{ N500JSONResponse } + +func (response AddKey500JSONResponse) VisitAddKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type GetConnectionsRequestObject struct { Identifier PathIdentifier `json:"identifier"` Params GetConnectionsParams @@ -4605,6 +4683,9 @@ type StrictServerInterface interface { // Update Identity // (PATCH /v2/identities/{identifier}) UpdateIdentity(ctx context.Context, request UpdateIdentityRequestObject) (UpdateIdentityResponseObject, error) + // Add a new key to the identity + // (POST /v2/identities/{identifier}/add-key) + AddKey(ctx context.Context, request AddKeyRequestObject) (AddKeyResponseObject, error) // Get Connections // (GET /v2/identities/{identifier}/connections) GetConnections(ctx context.Context, request GetConnectionsRequestObject) (GetConnectionsResponseObject, error) @@ -5015,6 +5096,32 @@ func (sh *strictHandler) UpdateIdentity(w http.ResponseWriter, r *http.Request, } } +// AddKey operation middleware +func (sh *strictHandler) AddKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier) { + var request AddKeyRequestObject + + request.Identifier = identifier + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.AddKey(ctx, request.(AddKeyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "AddKey") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(AddKeyResponseObject); ok { + if err := validResponse.VisitAddKeyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // GetConnections operation middleware func (sh *strictHandler) GetConnections(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetConnectionsParams) { var request GetConnectionsRequestObject diff --git a/internal/api/identity.go b/internal/api/identity.go index 42aa75ede..663c1c16f 100644 --- a/internal/api/identity.go +++ b/internal/api/identity.go @@ -310,3 +310,28 @@ func (s *Server) GetIdentityDetails(ctx context.Context, request GetIdentityDeta return response, nil } + +// AddKey is the controller to add key +func (s *Server) AddKey(ctx context.Context, request AddKeyRequestObject) (AddKeyResponseObject, error) { + did, err := w3c.ParseDID(request.Identifier) + if err != nil { + log.Error(ctx, "add key. Parsing did", "err", err) + return AddKey400JSONResponse{ + N400JSONResponse{ + Message: "invalid did", + }, + }, err + } + + err = s.identityService.AddKey(ctx, did) + if err != nil { + log.Error(ctx, "add key. Adding key", "err", err) + return AddKey400JSONResponse{ + N400JSONResponse{ + Message: err.Error(), + }, + }, err + } + + return AddKey200JSONResponse{Message: "Key added"}, nil +} diff --git a/internal/core/ports/identity_service.go b/internal/core/ports/identity_service.go index b3372baa2..e01feba2e 100644 --- a/internal/core/ports/identity_service.go +++ b/internal/core/ports/identity_service.go @@ -58,4 +58,5 @@ type IdentityService interface { GetFailedState(ctx context.Context, identifier w3c.DID) (*domain.IdentityState, error) PublishGenesisStateToRHS(ctx context.Context, did *w3c.DID) error UpdateIdentityDisplayName(ctx context.Context, did w3c.DID, displayName string) error + AddKey(ctx context.Context, did *w3c.DID) error } diff --git a/internal/core/services/identity.go b/internal/core/services/identity.go index 53219d811..d39d3bac2 100644 --- a/internal/core/services/identity.go +++ b/internal/core/services/identity.go @@ -719,7 +719,7 @@ func (i *identity) createEthIdentity(ctx context.Context, tx db.Querier, hostURL return nil, nil, err } - authClaimModel, err := i.authClaimToModel(ctx, did, identity, authClaim, claimsTree, bjjPubKey, hostURL, didOptions.AuthCredentialStatus, false) + authClaimModel, err := i.authClaimToModel(ctx, did, identity, authClaim, claimsTree, bjjPubKey, didOptions.AuthCredentialStatus, false) if err != nil { log.Error(ctx, "auth claim to model", "err", err) return nil, nil, err @@ -781,7 +781,7 @@ func (i *identity) createIdentity(ctx context.Context, tx db.Querier, hostURL st return nil, nil, err } - authClaimModel, err := i.authClaimToModel(ctx, did, identity, authClaim, claimsTree, pubKey, hostURL, didOptions.AuthCredentialStatus, true) + authClaimModel, err := i.authClaimToModel(ctx, did, identity, authClaim, claimsTree, pubKey, didOptions.AuthCredentialStatus, true) if err != nil { log.Error(ctx, "auth claim to model", "err", err) return nil, nil, err @@ -851,6 +851,82 @@ func (i *identity) createIdentity(ctx context.Context, tx db.Querier, hostURL st return did, identity.State.TreeState().State.BigInt(), nil } +// AddKey adds a new key to the identity +func (i *identity) AddKey(ctx context.Context, did *w3c.DID) error { + return i.storage.Pgx.BeginFunc(ctx, + func(tx pgx.Tx) error { + identity, err := i.identityRepository.GetByID(ctx, tx, *did) + if err != nil { + return err + } + + // get current auth core claim + authHash, err := core.AuthSchemaHash.MarshalText() + if err != nil { + log.Error(ctx, "marshaling auth schema hash", "err", err) + return err + } + currentAuthClaim, err := i.claimsRepository.FindOneClaimBySchemaHash(ctx, tx, did, string(authHash)) + if err != nil { + log.Error(ctx, "finding auth claim by schema hash", "err", err) + return err + } + + var authCoreClaimRevocationStatus domain.AuthCoreClaimRevocationStatus + if err := json.Unmarshal(currentAuthClaim.CredentialStatus.Bytes, &authCoreClaimRevocationStatus); err != nil { + log.Error(ctx, "unmarshalling auth core claim revocation status", "err", err) + return err + } + + // add bjj auth claim + bjjKey, err := i.kms.CreateKey(kms.KeyTypeBabyJubJub, did) + if err != nil { + return err + } + + bjjPubKey, err := bjjPubKey(i.kms, bjjKey) + if err != nil { + return err + } + + authClaim, err := newAuthClaim(bjjPubKey) + if err != nil { + return errors.Join(err, errors.New("can't create auth claim")) + } + + revNonce, err := common.RandInt64() + if err != nil { + log.Error(ctx, "generating revocation nonce", "err", err) + return fmt.Errorf("can't generate revocation nonce: %w", err) + } + + authClaim.SetRevocationNonce(revNonce) + + // get identity merkle trees + mts, err := i.mtService.GetIdentityMerkleTrees(ctx, tx, did) + if err != nil { + return fmt.Errorf("can't create identity markle tree: %w", err) + } + + claimsTree, err := mts.ClaimsTree() + if err != nil { + return err + } + + authClaimModel, err := i.authClaimToModel(ctx, did, identity, authClaim, claimsTree, bjjPubKey, verifiable.CredentialStatusType(authCoreClaimRevocationStatus.Type), false) + if err != nil { + log.Error(ctx, "auth claim to model", "err", err) + return err + } + + _, err = i.claimsRepository.Save(ctx, tx, authClaimModel) + if err != nil { + return errors.Join(err, errors.New("can't save auth claim")) + } + return nil + }) +} + func (i *identity) createEthIdentityFromKeyID(ctx context.Context, mts *domain.IdentityMerkleTrees, key *kms.KeyID, didOptions *ports.DIDCreationOptions, tx db.Querier) (*domain.Identity, *w3c.DID, error) { pubKey, err := ethPubKey(ctx, i.kms, *key) if err != nil { @@ -995,6 +1071,7 @@ func (i *identity) GetFailedState(ctx context.Context, identifier w3c.DID) (*dom return nil, nil } +// TODO: remove this method. is not used func (i *identity) PublishGenesisStateToRHS(ctx context.Context, did *w3c.DID) error { identity, err := i.identityRepository.GetByID(ctx, i.storage.Pgx, *did) if err != nil { @@ -1125,7 +1202,7 @@ func (i *identity) addGenesisClaimsToTree(ctx context.Context, return identity, did, nil } -func (i *identity) authClaimToModel(ctx context.Context, did *w3c.DID, identity *domain.Identity, authClaim *core.Claim, claimsTree *merkletree.MerkleTree, pubKey *babyjub.PublicKey, hostURL string, status verifiable.CredentialStatusType, isAuthInGenesis bool) (*domain.Claim, error) { +func (i *identity) authClaimToModel(ctx context.Context, did *w3c.DID, identity *domain.Identity, authClaim *core.Claim, claimsTree *merkletree.MerkleTree, pubKey *babyjub.PublicKey, status verifiable.CredentialStatusType, isAuthInGenesis bool) (*domain.Claim, error) { authClaimData := make(map[string]interface{}) authClaimData["x"] = pubKey.X.String() authClaimData["y"] = pubKey.Y.String() diff --git a/internal/core/services/identity_test.go b/internal/core/services/identity_test.go index 850f994ec..5e18d722d 100644 --- a/internal/core/services/identity_test.go +++ b/internal/core/services/identity_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + core "github.com/iden3/go-iden3-core/v2" "github.com/iden3/go-iden3-core/v2/w3c" "github.com/iden3/go-schema-processor/v2/verifiable" "github.com/iden3/iden3comm/v2" @@ -688,3 +689,77 @@ func Test_identity_GetLatestStateByID(t *testing.T) { }) } } + +func Test_identity_RotateKey(t *testing.T) { + ctx := context.Background() + identityRepo := repositories.NewIdentity() + claimsRepo := repositories.NewClaim() + mtRepo := repositories.NewIdentityMerkleTreeRepository() + identityStateRepo := repositories.NewIdentityState() + revocationRepository := repositories.NewRevocation() + mtService := NewIdentityMerkleTrees(mtRepo) + connectionsRepository := repositories.NewConnection() + + reader := common.CreateFile(t) + networkResolver, err := network.NewResolver(ctx, cfg, keyStore, reader) + require.NoError(t, err) + + rhsFactory := reversehash.NewFactory(*networkResolver, reversehash.DefaultRHSTimeOut) + revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) + + type testConfig struct { + name string + options *ports.DIDCreationOptions + shouldReturnErr bool + } + + genesisStr := "0000000000000000000000000000000000000000000000000000000000000000" + + for _, tc := range []testConfig{ + { + name: "should rotate BJJ identity", + options: &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: BJJ}, + shouldReturnErr: false, + }, + } { + t.Run(tc.name, func(t *testing.T) { + identity, err := identityService.Create(ctx, cfg.ServerUrl, tc.options) + assert.NoError(t, err) + did, err := w3c.ParseDID(identity.Identifier) + assert.NoError(t, err) + authHash, err := core.AuthSchemaHash.MarshalText() + assert.NoError(t, err) + + authCoreClaim, err := claimsRepo.FindOneClaimBySchemaHash(ctx, storage.Pgx, did, string(authHash)) + assert.NoError(t, err) + if tc.shouldReturnErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, identity.Identifier) + assert.NotNil(t, identity.AuthCoreClaimRevocationStatus) + assert.Equal(t, uint(0), identity.AuthCoreClaimRevocationStatus.RevocationNonce) + assert.Equal(t, string(verifiable.Iden3commRevocationStatusV1), identity.AuthCoreClaimRevocationStatus.Type) + assert.Equal(t, fmt.Sprintf("%s/v2/agent", cfg.ServerUrl), identity.AuthCoreClaimRevocationStatus.ID) + assert.NotNil(t, identity.State.State) + assert.Equal(t, "confirmed", string(identity.State.Status)) + if tc.options.KeyType == ETH { + assert.NotNil(t, identity.Address) + assert.Equal(t, genesisStr, *identity.State.State) + } else if tc.options.KeyType == BJJ { + assert.NotNil(t, identity.State.ClaimsTreeRoot) + } else { + t.Errorf("invalid key type") + } + assert.Equal(t, string(verifiable.Iden3commRevocationStatusV1), identity.AuthCoreClaimRevocationStatus.Type) + assert.Equal(t, fmt.Sprintf("%s/v2/agent", cfg.ServerUrl), identity.AuthCoreClaimRevocationStatus.ID) + assert.Equal(t, uint(0), identity.AuthCoreClaimRevocationStatus.RevocationNonce) + + authCoreClaimNew, err := claimsRepo.FindOneClaimBySchemaHash(ctx, storage.Pgx, did, string(authHash)) + assert.NoError(t, err) + assert.Equal(t, authCoreClaim.ID, authCoreClaimNew.ID) + } + }) + } +} diff --git a/internal/repositories/claim.go b/internal/repositories/claim.go index 54cb6ffe6..22e6fe851 100644 --- a/internal/repositories/claim.go +++ b/internal/repositories/claim.go @@ -371,13 +371,14 @@ func (c *claim) FindOneClaimBySchemaHash(ctx context.Context, conn db.Querier, s WHERE claims.identifier=$1 AND ( claims.other_identifier = $1 or claims.other_identifier = '') AND claims.schema_hash = $2 - AND claims.revoked = false`, subject.String(), schemaHash) + AND claims.revoked = false + AND claims.mtp_proof IS NOT NULL `, subject.String(), schemaHash) err := row.Scan(&claim.ID, &claim.Issuer, &claim.SchemaHash, &claim.SchemaType, - &claim.SchemaHash, + &claim.SchemaURL, &claim.OtherIdentifier, &claim.Expiration, &claim.Updatable, diff --git a/internal/repositories/identity.go b/internal/repositories/identity.go index e139add6b..1c1975abf 100644 --- a/internal/repositories/identity.go +++ b/internal/repositories/identity.go @@ -22,7 +22,7 @@ var ErrDisplayNameDuplicated = errors.New("display name already exists") type identity struct{} -// NewIdentity TODO +// NewIdentity - Create new identity repository func NewIdentity() ports.IndentityRepository { return &identity{} } @@ -70,7 +70,8 @@ func (i *identity) GetByID(ctx context.Context, conn db.Querier, identifier w3c. claims.credential_status FROM identities LEFT JOIN identity_states ON identities.identifier = identity_states.identifier - LEFT JOIN claims ON claims.identifier = identities.identifier and claims.schema_type = 'https://schema.iden3.io/core/jsonld/auth.jsonld#AuthBJJCredential' + LEFT JOIN claims ON claims.identifier = identities.identifier + AND claims.schema_type = 'https://schema.iden3.io/core/jsonld/auth.jsonld#AuthBJJCredential' WHERE identities.identifier=$1 AND ( status = 'transacted' OR status = 'confirmed') OR (identities.identifier=$1 AND status = 'created' AND previous_state is null From 4251620b612b1a1c819b7eb2d1008b2260522a40 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 30 Oct 2024 14:51:56 -0300 Subject: [PATCH 02/62] chore: remove duplicated code --- internal/core/services/identity.go | 47 ++---------------------------- 1 file changed, 2 insertions(+), 45 deletions(-) diff --git a/internal/core/services/identity.go b/internal/core/services/identity.go index d39d3bac2..83c4c5bd2 100644 --- a/internal/core/services/identity.go +++ b/internal/core/services/identity.go @@ -152,7 +152,7 @@ func (i *identity) Create(ctx context.Context, hostURL string, didOptions *ports } func (i *identity) SignClaimEntry(ctx context.Context, authClaim *domain.Claim, claimEntry *core.Claim) (*verifiable.BJJSignatureProof2021, error) { - keyID, err := i.getKeyIDFromAuthClaim(ctx, authClaim) + keyID, err := i.GetKeyIDFromAuthClaim(ctx, authClaim) if err != nil { return nil, err } @@ -209,50 +209,6 @@ func (i *identity) Exists(ctx context.Context, identifier w3c.DID) (bool, error) return identity != nil, nil } -// getKeyIDFromAuthClaim finds BJJ KeyID of auth claim -// in registered key providers -func (i *identity) getKeyIDFromAuthClaim(ctx context.Context, authClaim *domain.Claim) (kms.KeyID, error) { - var keyID kms.KeyID - - if authClaim.Identifier == nil { - return keyID, errors.New("identifier is empty in auth claim") - } - - identity, err := w3c.ParseDID(*authClaim.Identifier) - if err != nil { - return keyID, err - } - - entry := authClaim.CoreClaim.Get() - bjjClaim := entry.RawSlotsAsInts() - - var publicKey babyjub.PublicKey - publicKey.X, publicKey.Y = bjjClaim[2], bjjClaim[3] - - compPubKey := publicKey.Compress() - - keyIDs, err := i.kms.KeysByIdentity(ctx, *identity) - if err != nil { - return keyID, err - } - - for _, keyID = range keyIDs { - if keyID.Type != kms.KeyTypeBabyJubJub { - continue - } - - pubKeyBytes, err := i.kms.PublicKey(keyID) - if err != nil { - return keyID, err - } - if bytes.Equal(pubKeyBytes, compPubKey[:]) { - return keyID, nil - } - } - - return keyID, errors.New("private key not found") -} - // Get - returns all the identities func (i *identity) Get(ctx context.Context) (identities []domain.IdentityDisplayName, err error) { return i.identityRepository.Get(ctx, i.storage.Pgx) @@ -313,6 +269,7 @@ func (i *identity) GetKeyIDFromAuthClaim(ctx context.Context, authClaim *domain. return keyID, err } if bytes.Equal(pubKeyBytes, compPubKey[:]) { + log.Info(ctx, "key found", "keyID", keyID) return keyID, nil } } From 9105926a7e5346eb84ccbfcd3abb7ec5c8420614 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Thu, 31 Oct 2024 08:43:57 -0300 Subject: [PATCH 03/62] chore: add revocation nonce as response --- api/api.yaml | 10 +++++++++- internal/api/api.gen.go | 5 ++++- internal/api/credentials.go | 3 +++ internal/api/identity.go | 13 +++++++++---- internal/core/ports/identity_service.go | 2 +- internal/core/services/identity.go | 21 +++++++++++++-------- 6 files changed, 39 insertions(+), 15 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index 055ba95c2..1d541fa87 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -442,7 +442,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/GenericMessage' + type: object + required: + - revocationNonce + properties: + revocationNonce: + type: integer + format: int64 + description: added key revocation nonce + '400': $ref: '#/components/responses/400' '500': diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 498f62376..4dea35df7 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -3366,7 +3366,10 @@ type AddKeyResponseObject interface { VisitAddKeyResponse(w http.ResponseWriter) error } -type AddKey200JSONResponse GenericMessage +type AddKey200JSONResponse struct { + // RevocationNonce added key revocation nonce + RevocationNonce int64 `json:"revocationNonce"` +} func (response AddKey200JSONResponse) VisitAddKeyResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") diff --git a/internal/api/credentials.go b/internal/api/credentials.go index ddf85f13a..42011607b 100644 --- a/internal/api/credentials.go +++ b/internal/api/credentials.go @@ -96,6 +96,9 @@ func (s *Server) CreateCredential(ctx context.Context, request CreateCredentialR if errors.Is(err, services.ErrLoadingSchema) { return CreateCredential422JSONResponse{N422JSONResponse{Message: err.Error()}}, nil } + if errors.Is(err, repositories.ErrClaimDoesNotExist) { + return CreateCredential500JSONResponse{N500JSONResponse{Message: "if this identity has keyType=ETH you must to publish the state first"}}, nil + } errs := []error{ services.ErrJSONLdContext, services.ErrProcessSchema, diff --git a/internal/api/identity.go b/internal/api/identity.go index 663c1c16f..780f95063 100644 --- a/internal/api/identity.go +++ b/internal/api/identity.go @@ -323,15 +323,20 @@ func (s *Server) AddKey(ctx context.Context, request AddKeyRequestObject) (AddKe }, err } - err = s.identityService.AddKey(ctx, did) + revocationNonce, err := s.identityService.AddKey(ctx, did) if err != nil { log.Error(ctx, "add key. Adding key", "err", err) - return AddKey400JSONResponse{ - N400JSONResponse{ + if errors.Is(err, repositories.ErrClaimDoesNotExist) { + return AddKey500JSONResponse{N500JSONResponse{Message: "If this identity has keyType=ETH you must to publish the state first"}}, nil + } + return AddKey500JSONResponse{ + N500JSONResponse{ Message: err.Error(), }, }, err } - return AddKey200JSONResponse{Message: "Key added"}, nil + return AddKey200JSONResponse{ + RevocationNonce: int64(*revocationNonce), + }, nil } diff --git a/internal/core/ports/identity_service.go b/internal/core/ports/identity_service.go index e01feba2e..39d814cf3 100644 --- a/internal/core/ports/identity_service.go +++ b/internal/core/ports/identity_service.go @@ -58,5 +58,5 @@ type IdentityService interface { GetFailedState(ctx context.Context, identifier w3c.DID) (*domain.IdentityState, error) PublishGenesisStateToRHS(ctx context.Context, did *w3c.DID) error UpdateIdentityDisplayName(ctx context.Context, did w3c.DID, displayName string) error - AddKey(ctx context.Context, did *w3c.DID) error + AddKey(ctx context.Context, did *w3c.DID) (*uint64, error) } diff --git a/internal/core/services/identity.go b/internal/core/services/identity.go index 83c4c5bd2..ee8f37c41 100644 --- a/internal/core/services/identity.go +++ b/internal/core/services/identity.go @@ -809,8 +809,14 @@ func (i *identity) createIdentity(ctx context.Context, tx db.Querier, hostURL st } // AddKey adds a new key to the identity -func (i *identity) AddKey(ctx context.Context, did *w3c.DID) error { - return i.storage.Pgx.BeginFunc(ctx, +func (i *identity) AddKey(ctx context.Context, did *w3c.DID) (*uint64, error) { + revNonce, err := common.RandInt64() + if err != nil { + log.Error(ctx, "generating revocation nonce", "err", err) + return nil, fmt.Errorf("can't generate revocation nonce: %w", err) + } + + err = i.storage.Pgx.BeginFunc(ctx, func(tx pgx.Tx) error { identity, err := i.identityRepository.GetByID(ctx, tx, *did) if err != nil { @@ -851,12 +857,6 @@ func (i *identity) AddKey(ctx context.Context, did *w3c.DID) error { return errors.Join(err, errors.New("can't create auth claim")) } - revNonce, err := common.RandInt64() - if err != nil { - log.Error(ctx, "generating revocation nonce", "err", err) - return fmt.Errorf("can't generate revocation nonce: %w", err) - } - authClaim.SetRevocationNonce(revNonce) // get identity merkle trees @@ -882,6 +882,11 @@ func (i *identity) AddKey(ctx context.Context, did *w3c.DID) error { } return nil }) + if err != nil { + return nil, err + } + + return common.ToPointer(revNonce), nil } func (i *identity) createEthIdentityFromKeyID(ctx context.Context, mts *domain.IdentityMerkleTrees, key *kms.KeyID, didOptions *ports.DIDCreationOptions, tx db.Querier) (*domain.Identity, *w3c.DID, error) { From e74fc747b153b017e5892fb5d68c8c9fbc36430a Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Thu, 28 Nov 2024 09:59:40 -0300 Subject: [PATCH 04/62] chore: add key management service --- internal/core/ports/key_management_service.go | 12 ++++++++++ .../core/services/key_management_service.go | 23 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 internal/core/ports/key_management_service.go create mode 100644 internal/core/services/key_management_service.go diff --git a/internal/core/ports/key_management_service.go b/internal/core/ports/key_management_service.go new file mode 100644 index 000000000..567f971c6 --- /dev/null +++ b/internal/core/ports/key_management_service.go @@ -0,0 +1,12 @@ +package ports + +import ( + "context" + "github.com/iden3/go-iden3-core/v2/w3c" + + "github.com/polygonid/sh-id-platform/internal/kms" +) + +type KeyManagementService interface { + CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) +} diff --git a/internal/core/services/key_management_service.go b/internal/core/services/key_management_service.go new file mode 100644 index 000000000..5957066a1 --- /dev/null +++ b/internal/core/services/key_management_service.go @@ -0,0 +1,23 @@ +package services + +import ( + "context" + "github.com/iden3/go-iden3-core/v2/w3c" + + "github.com/polygonid/sh-id-platform/internal/kms" +) + +type KeyManagementService struct { + kms kms.KeyProvider +} + +func NewKeyManagementService(kms kms.KeyProvider) *KeyManagementService { + return &KeyManagementService{ + kms: kms, + } +} + +// CreateKey creates a new key for the given DID +func (kms *KeyManagementService) CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) { + return kms.kms.New(did) +} From c7462529ea4d085fdddf371b92cdf522ac104732 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Fri, 29 Nov 2024 11:16:26 -0300 Subject: [PATCH 05/62] chore: add create, get and get all for keys and modify create auth core claim --- api/api.yaml | 218 +++++- cmd/platform/main.go | 6 +- internal/api/api.gen.go | 663 +++++++++++++++--- internal/api/api_custom_types.go | 22 + internal/api/identity.go | 32 +- internal/api/key.go | 102 +++ internal/api/key_test.go | 282 ++++++++ internal/api/main_test.go | 5 +- internal/api/server.go | 4 +- internal/core/domain/claim.go | 15 + internal/core/ports/claim_repository.go | 1 + internal/core/ports/claim_service.go | 1 + internal/core/ports/identity_service.go | 2 +- internal/core/ports/key_management_service.go | 12 - internal/core/ports/key_service.go | 28 + internal/core/services/claims.go | 8 + internal/core/services/identity.go | 49 +- .../core/services/key_management_service.go | 23 - internal/core/services/key_service.go | 96 +++ internal/repositories/claim.go | 58 ++ 20 files changed, 1430 insertions(+), 197 deletions(-) create mode 100644 internal/api/api_custom_types.go create mode 100644 internal/api/key.go create mode 100644 internal/api/key_test.go delete mode 100644 internal/core/ports/key_management_service.go create mode 100644 internal/core/ports/key_service.go delete mode 100644 internal/core/services/key_management_service.go create mode 100644 internal/core/services/key_service.go diff --git a/api/api.yaml b/api/api.yaml index 0a06839a0..e05cb4af7 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -26,6 +26,8 @@ tags: description: Collection of endpoints related to Mobile - name: config description: Collection of endpoints related to Config + - name: Key Management + description: Collection of endpoints related to Key Management paths: @@ -425,31 +427,48 @@ paths: '500': $ref: '#/components/responses/500' - /v2/identities/{identifier}/add-key: + /v2/identities/{identifier}/create-auth-core-claim: post: summary: Add a new key to the identity - operationId: AddKey - description: Endpoint to add a new key to the identity + operationId: CreateAuthCoreClaim + description: Endpoint to create a new Auth Core Claim security: - basicAuth: [ ] parameters: - - $ref: '#/components/parameters/pathIdentifier' + - $ref: '#/components/parameters/pathIdentifier2' tags: - Identity + - Key Management + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - keyID + properties: + keyID: + type: string + x-omitempty: false + example: ZGlkOnBvbHlnb25pZDpwb2x5Z29uOmFtb3k6MnFRNjhKa1JjZjN5cXBYanRqVVQ3WjdVeW1TV0hzYll responses: - '200': + '201': description: Key added successfully content: application/json: schema: type: object required: - - revocationNonce + - id properties: - revocationNonce: - type: integer - format: int64 - description: added key revocation nonce + 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 '400': $ref: '#/components/responses/400' @@ -1323,6 +1342,115 @@ paths: '500': $ref: '#/components/responses/500' + /v2/identities/{identifier}/keys: + post: + summary: Create a Key + operationId: CreateKey + description: Endpoint to create a new key. + tags: + - Key Management + security: + - basicAuth: [ ] + parameters: + - $ref: '#/components/parameters/pathIdentifier2' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateKeyRequest' + responses: + '201': + description: Crated Key + content: + application/json: + schema: + $ref: '#/components/schemas/CreateKeyResponse' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '500': + $ref: '#/components/responses/500' + + get: + summary: Get Keys + operationId: GetKeys + description: | + Returns a list of Keys for the provided identity. + security: + - basicAuth: [ ] + tags: + - Key Management + parameters: + - $ref: '#/components/parameters/pathIdentifier2' + responses: + '200': + description: Keys collection + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Key' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + '500': + $ref: '#/components/responses/500' + + /v2/identities/{identifier}/keys/{keyID}: + get: + summary: Get a Key + operationId: GetKey + description: Get a specific key for the provided identity. + tags: + - Key Management + security: + - basicAuth: [ ] + parameters: + - $ref: '#/components/parameters/pathIdentifier2' + - $ref: '#/components/parameters/pathKeyID' + responses: + '200': + description: Key found + content: + application/json: + schema: + $ref: '#/components/schemas/Key' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '500': + $ref: '#/components/responses/500' +# delete: +# summary: Delete Key +# operationId: DeleteKey +# description: Remove a specific key for the provided identity. +# tags: +# - Identity +# security: +# - basicAuth: [ ] +# parameters: +# - $ref: '#/components/parameters/pathIdentifier' +# - $ref: '#/components/parameters/keyID' +# responses: +# '200': +# description: Key deleted +# content: +# application/json: +# schema: +# $ref: '#/components/schemas/GenericMessage' +# '400': +# $ref: '#/components/responses/400' +# '401': +# $ref: '#/components/responses/401' +# '500': +# $ref: '#/components/responses/500' components: securitySchemes: basicAuth: @@ -2262,6 +2390,55 @@ components: name: protocol path: github.com/iden3/iden3comm/v2/protocol + CreateKeyRequest: + type: object + required: + - keyType + properties: + keyType: + type: string + x-omitempty: false + example: "BJJ" + enum: [ BJJ, ETH ] + + CreateKeyResponse: + type: object + required: + - keyID + properties: + keyID: + type: string + x-omitempty: false + description: base64 encoded keyID + example: a2V5cy9kaWQ6aWRlbjM6cG9seWdvbjphbW95OnhKQktvbkJ1dWdKbW1aMkdvS2gzOTM + + Key: + type: object + required: + - keyID + - keyType + - publicKey + - isAuthCoreClaim + properties: + keyID: + type: string + x-omitempty: false + example: ZGlkOnBvbHlnb25pZDpwb2x5Z29uOmFtb3k6MnFRNjhKa1JjZjN5cXBYanRqVVQ3WjdVeW1TV0hzYll + description: base64 encoded keyID + keyType: + type: string + x-omitempty: false + example: "BJJ" + enum: [ BJJ, ETH ] + publicKey: + type: string + x-omitempty: false + example: "0x04e3e7e" + isAuthCoreClaim: + type: boolean + x-omitempty: false + example: true + parameters: credentialStatusType: name: credentialStatusType @@ -2319,6 +2496,18 @@ components: schema: type: string + pathIdentifier2: + name: identifier + in: path + required: true + description: Issuer identifier + schema: + type: string + x-go-type: Identity + x-go-type-import: + name: customIdentity + path: github.com/polygonid/sh-id-platform/internal/api + pathClaim: name: id in: path @@ -2336,6 +2525,15 @@ components: type: integer format: int64 + pathKeyID: + name: keyID + in: path + required: true + description: Key ID in base64 + schema: + type: string + + responses: '400': description: 'Bad Request' diff --git a/cmd/platform/main.go b/cmd/platform/main.go index 67943f652..8dd00f109 100644 --- a/cmd/platform/main.go +++ b/cmd/platform/main.go @@ -151,7 +151,7 @@ func main() { proofService := services.NewProver(circuitsLoaderService) schemaService := services.NewSchema(schemaRepository, schemaLoader) linkService := services.NewLinkService(storage, claimsService, qrService, claimsRepository, linkRepository, schemaRepository, schemaLoader, sessionRepository, ps, identityService, *networkResolver, cfg.UniversalLinks) - + keyService := services.NewKeyService(keyStore, claimsService) transactionService, err := gateways.NewTransaction(*networkResolver) if err != nil { log.Error(ctx, "error creating transaction service", "err", err) @@ -191,10 +191,12 @@ func main() { corsMiddleware.Handler, chiMiddleware.NoCache, ) + api.HandlerWithOptions( api.NewStrictHandlerWithOptions( - api.NewServer(cfg, identityService, accountService, connectionsService, claimsService, qrService, publisher, packageManager, *networkResolver, serverHealth, schemaService, linkService), + api.NewServer(cfg, identityService, accountService, connectionsService, claimsService, qrService, publisher, packageManager, *networkResolver, serverHealth, schemaService, linkService, keyService), middlewares(ctx, cfg.HTTPBasicAuth), + api.StrictHTTPServerOptions{ RequestErrorHandlerFunc: errors.RequestErrorHandlerFunc, ResponseErrorHandlerFunc: errors.ResponseErrorHandlerFunc, diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index c19f201dd..2a690ac1f 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -46,8 +46,8 @@ const ( // Defines values for CreateIdentityRequestDidMetadataType. const ( - BJJ CreateIdentityRequestDidMetadataType = "BJJ" - ETH CreateIdentityRequestDidMetadataType = "ETH" + CreateIdentityRequestDidMetadataTypeBJJ CreateIdentityRequestDidMetadataType = "BJJ" + CreateIdentityRequestDidMetadataTypeETH CreateIdentityRequestDidMetadataType = "ETH" ) // Defines values for CreateIdentityResponseCredentialStatusType. @@ -57,6 +57,12 @@ const ( CreateIdentityResponseCredentialStatusTypeIden3commRevocationStatusV10 CreateIdentityResponseCredentialStatusType = "Iden3commRevocationStatusV1.0" ) +// Defines values for CreateKeyRequestKeyType. +const ( + CreateKeyRequestKeyTypeBJJ CreateKeyRequestKeyType = "BJJ" + CreateKeyRequestKeyTypeETH CreateKeyRequestKeyType = "ETH" +) + // Defines values for DisplayMethodType. const ( Iden3BasicDisplayMethodv2 DisplayMethodType = "Iden3BasicDisplayMethodv2" @@ -76,6 +82,12 @@ const ( Iden3commRevocationStatusV10 GetIdentityDetailsResponseCredentialStatusType = "Iden3commRevocationStatusV1.0" ) +// Defines values for KeyKeyType. +const ( + BJJ KeyKeyType = "BJJ" + ETH KeyKeyType = "ETH" +) + // Defines values for LinkStatus. const ( LinkStatusActive LinkStatus = "active" @@ -257,6 +269,20 @@ type CreateIdentityResponse struct { // CreateIdentityResponseCredentialStatusType defines model for CreateIdentityResponse.CredentialStatusType. type CreateIdentityResponseCredentialStatusType string +// CreateKeyRequest defines model for CreateKeyRequest. +type CreateKeyRequest struct { + KeyType CreateKeyRequestKeyType `json:"keyType"` +} + +// CreateKeyRequestKeyType defines model for CreateKeyRequest.KeyType. +type CreateKeyRequestKeyType string + +// CreateKeyResponse defines model for CreateKeyResponse. +type CreateKeyResponse struct { + // KeyID base64 encoded keyID + KeyID string `json:"keyID"` +} + // CreateLinkRequest defines model for CreateLinkRequest. type CreateLinkRequest struct { CredentialExpiration *time.Time `json:"credentialExpiration,omitempty"` @@ -401,6 +427,19 @@ type IssuerDescription struct { Logo string `json:"logo"` } +// Key defines model for Key. +type Key struct { + IsAuthCoreClaim bool `json:"isAuthCoreClaim"` + + // KeyID base64 encoded keyID + KeyID string `json:"keyID"` + KeyType KeyKeyType `json:"keyType"` + PublicKey string `json:"publicKey"` +} + +// KeyKeyType defines model for Key.KeyType. +type KeyKeyType string + // Link defines model for Link. type Link struct { Active bool `json:"active"` @@ -559,6 +598,12 @@ type PathClaim = string // PathIdentifier defines model for pathIdentifier. type PathIdentifier = string +// PathIdentifier2 defines model for pathIdentifier2. +type PathIdentifier2 = Identity + +// PathKeyID defines model for pathKeyID. +type PathKeyID = string + // PathNonce defines model for pathNonce. type PathNonce = int64 @@ -644,6 +689,11 @@ type DeleteConnectionParams struct { DeleteCredentials *bool `form:"deleteCredentials,omitempty" json:"deleteCredentials,omitempty"` } +// CreateAuthCoreClaimJSONBody defines parameters for CreateAuthCoreClaim. +type CreateAuthCoreClaimJSONBody struct { + KeyID string `json:"keyID"` +} + // GetCredentialsParams defines parameters for GetCredentials. type GetCredentialsParams struct { // Page Page to fetch. First is one. If omitted, all results will be returned. @@ -771,6 +821,9 @@ type UpdateIdentityJSONRequestBody UpdateIdentityJSONBody // CreateConnectionJSONRequestBody defines body for CreateConnection for application/json ContentType. type CreateConnectionJSONRequestBody = CreateConnectionRequest +// CreateAuthCoreClaimJSONRequestBody defines body for CreateAuthCoreClaim for application/json ContentType. +type CreateAuthCoreClaimJSONRequestBody CreateAuthCoreClaimJSONBody + // CreateCredentialJSONRequestBody defines body for CreateCredential for application/json ContentType. type CreateCredentialJSONRequestBody = CreateCredentialRequest @@ -783,6 +836,9 @@ type CreateLinkQrCodeCallbackTextRequestBody = CreateLinkQrCodeCallbackTextBody // ActivateLinkJSONRequestBody defines body for ActivateLink for application/json ContentType. type ActivateLinkJSONRequestBody ActivateLinkJSONBody +// CreateKeyJSONRequestBody defines body for CreateKey for application/json ContentType. +type CreateKeyJSONRequestBody = CreateKeyRequest + // ImportSchemaJSONRequestBody defines body for ImportSchema for application/json ContentType. type ImportSchemaJSONRequestBody = ImportSchemaRequest @@ -818,9 +874,6 @@ type ServerInterface interface { // Update Identity // (PATCH /v2/identities/{identifier}) UpdateIdentity(w http.ResponseWriter, r *http.Request, identifier PathIdentifier) - // Add a new key to the identity - // (POST /v2/identities/{identifier}/add-key) - AddKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier) // Get Connections // (GET /v2/identities/{identifier}/connections) GetConnections(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetConnectionsParams) @@ -839,6 +892,9 @@ type ServerInterface interface { // Revoke Connection Credentials // (POST /v2/identities/{identifier}/connections/{id}/credentials/revoke) RevokeConnectionCredentials(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, id Id) + // Add a new key to the identity + // (POST /v2/identities/{identifier}/create-auth-core-claim) + CreateAuthCoreClaim(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) // Get Credentials // (GET /v2/identities/{identifier}/credentials) GetCredentials(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetCredentialsParams) @@ -881,6 +937,15 @@ type ServerInterface interface { // Get Credentials Offer // (GET /v2/identities/{identifier}/credentials/{id}/offer) GetCredentialOffer(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, id PathClaim, params GetCredentialOfferParams) + // Get Keys + // (GET /v2/identities/{identifier}/keys) + GetKeys(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) + // Create a Key + // (POST /v2/identities/{identifier}/keys) + CreateKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) + // Get a Key + // (GET /v2/identities/{identifier}/keys/{keyID}) + GetKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) // Get Schemas // (GET /v2/identities/{identifier}/schemas) GetSchemas(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetSchemasParams) @@ -977,12 +1042,6 @@ func (_ Unimplemented) UpdateIdentity(w http.ResponseWriter, r *http.Request, id w.WriteHeader(http.StatusNotImplemented) } -// Add a new key to the identity -// (POST /v2/identities/{identifier}/add-key) -func (_ Unimplemented) AddKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier) { - w.WriteHeader(http.StatusNotImplemented) -} - // Get Connections // (GET /v2/identities/{identifier}/connections) func (_ Unimplemented) GetConnections(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetConnectionsParams) { @@ -1019,6 +1078,12 @@ func (_ Unimplemented) RevokeConnectionCredentials(w http.ResponseWriter, r *htt w.WriteHeader(http.StatusNotImplemented) } +// Add a new key to the identity +// (POST /v2/identities/{identifier}/create-auth-core-claim) +func (_ Unimplemented) CreateAuthCoreClaim(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { + w.WriteHeader(http.StatusNotImplemented) +} + // Get Credentials // (GET /v2/identities/{identifier}/credentials) func (_ Unimplemented) GetCredentials(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetCredentialsParams) { @@ -1103,6 +1168,24 @@ func (_ Unimplemented) GetCredentialOffer(w http.ResponseWriter, r *http.Request w.WriteHeader(http.StatusNotImplemented) } +// Get Keys +// (GET /v2/identities/{identifier}/keys) +func (_ Unimplemented) GetKeys(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Create a Key +// (POST /v2/identities/{identifier}/keys) +func (_ Unimplemented) CreateKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Get a Key +// (GET /v2/identities/{identifier}/keys/{keyID}) +func (_ Unimplemented) GetKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) { + w.WriteHeader(http.StatusNotImplemented) +} + // Get Schemas // (GET /v2/identities/{identifier}/schemas) func (_ Unimplemented) GetSchemas(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetSchemasParams) { @@ -1415,37 +1498,6 @@ func (siw *ServerInterfaceWrapper) UpdateIdentity(w http.ResponseWriter, r *http handler.ServeHTTP(w, r) } -// AddKey operation middleware -func (siw *ServerInterfaceWrapper) AddKey(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "identifier" ------------- - var identifier PathIdentifier - - err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.AddKey(w, r, identifier) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - // GetConnections operation middleware func (siw *ServerInterfaceWrapper) GetConnections(w http.ResponseWriter, r *http.Request) { @@ -1730,6 +1782,37 @@ func (siw *ServerInterfaceWrapper) RevokeConnectionCredentials(w http.ResponseWr handler.ServeHTTP(w, r) } +// CreateAuthCoreClaim operation middleware +func (siw *ServerInterfaceWrapper) CreateAuthCoreClaim(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "identifier" ------------- + var identifier PathIdentifier2 + + err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateAuthCoreClaim(w, r, identifier) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // GetCredentials operation middleware func (siw *ServerInterfaceWrapper) GetCredentials(w http.ResponseWriter, r *http.Request) { @@ -2326,6 +2409,108 @@ func (siw *ServerInterfaceWrapper) GetCredentialOffer(w http.ResponseWriter, r * handler.ServeHTTP(w, r) } +// GetKeys operation middleware +func (siw *ServerInterfaceWrapper) GetKeys(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "identifier" ------------- + var identifier PathIdentifier2 + + err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetKeys(w, r, identifier) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateKey operation middleware +func (siw *ServerInterfaceWrapper) CreateKey(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "identifier" ------------- + var identifier PathIdentifier2 + + err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateKey(w, r, identifier) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetKey operation middleware +func (siw *ServerInterfaceWrapper) GetKey(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "identifier" ------------- + var identifier PathIdentifier2 + + err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) + return + } + + // ------------- Path parameter "keyID" ------------- + var keyID PathKeyID + + err = runtime.BindStyledParameterWithOptions("simple", "keyID", chi.URLParam(r, "keyID"), &keyID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "keyID", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetKey(w, r, identifier, keyID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // GetSchemas operation middleware func (siw *ServerInterfaceWrapper) GetSchemas(w http.ResponseWriter, r *http.Request) { @@ -2832,9 +3017,6 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Patch(options.BaseURL+"/v2/identities/{identifier}", wrapper.UpdateIdentity) }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/v2/identities/{identifier}/add-key", wrapper.AddKey) - }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/identities/{identifier}/connections", wrapper.GetConnections) }) @@ -2853,6 +3035,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/v2/identities/{identifier}/connections/{id}/credentials/revoke", wrapper.RevokeConnectionCredentials) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/v2/identities/{identifier}/create-auth-core-claim", wrapper.CreateAuthCoreClaim) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/identities/{identifier}/credentials", wrapper.GetCredentials) }) @@ -2895,6 +3080,15 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/identities/{identifier}/credentials/{id}/offer", wrapper.GetCredentialOffer) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/v2/identities/{identifier}/keys", wrapper.GetKeys) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/v2/identities/{identifier}/keys", wrapper.CreateKey) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/v2/identities/{identifier}/keys/{keyID}", wrapper.GetKey) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/identities/{identifier}/schemas", wrapper.GetSchemas) }) @@ -3364,44 +3558,6 @@ func (response UpdateIdentity500JSONResponse) VisitUpdateIdentityResponse(w http return json.NewEncoder(w).Encode(response) } -type AddKeyRequestObject struct { - Identifier PathIdentifier `json:"identifier"` -} - -type AddKeyResponseObject interface { - VisitAddKeyResponse(w http.ResponseWriter) error -} - -type AddKey200JSONResponse struct { - // RevocationNonce added key revocation nonce - RevocationNonce int64 `json:"revocationNonce"` -} - -func (response AddKey200JSONResponse) VisitAddKeyResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type AddKey400JSONResponse struct{ N400JSONResponse } - -func (response AddKey400JSONResponse) VisitAddKeyResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) - - return json.NewEncoder(w).Encode(response) -} - -type AddKey500JSONResponse struct{ N500JSONResponse } - -func (response AddKey500JSONResponse) VisitAddKeyResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) - - return json.NewEncoder(w).Encode(response) -} - type GetConnectionsRequestObject struct { Identifier PathIdentifier `json:"identifier"` Params GetConnectionsParams @@ -3619,6 +3775,44 @@ func (response RevokeConnectionCredentials500JSONResponse) VisitRevokeConnection return json.NewEncoder(w).Encode(response) } +type CreateAuthCoreClaimRequestObject struct { + Identifier PathIdentifier2 `json:"identifier"` + Body *CreateAuthCoreClaimJSONRequestBody +} + +type CreateAuthCoreClaimResponseObject interface { + VisitCreateAuthCoreClaimResponse(w http.ResponseWriter) error +} + +type CreateAuthCoreClaim201JSONResponse struct { + Id uuid.UUID `json:"id"` +} + +func (response CreateAuthCoreClaim201JSONResponse) VisitCreateAuthCoreClaimResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(201) + + return json.NewEncoder(w).Encode(response) +} + +type CreateAuthCoreClaim400JSONResponse struct{ N400JSONResponse } + +func (response CreateAuthCoreClaim400JSONResponse) VisitCreateAuthCoreClaimResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type CreateAuthCoreClaim500JSONResponse struct{ N500JSONResponse } + +func (response CreateAuthCoreClaim500JSONResponse) VisitCreateAuthCoreClaimResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type GetCredentialsRequestObject struct { Identifier PathIdentifier `json:"identifier"` Params GetCredentialsParams @@ -4243,6 +4437,149 @@ func (response GetCredentialOffer500JSONResponse) VisitGetCredentialOfferRespons return json.NewEncoder(w).Encode(response) } +type GetKeysRequestObject struct { + Identifier PathIdentifier2 `json:"identifier"` +} + +type GetKeysResponseObject interface { + VisitGetKeysResponse(w http.ResponseWriter) error +} + +type GetKeys200JSONResponse []Key + +func (response GetKeys200JSONResponse) VisitGetKeysResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetKeys400JSONResponse struct{ N400JSONResponse } + +func (response GetKeys400JSONResponse) VisitGetKeysResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type GetKeys404JSONResponse struct{ N404JSONResponse } + +func (response GetKeys404JSONResponse) VisitGetKeysResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type GetKeys500JSONResponse struct{ N500JSONResponse } + +func (response GetKeys500JSONResponse) VisitGetKeysResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type CreateKeyRequestObject struct { + Identifier PathIdentifier2 `json:"identifier"` + Body *CreateKeyJSONRequestBody +} + +type CreateKeyResponseObject interface { + VisitCreateKeyResponse(w http.ResponseWriter) error +} + +type CreateKey201JSONResponse CreateKeyResponse + +func (response CreateKey201JSONResponse) VisitCreateKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(201) + + return json.NewEncoder(w).Encode(response) +} + +type CreateKey400JSONResponse struct{ N400JSONResponse } + +func (response CreateKey400JSONResponse) VisitCreateKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type CreateKey401JSONResponse struct{ N401JSONResponse } + +func (response CreateKey401JSONResponse) VisitCreateKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) +} + +type CreateKey500JSONResponse struct{ N500JSONResponse } + +func (response CreateKey500JSONResponse) VisitCreateKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type GetKeyRequestObject struct { + Identifier PathIdentifier2 `json:"identifier"` + KeyID PathKeyID `json:"keyID"` +} + +type GetKeyResponseObject interface { + VisitGetKeyResponse(w http.ResponseWriter) error +} + +type GetKey200JSONResponse Key + +func (response GetKey200JSONResponse) VisitGetKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetKey400JSONResponse struct{ N400JSONResponse } + +func (response GetKey400JSONResponse) VisitGetKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type GetKey401JSONResponse struct{ N401JSONResponse } + +func (response GetKey401JSONResponse) VisitGetKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) +} + +type GetKey404JSONResponse struct{ N404JSONResponse } + +func (response GetKey404JSONResponse) VisitGetKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type GetKey500JSONResponse struct{ N500JSONResponse } + +func (response GetKey500JSONResponse) VisitGetKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type GetSchemasRequestObject struct { Identifier PathIdentifier `json:"identifier"` Params GetSchemasParams @@ -4692,9 +5029,6 @@ type StrictServerInterface interface { // Update Identity // (PATCH /v2/identities/{identifier}) UpdateIdentity(ctx context.Context, request UpdateIdentityRequestObject) (UpdateIdentityResponseObject, error) - // Add a new key to the identity - // (POST /v2/identities/{identifier}/add-key) - AddKey(ctx context.Context, request AddKeyRequestObject) (AddKeyResponseObject, error) // Get Connections // (GET /v2/identities/{identifier}/connections) GetConnections(ctx context.Context, request GetConnectionsRequestObject) (GetConnectionsResponseObject, error) @@ -4713,6 +5047,9 @@ type StrictServerInterface interface { // Revoke Connection Credentials // (POST /v2/identities/{identifier}/connections/{id}/credentials/revoke) RevokeConnectionCredentials(ctx context.Context, request RevokeConnectionCredentialsRequestObject) (RevokeConnectionCredentialsResponseObject, error) + // Add a new key to the identity + // (POST /v2/identities/{identifier}/create-auth-core-claim) + CreateAuthCoreClaim(ctx context.Context, request CreateAuthCoreClaimRequestObject) (CreateAuthCoreClaimResponseObject, error) // Get Credentials // (GET /v2/identities/{identifier}/credentials) GetCredentials(ctx context.Context, request GetCredentialsRequestObject) (GetCredentialsResponseObject, error) @@ -4755,6 +5092,15 @@ type StrictServerInterface interface { // Get Credentials Offer // (GET /v2/identities/{identifier}/credentials/{id}/offer) GetCredentialOffer(ctx context.Context, request GetCredentialOfferRequestObject) (GetCredentialOfferResponseObject, error) + // Get Keys + // (GET /v2/identities/{identifier}/keys) + GetKeys(ctx context.Context, request GetKeysRequestObject) (GetKeysResponseObject, error) + // Create a Key + // (POST /v2/identities/{identifier}/keys) + CreateKey(ctx context.Context, request CreateKeyRequestObject) (CreateKeyResponseObject, error) + // Get a Key + // (GET /v2/identities/{identifier}/keys/{keyID}) + GetKey(ctx context.Context, request GetKeyRequestObject) (GetKeyResponseObject, error) // Get Schemas // (GET /v2/identities/{identifier}/schemas) GetSchemas(ctx context.Context, request GetSchemasRequestObject) (GetSchemasResponseObject, error) @@ -5105,32 +5451,6 @@ func (sh *strictHandler) UpdateIdentity(w http.ResponseWriter, r *http.Request, } } -// AddKey operation middleware -func (sh *strictHandler) AddKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier) { - var request AddKeyRequestObject - - request.Identifier = identifier - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.AddKey(ctx, request.(AddKeyRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "AddKey") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(AddKeyResponseObject); ok { - if err := validResponse.VisitAddKeyResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - // GetConnections operation middleware func (sh *strictHandler) GetConnections(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetConnectionsParams) { var request GetConnectionsRequestObject @@ -5300,6 +5620,39 @@ func (sh *strictHandler) RevokeConnectionCredentials(w http.ResponseWriter, r *h } } +// CreateAuthCoreClaim operation middleware +func (sh *strictHandler) CreateAuthCoreClaim(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { + var request CreateAuthCoreClaimRequestObject + + request.Identifier = identifier + + var body CreateAuthCoreClaimJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.CreateAuthCoreClaim(ctx, request.(CreateAuthCoreClaimRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CreateAuthCoreClaim") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(CreateAuthCoreClaimResponseObject); ok { + if err := validResponse.VisitCreateAuthCoreClaimResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // GetCredentials operation middleware func (sh *strictHandler) GetCredentials(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetCredentialsParams) { var request GetCredentialsRequestObject @@ -5706,6 +6059,92 @@ func (sh *strictHandler) GetCredentialOffer(w http.ResponseWriter, r *http.Reque } } +// GetKeys operation middleware +func (sh *strictHandler) GetKeys(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { + var request GetKeysRequestObject + + request.Identifier = identifier + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetKeys(ctx, request.(GetKeysRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetKeys") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetKeysResponseObject); ok { + if err := validResponse.VisitGetKeysResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// CreateKey operation middleware +func (sh *strictHandler) CreateKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { + var request CreateKeyRequestObject + + request.Identifier = identifier + + var body CreateKeyJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.CreateKey(ctx, request.(CreateKeyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CreateKey") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(CreateKeyResponseObject); ok { + if err := validResponse.VisitCreateKeyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// GetKey operation middleware +func (sh *strictHandler) GetKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) { + var request GetKeyRequestObject + + request.Identifier = identifier + request.KeyID = keyID + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetKey(ctx, request.(GetKeyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetKey") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetKeyResponseObject); ok { + if err := validResponse.VisitGetKeyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // GetSchemas operation middleware func (sh *strictHandler) GetSchemas(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetSchemasParams) { var request GetSchemasRequestObject diff --git a/internal/api/api_custom_types.go b/internal/api/api_custom_types.go new file mode 100644 index 000000000..1f4b8b388 --- /dev/null +++ b/internal/api/api_custom_types.go @@ -0,0 +1,22 @@ +package api + +import "github.com/iden3/go-iden3-core/v2/w3c" + +// Identity represents a DID +type Identity struct { + w3cDID *w3c.DID +} + +// UnmarshalText unmarshals a DID from text +func (c *Identity) UnmarshalText(text []byte) error { + did, err := w3c.ParseDID(string(text)) + if err != nil { + return err + } + c.w3cDID = did + return nil +} + +func (c *Identity) did() *w3c.DID { + return c.w3cDID +} diff --git a/internal/api/identity.go b/internal/api/identity.go index f36370c01..6aafc9754 100644 --- a/internal/api/identity.go +++ b/internal/api/identity.go @@ -2,6 +2,7 @@ package api import ( "context" + b64 "encoding/base64" "errors" "fmt" "math/big" @@ -311,32 +312,41 @@ func (s *Server) GetIdentityDetails(ctx context.Context, request GetIdentityDeta return response, nil } -// AddKey is the controller to add key -func (s *Server) AddKey(ctx context.Context, request AddKeyRequestObject) (AddKeyResponseObject, error) { - did, err := w3c.ParseDID(request.Identifier) +// CreateAuthCoreClaim is the controller to create an auth core claim +func (s *Server) CreateAuthCoreClaim(ctx context.Context, request CreateAuthCoreClaimRequestObject) (CreateAuthCoreClaimResponseObject, error) { + decodedKeyID, err := b64.StdEncoding.DecodeString(request.Body.KeyID) if err != nil { - log.Error(ctx, "add key. Parsing did", "err", err) - return AddKey400JSONResponse{ + log.Error(ctx, "add key. Decoding key id", "err", err) + return CreateAuthCoreClaim400JSONResponse{ N400JSONResponse{ - Message: "invalid did", + Message: "invalid key id", }, }, err } - revocationNonce, err := s.identityService.AddKey(ctx, did) + authCoreClaimID, err := s.identityService.AddKey(ctx, request.Identifier.w3cDID, string(decodedKeyID)) if err != nil { log.Error(ctx, "add key. Adding key", "err", err) + if errors.Is(err, services.ErrSavingAuthCoreClaim) { + message := fmt.Sprintf("%s. This means an auth core claim was already created with this key", err.Error()) + return CreateAuthCoreClaim400JSONResponse{ + N400JSONResponse{ + Message: message, + }, + }, nil + } + if errors.Is(err, repositories.ErrClaimDoesNotExist) { - return AddKey500JSONResponse{N500JSONResponse{Message: "If this identity has keyType=ETH you must to publish the state first"}}, nil + return CreateAuthCoreClaim500JSONResponse{N500JSONResponse{Message: "If this identity has keyType=ETH you must to publish the state first"}}, nil } - return AddKey500JSONResponse{ + return CreateAuthCoreClaim500JSONResponse{ N500JSONResponse{ Message: err.Error(), }, }, err } - return AddKey200JSONResponse{ - RevocationNonce: int64(*revocationNonce), + return CreateAuthCoreClaim201JSONResponse{ + Id: authCoreClaimID, }, nil } diff --git a/internal/api/key.go b/internal/api/key.go new file mode 100644 index 000000000..2b63344fc --- /dev/null +++ b/internal/api/key.go @@ -0,0 +1,102 @@ +package api + +import ( + "context" + b64 "encoding/base64" + "errors" + + "github.com/polygonid/sh-id-platform/internal/core/ports" + "github.com/polygonid/sh-id-platform/internal/kms" + "github.com/polygonid/sh-id-platform/internal/log" +) + +// CreateKey is the handler for the POST /keys endpoint. +func (s *Server) CreateKey(ctx context.Context, request CreateKeyRequestObject) (CreateKeyResponseObject, error) { + if string(request.Body.KeyType) != string(BJJ) { + log.Error(ctx, "create key. Invalid key type. BJJ and ETH Keys are supported") + return CreateKey400JSONResponse{ + N400JSONResponse{ + Message: "Invalid key type. BJJ Keys are supported", + }, + }, nil + } + + keyID, err := s.keyService.CreateKey(ctx, request.Identifier.did(), kms.KeyType(request.Body.KeyType)) + if err != nil { + log.Error(ctx, "add key. Creating key", "err", err) + return CreateKey500JSONResponse{ + N500JSONResponse{ + Message: "internal error", + }, + }, err + } + + encodedKeyID := b64.StdEncoding.EncodeToString([]byte(keyID.ID)) + return CreateKey201JSONResponse{ + KeyID: encodedKeyID, + }, nil +} + +// GetKey is the handler for the GET /keys/{keyID} endpoint. +func (s *Server) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKeyResponseObject, error) { + decodedKeyID, err := b64.StdEncoding.DecodeString(request.KeyID) + if err != nil { + log.Error(ctx, "get key. Decoding key id", "err", err) + return GetKey400JSONResponse{ + N400JSONResponse{ + Message: "invalid key id", + }, + }, nil + } + + key, err := s.keyService.Get(ctx, request.Identifier.did(), string(decodedKeyID)) + if err != nil { + log.Error(ctx, "get key. Getting key", "err", err) + if errors.Is(err, ports.ErrInvalidKeyType) { + return GetKey400JSONResponse{ + N400JSONResponse{ + Message: "invalid key type", + }, + }, nil + } + + return GetKey500JSONResponse{ + N500JSONResponse{ + Message: "internal error", + }, + }, nil + } + + encodedKeyID := b64.StdEncoding.EncodeToString([]byte(key.KeyID)) + return GetKey200JSONResponse{ + KeyID: encodedKeyID, + KeyType: KeyKeyType(key.KeyType), + PublicKey: key.PublicKey, + IsAuthCoreClaim: key.HasAssociatedAuthCoreClaim, + }, nil +} + +// GetKeys is the handler for the GET /keys endpoint. +func (s *Server) GetKeys(ctx context.Context, request GetKeysRequestObject) (GetKeysResponseObject, error) { + keys, err := s.keyService.GetAll(ctx, request.Identifier.did()) + if err != nil { + log.Error(ctx, "get keys. Getting keys", "err", err) + return GetKeys500JSONResponse{ + N500JSONResponse{ + Message: "internal error", + }, + }, nil + } + + var keysResponse GetKeys200JSONResponse + for _, key := range keys { + encodedKeyID := b64.StdEncoding.EncodeToString([]byte(key.KeyID)) + keysResponse = append(keysResponse, Key{ + KeyID: encodedKeyID, + KeyType: KeyKeyType(key.KeyType), + PublicKey: key.PublicKey, + IsAuthCoreClaim: key.HasAssociatedAuthCoreClaim, + }) + } + return keysResponse, nil +} diff --git a/internal/api/key_test.go b/internal/api/key_test.go new file mode 100644 index 000000000..64a39adf8 --- /dev/null +++ b/internal/api/key_test.go @@ -0,0 +1,282 @@ +package api + +import ( + "context" + b64 "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/iden3/go-iden3-core/v2/w3c" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "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" +) + +func TestServer_CreateKey(t *testing.T) { + const ( + method = "polygonid" + blockchain = "polygon" + network = "amoy" + BJJ = "BJJ" + ) + ctx := context.Background() + server := newTestServer(t, nil) + + iden, err := server.Services.identity.Create(ctx, "polygon-test", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) + require.NoError(t, err) + did, err := w3c.ParseDID(iden.Identifier) + require.NoError(t, err) + + handler := getHandler(ctx, server) + + type expected struct { + response CreateKeyResponseObject + httpCode int + } + + type testConfig struct { + name string + auth func() (string, string) + body CreateKeyRequest + expected expected + } + + for _, tc := range []testConfig{ + { + name: "No auth header", + auth: authWrong, + expected: expected{ + httpCode: http.StatusUnauthorized, + }, + }, + { + name: "should create a key", + auth: authOk, + body: CreateKeyRequest{ + KeyType: BJJ, + }, + expected: expected{ + httpCode: http.StatusCreated, + }, + }, + { + name: "should get an error", + auth: authOk, + body: CreateKeyRequest{ + KeyType: "wrong type", + }, + expected: expected{ + httpCode: http.StatusBadRequest, + response: CreateKey400JSONResponse{ + N400JSONResponse: N400JSONResponse{ + Message: "Invalid key type. BJJ Keys are supported", + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + rr := httptest.NewRecorder() + url := fmt.Sprintf("/v2/identities/%s/keys", did) + req, err := http.NewRequest(http.MethodPost, url, tests.JSONBody(t, tc.body)) + req.SetBasicAuth(tc.auth()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, tc.expected.httpCode, rr.Code) + + switch tc.expected.httpCode { + case http.StatusCreated: + var response CreateKey201JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.NotNil(t, response.KeyID) + case http.StatusBadRequest: + var response CreateKey400JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.EqualValues(t, tc.expected.response, response) + } + }) + } +} + +func TestServer_GetKey(t *testing.T) { + const ( + method = "polygonid" + blockchain = "polygon" + network = "amoy" + BJJ = "BJJ" + ) + ctx := context.Background() + server := newTestServer(t, nil) + + iden, err := server.Services.identity.Create(ctx, "polygon-test", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) + require.NoError(t, err) + did, err := w3c.ParseDID(iden.Identifier) + require.NoError(t, err) + + keyID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) + require.NoError(t, err) + + encodedKeyID := b64.StdEncoding.EncodeToString([]byte(keyID.ID)) + + handler := getHandler(ctx, server) + + type expected struct { + response GetKeyResponseObject + httpCode int + } + + type testConfig struct { + name string + auth func() (string, string) + KeyID string + expected expected + } + + for _, tc := range []testConfig{ + { + name: "No auth header", + auth: authWrong, + KeyID: encodedKeyID, + expected: expected{ + httpCode: http.StatusUnauthorized, + }, + }, + { + name: "should get a key", + auth: authOk, + KeyID: encodedKeyID, + expected: expected{ + httpCode: http.StatusOK, + }, + }, + { + name: "should get an error", + auth: authOk, + KeyID: "123", + expected: expected{ + httpCode: http.StatusBadRequest, + response: GetKey400JSONResponse{ + N400JSONResponse: N400JSONResponse{ + Message: "invalid key id", + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + rr := httptest.NewRecorder() + url := fmt.Sprintf("/v2/identities/%s/keys/%s", did, tc.KeyID) + req, err := http.NewRequest(http.MethodGet, url, nil) + req.SetBasicAuth(tc.auth()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, tc.expected.httpCode, rr.Code) + + switch tc.expected.httpCode { + case http.StatusCreated: + var response GetKey200JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.NotNil(t, response.KeyID) + assert.Equal(t, keyID, response.KeyID) + assert.NotNil(t, response.PublicKey) + assert.Equal(t, BJJ, response.KeyType) + assert.False(t, response.IsAuthCoreClaim) + case http.StatusBadRequest: + var response GetKey400JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.EqualValues(t, tc.expected.response, response) + } + }) + } +} + +func TestServer_GetKeys(t *testing.T) { + const ( + method = "polygonid" + blockchain = "polygon" + network = "amoy" + BJJ = "BJJ" + ) + ctx := context.Background() + server := newTestServer(t, nil) + + iden, err := server.Services.identity.Create(ctx, "polygon-test", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) + require.NoError(t, err) + did, err := w3c.ParseDID(iden.Identifier) + require.NoError(t, err) + + keyID1, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) + require.NoError(t, err) + + keyID2, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) + require.NoError(t, err) + + encodedKeyID1 := b64.StdEncoding.EncodeToString([]byte(keyID1.ID)) + encodedKeyID2 := b64.StdEncoding.EncodeToString([]byte(keyID2.ID)) + + handler := getHandler(ctx, server) + + type expected struct { + response GetKeysResponseObject + httpCode int + } + + type testConfig struct { + name string + auth func() (string, string) + expected expected + } + + for _, tc := range []testConfig{ + { + name: "No auth header", + auth: authWrong, + expected: expected{ + httpCode: http.StatusUnauthorized, + }, + }, + { + name: "should get the keys", + auth: authOk, + expected: expected{ + httpCode: http.StatusOK, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + rr := httptest.NewRecorder() + url := fmt.Sprintf("/v2/identities/%s/keys", did) + req, err := http.NewRequest(http.MethodGet, url, nil) + req.SetBasicAuth(tc.auth()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, tc.expected.httpCode, rr.Code) + + switch tc.expected.httpCode { + case http.StatusCreated: + var response GetKeys200JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.NotNil(t, response[0].KeyID) + assert.Equal(t, encodedKeyID1, response[0].KeyID) + assert.NotNil(t, response[0].PublicKey) + assert.Equal(t, BJJ, response[0].KeyType) + assert.False(t, response[0].IsAuthCoreClaim) + assert.NotNil(t, response[1].KeyID) + assert.Equal(t, encodedKeyID2, response[1].KeyID) + assert.NotNil(t, response[1].PublicKey) + assert.Equal(t, BJJ, response[1].KeyType) + assert.False(t, response[1].IsAuthCoreClaim) + case http.StatusBadRequest: + var response GetKeys400JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.EqualValues(t, tc.expected.response, response) + } + }) + } +} diff --git a/internal/api/main_test.go b/internal/api/main_test.go index 476cbdde8..a0f35ffd7 100644 --- a/internal/api/main_test.go +++ b/internal/api/main_test.go @@ -243,6 +243,7 @@ type servicex struct { schema ports.SchemaService links ports.LinkService qrs ports.QrStoreService + keyService ports.KeyService } type infra struct { @@ -298,7 +299,8 @@ func newTestServer(t *testing.T, st *db.Storage) *testServer { claimsService := services.NewClaim(repos.claims, identityService, qrService, mtService, repos.identityState, schemaLoader, st, cfg.ServerUrl, pubSub, ipfsGatewayURL, revocationStatusResolver, mediaTypeManager, cfg.UniversalLinks) accountService := services.NewAccountService(*networkResolver) linkService := services.NewLinkService(storage, claimsService, qrService, repos.claims, repos.links, repos.schemas, schemaLoader, repos.sessions, pubSub, identityService, *networkResolver, cfg.UniversalLinks) - server := NewServer(&cfg, identityService, accountService, connectionService, claimsService, qrService, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil, schemaService, linkService) + keyService := services.NewKeyService(keyStore, claimsService) + server := NewServer(&cfg, identityService, accountService, connectionService, claimsService, qrService, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil, schemaService, linkService, keyService) return &testServer{ Server: server, @@ -309,6 +311,7 @@ func newTestServer(t *testing.T, st *db.Storage) *testServer { links: linkService, qrs: qrService, schema: schemaService, + keyService: keyService, }, Infra: infra{ db: st, diff --git a/internal/api/server.go b/internal/api/server.go index 96b7de338..b284231ea 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -29,10 +29,11 @@ type Server struct { publisherGateway ports.Publisher qrService ports.QrStoreService schemaService ports.SchemaService + keyService ports.KeyService } // NewServer is a Server constructor -func NewServer(cfg *config.Configuration, identityService ports.IdentityService, accountService ports.AccountService, connectionsService ports.ConnectionService, claimsService ports.ClaimService, qrService ports.QrStoreService, publisherGateway ports.Publisher, packageManager *iden3comm.PackageManager, networkResolver network.Resolver, health *health.Status, schemaService ports.SchemaService, linkService ports.LinkService) *Server { +func NewServer(cfg *config.Configuration, identityService ports.IdentityService, accountService ports.AccountService, connectionsService ports.ConnectionService, claimsService ports.ClaimService, qrService ports.QrStoreService, publisherGateway ports.Publisher, packageManager *iden3comm.PackageManager, networkResolver network.Resolver, health *health.Status, schemaService ports.SchemaService, linkService ports.LinkService, keyService ports.KeyService) *Server { return &Server{ cfg: cfg, accountService: accountService, @@ -46,6 +47,7 @@ func NewServer(cfg *config.Configuration, identityService ports.IdentityService, packageManager: packageManager, qrService: qrService, schemaService: schemaService, + keyService: keyService, } } diff --git a/internal/core/domain/claim.go b/internal/core/domain/claim.go index affff5665..48ce63a56 100644 --- a/internal/core/domain/claim.go +++ b/internal/core/domain/claim.go @@ -1,6 +1,7 @@ package domain import ( + "bytes" "database/sql/driver" "encoding/hex" "encoding/json" @@ -12,6 +13,7 @@ import ( "github.com/iden3/go-circuits/v2" core "github.com/iden3/go-iden3-core/v2" "github.com/iden3/go-iden3-core/v2/w3c" + "github.com/iden3/go-iden3-crypto/babyjub" "github.com/iden3/go-schema-processor/v2/verifiable" "github.com/jackc/pgtype" @@ -124,6 +126,19 @@ func (c *CoreClaim) Get() *core.Claim { return (*core.Claim)(c) } +// HasPublicKey returns true if the claim has the given public key +func (c *CoreClaim) HasPublicKey(pubKey []byte) bool { + entry := c.Get() + bjjClaim := entry.RawSlotsAsInts() + + var authCoreClaimPublicKey babyjub.PublicKey + authCoreClaimPublicKey.X = bjjClaim[2] + authCoreClaimPublicKey.Y = bjjClaim[3] + + compPubKey := authCoreClaimPublicKey.Compress() + return bytes.Equal(pubKey, compPubKey[:]) +} + // ValidProof returns true if the claim has a valid proof func (c *Claim) ValidProof() bool { if !c.MtProof || c.SignatureProof.Status != pgtype.Null { // this second condition is for credentials that has both types of proofs diff --git a/internal/core/ports/claim_repository.go b/internal/core/ports/claim_repository.go index 8b5ec454a..6099d02f4 100644 --- a/internal/core/ports/claim_repository.go +++ b/internal/core/ports/claim_repository.go @@ -31,4 +31,5 @@ type ClaimRepository interface { GetClaimsIssuedForUser(ctx context.Context, conn db.Querier, identifier w3c.DID, userDID w3c.DID, linkID uuid.UUID) ([]*domain.Claim, error) GetClaimsOfAConnection(ctx context.Context, conn db.Querier, identifier w3c.DID, userDID w3c.DID) ([]*domain.Claim, error) GetByStateIDWithMTPProof(ctx context.Context, conn db.Querier, did *w3c.DID, state string) (claims []*domain.Claim, err error) + GetAuthCoreClaims(ctx context.Context, conn db.Querier, identifier *w3c.DID, schemaHash string) ([]*domain.Claim, error) } diff --git a/internal/core/ports/claim_service.go b/internal/core/ports/claim_service.go index 73264e40f..50a7708b5 100644 --- a/internal/core/ports/claim_service.go +++ b/internal/core/ports/claim_service.go @@ -227,4 +227,5 @@ type ClaimService interface { UpdateClaimsMTPAndState(ctx context.Context, currentState *domain.IdentityState) error Delete(ctx context.Context, id uuid.UUID) error GetByStateIDWithMTPProof(ctx context.Context, did *w3c.DID, state string) ([]*domain.Claim, error) + GetAuthCoreClaims(ctx context.Context, identifier *w3c.DID) ([]*domain.Claim, error) } diff --git a/internal/core/ports/identity_service.go b/internal/core/ports/identity_service.go index 39d814cf3..4b226b1bd 100644 --- a/internal/core/ports/identity_service.go +++ b/internal/core/ports/identity_service.go @@ -58,5 +58,5 @@ type IdentityService interface { GetFailedState(ctx context.Context, identifier w3c.DID) (*domain.IdentityState, error) PublishGenesisStateToRHS(ctx context.Context, did *w3c.DID) error UpdateIdentityDisplayName(ctx context.Context, did w3c.DID, displayName string) error - AddKey(ctx context.Context, did *w3c.DID) (*uint64, error) + AddKey(ctx context.Context, did *w3c.DID, keyID string) (uuid.UUID, error) } diff --git a/internal/core/ports/key_management_service.go b/internal/core/ports/key_management_service.go deleted file mode 100644 index 567f971c6..000000000 --- a/internal/core/ports/key_management_service.go +++ /dev/null @@ -1,12 +0,0 @@ -package ports - -import ( - "context" - "github.com/iden3/go-iden3-core/v2/w3c" - - "github.com/polygonid/sh-id-platform/internal/kms" -) - -type KeyManagementService interface { - CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) -} diff --git a/internal/core/ports/key_service.go b/internal/core/ports/key_service.go new file mode 100644 index 000000000..81a19605c --- /dev/null +++ b/internal/core/ports/key_service.go @@ -0,0 +1,28 @@ +package ports + +import ( + "context" + "errors" + + "github.com/iden3/go-iden3-core/v2/w3c" + + "github.com/polygonid/sh-id-platform/internal/kms" +) + +// ErrInvalidKeyType is returned when the key type is invalid +var ErrInvalidKeyType = errors.New("invalid key type") + +// KMSKey is the struct that represents a key +type KMSKey struct { + KeyID string + KeyType kms.KeyType + PublicKey string + HasAssociatedAuthCoreClaim bool +} + +// KeyService is the service that manages keys +type KeyService interface { + CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) + Get(ctx context.Context, did *w3c.DID, keyID string) (*KMSKey, error) + GetAll(ctx context.Context, did *w3c.DID) ([]*KMSKey, error) +} diff --git a/internal/core/services/claims.go b/internal/core/services/claims.go index a1b44a7c7..a830c65ea 100644 --- a/internal/core/services/claims.go +++ b/internal/core/services/claims.go @@ -587,6 +587,14 @@ func (c *claim) GetByStateIDWithMTPProof(ctx context.Context, did *w3c.DID, stat return c.icRepo.GetByStateIDWithMTPProof(ctx, c.storage.Pgx, did, state) } +func (c *claim) GetAuthCoreClaims(ctx context.Context, identifier *w3c.DID) ([]*domain.Claim, error) { + authHash, err := core.AuthSchemaHash.MarshalText() + if err != nil { + return nil, err + } + return c.icRepo.GetAuthCoreClaims(ctx, c.storage.Pgx, identifier, string(authHash)) +} + func (c *claim) revoke(ctx context.Context, did *w3c.DID, nonce uint64, description string, querier db.Querier) error { rID := new(big.Int).SetUint64(nonce) revocation := domain.Revocation{ diff --git a/internal/core/services/identity.go b/internal/core/services/identity.go index ee8f37c41..14bf02157 100644 --- a/internal/core/services/identity.go +++ b/internal/core/services/identity.go @@ -1,7 +1,6 @@ package services import ( - "bytes" "context" "crypto/ecdsa" "encoding/hex" @@ -65,6 +64,9 @@ var ( // ErrWrongDIDMetada - represents an error in the identity metadata ErrWrongDIDMetada = errors.New("wrong DID Metadata") + + // ErrSavingAuthCoreClaim - represents an error saving the claim + ErrSavingAuthCoreClaim = errors.New("error saving the AuthCoreClaim. Hash already exists") ) type identity struct { @@ -245,15 +247,6 @@ func (i *identity) GetKeyIDFromAuthClaim(ctx context.Context, authClaim *domain. return keyID, err } - entry := authClaim.CoreClaim.Get() - bjjClaim := entry.RawSlotsAsInts() - - var publicKey babyjub.PublicKey - publicKey.X = bjjClaim[2] - publicKey.Y = bjjClaim[3] - - compPubKey := publicKey.Compress() - keyIDs, err := i.kms.KeysByIdentity(ctx, *identity) if err != nil { return keyID, err @@ -268,13 +261,13 @@ func (i *identity) GetKeyIDFromAuthClaim(ctx context.Context, authClaim *domain. if err != nil { return keyID, err } - if bytes.Equal(pubKeyBytes, compPubKey[:]) { + if authClaim.CoreClaim.HasPublicKey(pubKeyBytes) { log.Info(ctx, "key found", "keyID", keyID) return keyID, nil } } - return keyID, errors.New("private key not found") + return keyID, errors.New("keyID not found") } func (i *identity) UpdateState(ctx context.Context, did w3c.DID) (*domain.IdentityState, error) { @@ -809,13 +802,24 @@ func (i *identity) createIdentity(ctx context.Context, tx db.Querier, hostURL st } // AddKey adds a new key to the identity -func (i *identity) AddKey(ctx context.Context, did *w3c.DID) (*uint64, error) { +func (i *identity) AddKey(ctx context.Context, did *w3c.DID, keyID string) (uuid.UUID, error) { revNonce, err := common.RandInt64() if err != nil { log.Error(ctx, "generating revocation nonce", "err", err) - return nil, fmt.Errorf("can't generate revocation nonce: %w", err) + return uuid.Nil, fmt.Errorf("can't generate revocation nonce: %w", err) } + var newAuthCoreClaimID uuid.UUID + var keyType kms.KeyType + if strings.Contains(keyID, "BJJ") { + keyType = kms.KeyTypeBabyJubJub + } else if strings.Contains(keyID, "ETH") { + keyType = kms.KeyTypeEthereum + } + kmsKeyID := kms.KeyID{ + ID: keyID, + Type: keyType, + } err = i.storage.Pgx.BeginFunc(ctx, func(tx pgx.Tx) error { identity, err := i.identityRepository.GetByID(ctx, tx, *did) @@ -841,13 +845,7 @@ func (i *identity) AddKey(ctx context.Context, did *w3c.DID) (*uint64, error) { return err } - // add bjj auth claim - bjjKey, err := i.kms.CreateKey(kms.KeyTypeBabyJubJub, did) - if err != nil { - return err - } - - bjjPubKey, err := bjjPubKey(i.kms, bjjKey) + bjjPubKey, err := bjjPubKey(i.kms, kmsKeyID) if err != nil { return err } @@ -876,17 +874,20 @@ func (i *identity) AddKey(ctx context.Context, did *w3c.DID) (*uint64, error) { return err } - _, err = i.claimsRepository.Save(ctx, tx, authClaimModel) + newAuthCoreClaimID, err = i.claimsRepository.Save(ctx, tx, authClaimModel) if err != nil { + if strings.Contains(err.Error(), "claims_identifier_issuer_index_hash_key") { + return ErrSavingAuthCoreClaim + } return errors.Join(err, errors.New("can't save auth claim")) } return nil }) if err != nil { - return nil, err + return uuid.Nil, err } - return common.ToPointer(revNonce), nil + return newAuthCoreClaimID, nil } func (i *identity) createEthIdentityFromKeyID(ctx context.Context, mts *domain.IdentityMerkleTrees, key *kms.KeyID, didOptions *ports.DIDCreationOptions, tx db.Querier) (*domain.Identity, *w3c.DID, error) { diff --git a/internal/core/services/key_management_service.go b/internal/core/services/key_management_service.go deleted file mode 100644 index 5957066a1..000000000 --- a/internal/core/services/key_management_service.go +++ /dev/null @@ -1,23 +0,0 @@ -package services - -import ( - "context" - "github.com/iden3/go-iden3-core/v2/w3c" - - "github.com/polygonid/sh-id-platform/internal/kms" -) - -type KeyManagementService struct { - kms kms.KeyProvider -} - -func NewKeyManagementService(kms kms.KeyProvider) *KeyManagementService { - return &KeyManagementService{ - kms: kms, - } -} - -// CreateKey creates a new key for the given DID -func (kms *KeyManagementService) CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) { - return kms.kms.New(did) -} diff --git a/internal/core/services/key_service.go b/internal/core/services/key_service.go new file mode 100644 index 000000000..f5cfd4811 --- /dev/null +++ b/internal/core/services/key_service.go @@ -0,0 +1,96 @@ +package services + +import ( + "context" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/iden3/go-iden3-core/v2/w3c" + + "github.com/polygonid/sh-id-platform/internal/core/ports" + "github.com/polygonid/sh-id-platform/internal/kms" + "github.com/polygonid/sh-id-platform/internal/log" +) + +// KeyService is the service that manages keys +type KeyService struct { + kms *kms.KMS + claimService ports.ClaimService +} + +// NewKeyService creates a new KeyService +func NewKeyService(kms *kms.KMS, claimService ports.ClaimService) ports.KeyService { + return &KeyService{ + kms: kms, + claimService: claimService, + } +} + +// CreateKey creates a new key for the given DID +func (ks *KeyService) CreateKey(_ context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) { + return ks.kms.CreateKey(keyType, did) +} + +// Get returns the public key for the given keyID +func (ks *KeyService) Get(ctx context.Context, did *w3c.DID, keyID string) (*ports.KMSKey, error) { + var keyType kms.KeyType + if strings.Contains(keyID, "BJJ") { + keyType = kms.KeyTypeBabyJubJub + } else if strings.Contains(keyID, "ETH") { + keyType = kms.KeyTypeEthereum + } else { + return nil, ports.ErrInvalidKeyType + } + + kmsKeyID := kms.KeyID{ + ID: keyID, + Type: keyType, + } + + publicKey, err := ks.kms.PublicKey(kmsKeyID) + if err != nil { + log.Error(ctx, "failed to get public key", "err", err) + return nil, err + } + + authCoreClaims, err := ks.claimService.GetAuthCoreClaims(ctx, did) + if err != nil { + log.Error(ctx, "failed to get auth core claims", "err", err) + return nil, err + } + + ok := false + for _, authCoreClaim := range authCoreClaims { + if authCoreClaim.CoreClaim.HasPublicKey(publicKey) { + ok = true + break + } + } + + return &ports.KMSKey{ + KeyID: keyID, + KeyType: keyType, + PublicKey: hexutil.Encode(publicKey), + HasAssociatedAuthCoreClaim: ok, + }, nil +} + +// GetAll returns all the keys for the given DID +func (ks *KeyService) GetAll(ctx context.Context, did *w3c.DID) ([]*ports.KMSKey, error) { + keyIDs, err := ks.kms.KeysByIdentity(ctx, *did) + if err != nil { + log.Error(ctx, "failed to get keys", "err", err) + return nil, err + } + + keys := make([]*ports.KMSKey, len(keyIDs)) + for i, keyID := range keyIDs { + key, err := ks.Get(ctx, did, keyID.ID) + if err != nil { + log.Error(ctx, "failed to get key", "err", err) + return nil, err + } + keys[i] = key + } + return keys, nil +} diff --git a/internal/repositories/claim.go b/internal/repositories/claim.go index 22e6fe851..69541f288 100644 --- a/internal/repositories/claim.go +++ b/internal/repositories/claim.go @@ -1099,3 +1099,61 @@ func (c *claim) GetByStateIDWithMTPProof(ctx context.Context, conn db.Querier, d return claims, nil } + +// GetAuthCoreClaims returns all the core claims for the given identifier and schema hash +func (c *claim) GetAuthCoreClaims(ctx context.Context, conn db.Querier, identifier *w3c.DID, schemaHash string) ([]*domain.Claim, error) { + rows, err := conn.Query(ctx, + `SELECT claims.id, + issuer, + schema_hash, + schema_type, + schema_url, + other_identifier, + expiration, + updatable, + claims.version, + rev_nonce, + mtp_proof, + signature_proof, + data, + claims.identifier, + identity_state, + credential_status, + revoked, + core_claim + FROM claims + WHERE claims.identifier=$1 + AND ( claims.other_identifier = $1 or claims.other_identifier = '') + AND claims.schema_hash = $2`, identifier.String(), schemaHash) + if err != nil { + return nil, err + } + + claims := make([]*domain.Claim, 0) + for rows.Next() { + var claim domain.Claim + err := rows.Scan(&claim.ID, + &claim.Issuer, + &claim.SchemaHash, + &claim.SchemaType, + &claim.SchemaURL, + &claim.OtherIdentifier, + &claim.Expiration, + &claim.Updatable, + &claim.Version, + &claim.RevNonce, + &claim.MTPProof, + &claim.SignatureProof, + &claim.Data, + &claim.Identifier, + &claim.IdentityState, + &claim.CredentialStatus, + &claim.Revoked, + &claim.CoreClaim) + if err != nil { + return nil, err + } + claims = append(claims, &claim) + } + return claims, nil +} From f1ae13c7e3319a65d33c07450b009f746aca57bb Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Fri, 29 Nov 2024 12:21:30 -0300 Subject: [PATCH 06/62] chore: add delete key endpoint --- api/api.yaml | 51 +++---- cmd/platform/main.go | 2 +- internal/api/api.gen.go | 127 +++++++++++++++++ internal/api/{key.go => keys.go} | 34 +++++ internal/api/{key_test.go => keys_test.go} | 0 internal/api/main_test.go | 2 +- internal/core/ports/key_service.go | 8 +- internal/core/services/key.go | 155 +++++++++++++++++++++ internal/core/services/key_service.go | 96 ------------- 9 files changed, 352 insertions(+), 123 deletions(-) rename internal/api/{key.go => keys.go} (74%) rename internal/api/{key_test.go => keys_test.go} (100%) create mode 100644 internal/core/services/key.go delete mode 100644 internal/core/services/key_service.go diff --git a/api/api.yaml b/api/api.yaml index e05cb4af7..e204228dd 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -1427,30 +1427,33 @@ paths: $ref: '#/components/responses/404' '500': $ref: '#/components/responses/500' -# delete: -# summary: Delete Key -# operationId: DeleteKey -# description: Remove a specific key for the provided identity. -# tags: -# - Identity -# security: -# - basicAuth: [ ] -# parameters: -# - $ref: '#/components/parameters/pathIdentifier' -# - $ref: '#/components/parameters/keyID' -# responses: -# '200': -# description: Key deleted -# content: -# application/json: -# schema: -# $ref: '#/components/schemas/GenericMessage' -# '400': -# $ref: '#/components/responses/400' -# '401': -# $ref: '#/components/responses/401' -# '500': -# $ref: '#/components/responses/500' + delete: + summary: Delete Key + operationId: DeleteKey + description: Remove a specific key for the provided identity. + tags: + - Identity + security: + - basicAuth: [ ] + parameters: + - $ref: '#/components/parameters/pathIdentifier2' + - $ref: '#/components/parameters/pathKeyID' + responses: + '200': + description: Key deleted + content: + application/json: + schema: + $ref: '#/components/schemas/GenericMessage' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '500': + $ref: '#/components/responses/500' + + + components: securitySchemes: basicAuth: diff --git a/cmd/platform/main.go b/cmd/platform/main.go index 8dd00f109..303e93b05 100644 --- a/cmd/platform/main.go +++ b/cmd/platform/main.go @@ -151,7 +151,7 @@ func main() { proofService := services.NewProver(circuitsLoaderService) schemaService := services.NewSchema(schemaRepository, schemaLoader) linkService := services.NewLinkService(storage, claimsService, qrService, claimsRepository, linkRepository, schemaRepository, schemaLoader, sessionRepository, ps, identityService, *networkResolver, cfg.UniversalLinks) - keyService := services.NewKeyService(keyStore, claimsService) + keyService := services.NewKey(keyStore, claimsService) transactionService, err := gateways.NewTransaction(*networkResolver) if err != nil { log.Error(ctx, "error creating transaction service", "err", err) diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 2a690ac1f..30acbb9ee 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -943,6 +943,9 @@ type ServerInterface interface { // Create a Key // (POST /v2/identities/{identifier}/keys) CreateKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) + // Delete Key + // (DELETE /v2/identities/{identifier}/keys/{keyID}) + DeleteKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) // Get a Key // (GET /v2/identities/{identifier}/keys/{keyID}) GetKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) @@ -1180,6 +1183,12 @@ func (_ Unimplemented) CreateKey(w http.ResponseWriter, r *http.Request, identif w.WriteHeader(http.StatusNotImplemented) } +// Delete Key +// (DELETE /v2/identities/{identifier}/keys/{keyID}) +func (_ Unimplemented) DeleteKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) { + w.WriteHeader(http.StatusNotImplemented) +} + // Get a Key // (GET /v2/identities/{identifier}/keys/{keyID}) func (_ Unimplemented) GetKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) { @@ -2471,6 +2480,46 @@ func (siw *ServerInterfaceWrapper) CreateKey(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r) } +// DeleteKey operation middleware +func (siw *ServerInterfaceWrapper) DeleteKey(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "identifier" ------------- + var identifier PathIdentifier2 + + err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) + return + } + + // ------------- Path parameter "keyID" ------------- + var keyID PathKeyID + + err = runtime.BindStyledParameterWithOptions("simple", "keyID", chi.URLParam(r, "keyID"), &keyID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "keyID", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteKey(w, r, identifier, keyID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // GetKey operation middleware func (siw *ServerInterfaceWrapper) GetKey(w http.ResponseWriter, r *http.Request) { @@ -3086,6 +3135,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/v2/identities/{identifier}/keys", wrapper.CreateKey) }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/v2/identities/{identifier}/keys/{keyID}", wrapper.DeleteKey) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/identities/{identifier}/keys/{keyID}", wrapper.GetKey) }) @@ -4526,6 +4578,51 @@ func (response CreateKey500JSONResponse) VisitCreateKeyResponse(w http.ResponseW return json.NewEncoder(w).Encode(response) } +type DeleteKeyRequestObject struct { + Identifier PathIdentifier2 `json:"identifier"` + KeyID PathKeyID `json:"keyID"` +} + +type DeleteKeyResponseObject interface { + VisitDeleteKeyResponse(w http.ResponseWriter) error +} + +type DeleteKey200JSONResponse GenericMessage + +func (response DeleteKey200JSONResponse) VisitDeleteKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteKey400JSONResponse struct{ N400JSONResponse } + +func (response DeleteKey400JSONResponse) VisitDeleteKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteKey401JSONResponse struct{ N401JSONResponse } + +func (response DeleteKey401JSONResponse) VisitDeleteKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteKey500JSONResponse struct{ N500JSONResponse } + +func (response DeleteKey500JSONResponse) VisitDeleteKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type GetKeyRequestObject struct { Identifier PathIdentifier2 `json:"identifier"` KeyID PathKeyID `json:"keyID"` @@ -5098,6 +5195,9 @@ type StrictServerInterface interface { // Create a Key // (POST /v2/identities/{identifier}/keys) CreateKey(ctx context.Context, request CreateKeyRequestObject) (CreateKeyResponseObject, error) + // Delete Key + // (DELETE /v2/identities/{identifier}/keys/{keyID}) + DeleteKey(ctx context.Context, request DeleteKeyRequestObject) (DeleteKeyResponseObject, error) // Get a Key // (GET /v2/identities/{identifier}/keys/{keyID}) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKeyResponseObject, error) @@ -6118,6 +6218,33 @@ func (sh *strictHandler) CreateKey(w http.ResponseWriter, r *http.Request, ident } } +// DeleteKey operation middleware +func (sh *strictHandler) DeleteKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) { + var request DeleteKeyRequestObject + + request.Identifier = identifier + request.KeyID = keyID + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.DeleteKey(ctx, request.(DeleteKeyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeleteKey") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(DeleteKeyResponseObject); ok { + if err := validResponse.VisitDeleteKeyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // GetKey operation middleware func (sh *strictHandler) GetKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) { var request GetKeyRequestObject diff --git a/internal/api/key.go b/internal/api/keys.go similarity index 74% rename from internal/api/key.go rename to internal/api/keys.go index 2b63344fc..08c1e8346 100644 --- a/internal/api/key.go +++ b/internal/api/keys.go @@ -100,3 +100,37 @@ func (s *Server) GetKeys(ctx context.Context, request GetKeysRequestObject) (Get } return keysResponse, nil } + +// DeleteKey is the handler for the DELETE /keys/{keyID} endpoint. +func (s *Server) DeleteKey(ctx context.Context, request DeleteKeyRequestObject) (DeleteKeyResponseObject, error) { + decodedKeyID, err := b64.StdEncoding.DecodeString(request.KeyID) + if err != nil { + log.Error(ctx, "delete key. Decoding key id", "err", err) + return DeleteKey400JSONResponse{ + N400JSONResponse{ + Message: "invalid key id", + }, + }, nil + } + + err = s.keyService.Delete(ctx, request.Identifier.did(), string(decodedKeyID)) + if err != nil { + if errors.Is(err, ports.ErrAuthCoreClaimNotRevoked) { + log.Error(ctx, "delete key. Auth core claim not revoked", "err", err) + return DeleteKey400JSONResponse{ + N400JSONResponse{ + Message: "associated auth core claim is not revoked", + }, + }, nil + } + + log.Error(ctx, "delete key. Deleting key", "err", err) + return DeleteKey500JSONResponse{ + N500JSONResponse{ + Message: "internal error", + }, + }, nil + } + + return DeleteKey200JSONResponse{}, nil +} diff --git a/internal/api/key_test.go b/internal/api/keys_test.go similarity index 100% rename from internal/api/key_test.go rename to internal/api/keys_test.go diff --git a/internal/api/main_test.go b/internal/api/main_test.go index a0f35ffd7..fb632867b 100644 --- a/internal/api/main_test.go +++ b/internal/api/main_test.go @@ -299,7 +299,7 @@ func newTestServer(t *testing.T, st *db.Storage) *testServer { claimsService := services.NewClaim(repos.claims, identityService, qrService, mtService, repos.identityState, schemaLoader, st, cfg.ServerUrl, pubSub, ipfsGatewayURL, revocationStatusResolver, mediaTypeManager, cfg.UniversalLinks) accountService := services.NewAccountService(*networkResolver) linkService := services.NewLinkService(storage, claimsService, qrService, repos.claims, repos.links, repos.schemas, schemaLoader, repos.sessions, pubSub, identityService, *networkResolver, cfg.UniversalLinks) - keyService := services.NewKeyService(keyStore, claimsService) + keyService := services.NewKey(keyStore, claimsService) server := NewServer(&cfg, identityService, accountService, connectionService, claimsService, qrService, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil, schemaService, linkService, keyService) return &testServer{ diff --git a/internal/core/ports/key_service.go b/internal/core/ports/key_service.go index 81a19605c..6efdddc69 100644 --- a/internal/core/ports/key_service.go +++ b/internal/core/ports/key_service.go @@ -10,7 +10,12 @@ import ( ) // ErrInvalidKeyType is returned when the key type is invalid -var ErrInvalidKeyType = errors.New("invalid key type") +var ( + // ErrInvalidKeyType is returned when the key type is invalid + ErrInvalidKeyType = errors.New("invalid key type") + // ErrAuthCoreClaimNotRevoked is returned when the associated auth core claim is not revoked + ErrAuthCoreClaimNotRevoked = errors.New("associated auth core claim not revoked") +) // KMSKey is the struct that represents a key type KMSKey struct { @@ -25,4 +30,5 @@ type KeyService interface { CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) Get(ctx context.Context, did *w3c.DID, keyID string) (*KMSKey, error) GetAll(ctx context.Context, did *w3c.DID) ([]*KMSKey, error) + Delete(ctx context.Context, did *w3c.DID, keyID string) error } diff --git a/internal/core/services/key.go b/internal/core/services/key.go new file mode 100644 index 000000000..738937d77 --- /dev/null +++ b/internal/core/services/key.go @@ -0,0 +1,155 @@ +package services + +import ( + "context" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/iden3/go-iden3-core/v2/w3c" + + "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/kms" + "github.com/polygonid/sh-id-platform/internal/log" +) + +// Key is the service that manages keys +type Key struct { + kms *kms.KMS + claimService ports.ClaimService +} + +// NewKey creates a new Key +func NewKey(kms *kms.KMS, claimService ports.ClaimService) ports.KeyService { + return &Key{ + kms: kms, + claimService: claimService, + } +} + +// CreateKey creates a new key for the given DID +func (ks *Key) CreateKey(_ context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) { + return ks.kms.CreateKey(keyType, did) +} + +// Get returns the public key for the given keyID +func (ks *Key) Get(ctx context.Context, did *w3c.DID, keyID string) (*ports.KMSKey, error) { + keyType, err := getKeyType(keyID) + if err != nil { + log.Error(ctx, "failed to get key type", "err", err) + return nil, err + } + + publicKey, err := ks.getPublicKey(ctx, keyID) + if err != nil { + log.Error(ctx, "failed to get public key", "err", err) + return nil, err + } + + authCoreClaim, err := getAuthCoreClaim(ctx, ks.claimService, did, publicKey) + if err != nil { + log.Error(ctx, "failed to check if key has associated auth core claim", "err", err) + return nil, err + } + return &ports.KMSKey{ + KeyID: keyID, + KeyType: keyType, + PublicKey: hexutil.Encode(publicKey), + HasAssociatedAuthCoreClaim: authCoreClaim != nil, + }, nil +} + +// GetAll returns all the keys for the given DID +func (ks *Key) GetAll(ctx context.Context, did *w3c.DID) ([]*ports.KMSKey, error) { + keyIDs, err := ks.kms.KeysByIdentity(ctx, *did) + if err != nil { + log.Error(ctx, "failed to get keys", "err", err) + return nil, err + } + + keys := make([]*ports.KMSKey, len(keyIDs)) + for i, keyID := range keyIDs { + key, err := ks.Get(ctx, did, keyID.ID) + if err != nil { + log.Error(ctx, "failed to get key", "err", err) + return nil, err + } + keys[i] = key + } + return keys, nil +} + +// Delete deletes the key with the given keyID +func (ks *Key) Delete(ctx context.Context, did *w3c.DID, keyID string) error { + publicKey, err := ks.getPublicKey(ctx, keyID) + if err != nil { + log.Error(ctx, "failed to get public key", "err", err) + return err + } + authCoreClaim, err := getAuthCoreClaim(ctx, ks.claimService, did, publicKey) + if err != nil { + log.Error(ctx, "failed to check if key has associated auth core claim", "err", err) + return err + } + + if authCoreClaim != nil { + log.Info(ctx, "can not be deleted because it has an associated auth core claim. Have to check revocation status") + revStatus, err := ks.claimService.GetRevocationStatus(ctx, *did, uint64(authCoreClaim.RevNonce)) + if err != nil { + log.Error(ctx, "failed to get revocation status", "err", err) + return err + } + + if revStatus != nil && !revStatus.MTP.Existence { + log.Info(ctx, "Auth core claim is non revoked. Can not be deleted") + return ports.ErrAuthCoreClaimNotRevoked + } + } + log.Info(ctx, "can be deleted") + return nil +} + +// getPublicKey returns the public key for the given keyID +func (ks *Key) getPublicKey(ctx context.Context, keyID string) ([]byte, error) { + keyType, err := getKeyType(keyID) + if err != nil { + log.Error(ctx, "failed to get key type", "err", err) + return nil, err + } + kmsKeyID := kms.KeyID{ + ID: keyID, + Type: keyType, + } + + return ks.kms.PublicKey(kmsKeyID) +} + +// getKeyType returns the key type for the given keyID +func getKeyType(keyID string) (kms.KeyType, error) { + var keyType kms.KeyType + if strings.Contains(keyID, "BJJ") { + keyType = kms.KeyTypeBabyJubJub + } else if strings.Contains(keyID, "ETH") { + keyType = kms.KeyTypeEthereum + } else { + return keyType, ports.ErrInvalidKeyType + } + + return keyType, nil +} + +// getAuthCoreClaim returns the keyID for the given DID and keyType +func getAuthCoreClaim(ctx context.Context, claimService ports.ClaimService, did *w3c.DID, publicKey []byte) (*domain.Claim, error) { + authCoreClaims, err := claimService.GetAuthCoreClaims(ctx, did) + if err != nil { + log.Error(ctx, "failed to get auth core claims", "err", err) + return nil, err + } + for _, authCoreClaim := range authCoreClaims { + if authCoreClaim.CoreClaim.HasPublicKey(publicKey) { + return authCoreClaim, nil + } + } + + return nil, nil +} diff --git a/internal/core/services/key_service.go b/internal/core/services/key_service.go deleted file mode 100644 index f5cfd4811..000000000 --- a/internal/core/services/key_service.go +++ /dev/null @@ -1,96 +0,0 @@ -package services - -import ( - "context" - "strings" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/iden3/go-iden3-core/v2/w3c" - - "github.com/polygonid/sh-id-platform/internal/core/ports" - "github.com/polygonid/sh-id-platform/internal/kms" - "github.com/polygonid/sh-id-platform/internal/log" -) - -// KeyService is the service that manages keys -type KeyService struct { - kms *kms.KMS - claimService ports.ClaimService -} - -// NewKeyService creates a new KeyService -func NewKeyService(kms *kms.KMS, claimService ports.ClaimService) ports.KeyService { - return &KeyService{ - kms: kms, - claimService: claimService, - } -} - -// CreateKey creates a new key for the given DID -func (ks *KeyService) CreateKey(_ context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) { - return ks.kms.CreateKey(keyType, did) -} - -// Get returns the public key for the given keyID -func (ks *KeyService) Get(ctx context.Context, did *w3c.DID, keyID string) (*ports.KMSKey, error) { - var keyType kms.KeyType - if strings.Contains(keyID, "BJJ") { - keyType = kms.KeyTypeBabyJubJub - } else if strings.Contains(keyID, "ETH") { - keyType = kms.KeyTypeEthereum - } else { - return nil, ports.ErrInvalidKeyType - } - - kmsKeyID := kms.KeyID{ - ID: keyID, - Type: keyType, - } - - publicKey, err := ks.kms.PublicKey(kmsKeyID) - if err != nil { - log.Error(ctx, "failed to get public key", "err", err) - return nil, err - } - - authCoreClaims, err := ks.claimService.GetAuthCoreClaims(ctx, did) - if err != nil { - log.Error(ctx, "failed to get auth core claims", "err", err) - return nil, err - } - - ok := false - for _, authCoreClaim := range authCoreClaims { - if authCoreClaim.CoreClaim.HasPublicKey(publicKey) { - ok = true - break - } - } - - return &ports.KMSKey{ - KeyID: keyID, - KeyType: keyType, - PublicKey: hexutil.Encode(publicKey), - HasAssociatedAuthCoreClaim: ok, - }, nil -} - -// GetAll returns all the keys for the given DID -func (ks *KeyService) GetAll(ctx context.Context, did *w3c.DID) ([]*ports.KMSKey, error) { - keyIDs, err := ks.kms.KeysByIdentity(ctx, *did) - if err != nil { - log.Error(ctx, "failed to get keys", "err", err) - return nil, err - } - - keys := make([]*ports.KMSKey, len(keyIDs)) - for i, keyID := range keyIDs { - key, err := ks.Get(ctx, did, keyID.ID) - if err != nil { - log.Error(ctx, "failed to get key", "err", err) - return nil, err - } - keys[i] = key - } - return keys, nil -} From c0de51673ac3583b0e030f34d4529c4c884195da Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Mon, 2 Dec 2024 09:46:46 -0300 Subject: [PATCH 07/62] chore: add delete key endpoint --- api/api.yaml | 2 + internal/api/api.gen.go | 9 ++ internal/api/keys.go | 21 +++- internal/api/keys_test.go | 108 ++++++++++++++++++ internal/core/ports/key_service.go | 2 + internal/core/services/key.go | 13 ++- internal/kms/aws_kms_eth_key_provider.go | 8 ++ internal/kms/aws_secret_storage_provider.go | 32 ++++++ .../kms/aws_secret_storage_provider_test.go | 85 ++++++++++++++ internal/kms/file_storage_manager.go | 41 +++++++ internal/kms/file_storage_manager_test.go | 85 ++++++++++++++ internal/kms/kms.go | 14 +++ internal/kms/local_bjj_key_provider.go | 11 ++ internal/kms/local_bjj_key_provider_test.go | 46 +++++++- internal/kms/local_eth_key_provider.go | 4 + internal/kms/vaultPluginIden3KeyProvider.go | 5 + internal/kms/vault_bjj_key_provider.go | 4 + internal/kms/vault_eth_key_provider.go | 5 + 18 files changed, 489 insertions(+), 6 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index e204228dd..3a8654f58 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -1447,6 +1447,8 @@ paths: $ref: '#/components/schemas/GenericMessage' '400': $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' '401': $ref: '#/components/responses/401' '500': diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 30acbb9ee..694a02f37 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -4614,6 +4614,15 @@ func (response DeleteKey401JSONResponse) VisitDeleteKeyResponse(w http.ResponseW return json.NewEncoder(w).Encode(response) } +type DeleteKey404JSONResponse struct{ N404JSONResponse } + +func (response DeleteKey404JSONResponse) VisitDeleteKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + type DeleteKey500JSONResponse struct{ N500JSONResponse } func (response DeleteKey500JSONResponse) VisitDeleteKeyResponse(w http.ResponseWriter) error { diff --git a/internal/api/keys.go b/internal/api/keys.go index 08c1e8346..81805c451 100644 --- a/internal/api/keys.go +++ b/internal/api/keys.go @@ -60,6 +60,14 @@ func (s *Server) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKe }, nil } + if errors.Is(err, ports.ErrKeyNotFound) { + return GetKey404JSONResponse{ + N404JSONResponse{ + Message: "key not found", + }, + }, nil + } + return GetKey500JSONResponse{ N500JSONResponse{ Message: "internal error", @@ -124,6 +132,15 @@ func (s *Server) DeleteKey(ctx context.Context, request DeleteKeyRequestObject) }, nil } + if errors.Is(err, ports.ErrKeyNotFound) { + log.Error(ctx, "delete key. Key not found", "err", err) + return DeleteKey404JSONResponse{ + N404JSONResponse{ + Message: "key not found", + }, + }, nil + } + log.Error(ctx, "delete key. Deleting key", "err", err) return DeleteKey500JSONResponse{ N500JSONResponse{ @@ -132,5 +149,7 @@ func (s *Server) DeleteKey(ctx context.Context, request DeleteKeyRequestObject) }, nil } - return DeleteKey200JSONResponse{}, nil + return DeleteKey200JSONResponse{ + Message: "key deleted", + }, nil } diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index 64a39adf8..7f43cd6d4 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -280,3 +280,111 @@ func TestServer_GetKeys(t *testing.T) { }) } } + +func TestServer_DeleteKey(t *testing.T) { + const ( + method = "polygonid" + blockchain = "polygon" + network = "amoy" + BJJ = "BJJ" + ) + ctx := context.Background() + server := newTestServer(t, nil) + + iden, err := server.Services.identity.Create(ctx, "polygon-test", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) + require.NoError(t, err) + did, err := w3c.ParseDID(iden.Identifier) + require.NoError(t, err) + + keyID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) + require.NoError(t, err) + + encodedKeyID := b64.StdEncoding.EncodeToString([]byte(keyID.ID)) + + keyIDForAuthCoreClaimID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) + require.NoError(t, err) + + encodedKeyIDForAuthCoreClaimID := b64.StdEncoding.EncodeToString([]byte(keyIDForAuthCoreClaimID.ID)) + + _, err = server.Services.identity.AddKey(ctx, did, keyIDForAuthCoreClaimID.ID) + require.NoError(t, err) + + handler := getHandler(ctx, server) + + type expected struct { + response DeleteKeyResponseObject + httpCode int + } + + type testConfig struct { + name string + auth func() (string, string) + KeyID string + expected expected + } + + for _, tc := range []testConfig{ + { + name: "No auth header", + auth: authWrong, + KeyID: encodedKeyID, + expected: expected{ + httpCode: http.StatusUnauthorized, + }, + }, + { + name: "should delete a key", + auth: authOk, + KeyID: encodedKeyID, + expected: expected{ + httpCode: http.StatusOK, + }, + }, + { + name: "should get an error", + auth: authOk, + KeyID: "123", + expected: expected{ + httpCode: http.StatusBadRequest, + response: DeleteKey400JSONResponse{ + N400JSONResponse: N400JSONResponse{ + Message: "invalid key id", + }, + }, + }, + }, + { + name: "should get an error - key is an auth core claim", + auth: authOk, + KeyID: encodedKeyIDForAuthCoreClaimID, + expected: expected{ + httpCode: http.StatusBadRequest, + response: DeleteKey400JSONResponse{ + N400JSONResponse: N400JSONResponse{ + Message: "associated auth core claim is not revoked", + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + rr := httptest.NewRecorder() + url := fmt.Sprintf("/v2/identities/%s/keys/%s", did, tc.KeyID) + req, err := http.NewRequest(http.MethodDelete, url, nil) + req.SetBasicAuth(tc.auth()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, tc.expected.httpCode, rr.Code) + + switch tc.expected.httpCode { + case http.StatusCreated: + var response DeleteKey200JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + case http.StatusBadRequest: + var response DeleteKey400JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.EqualValues(t, tc.expected.response, response) + } + }) + } +} diff --git a/internal/core/ports/key_service.go b/internal/core/ports/key_service.go index 6efdddc69..f38f3d33a 100644 --- a/internal/core/ports/key_service.go +++ b/internal/core/ports/key_service.go @@ -15,6 +15,8 @@ var ( ErrInvalidKeyType = errors.New("invalid key type") // ErrAuthCoreClaimNotRevoked is returned when the associated auth core claim is not revoked ErrAuthCoreClaimNotRevoked = errors.New("associated auth core claim not revoked") + // ErrKeyNotFound is returned when the key is not found + ErrKeyNotFound = errors.New("key not found") ) // KMSKey is the struct that represents a key diff --git a/internal/core/services/key.go b/internal/core/services/key.go index 738937d77..fa9c1e678 100644 --- a/internal/core/services/key.go +++ b/internal/core/services/key.go @@ -43,7 +43,7 @@ func (ks *Key) Get(ctx context.Context, did *w3c.DID, keyID string) (*ports.KMSK publicKey, err := ks.getPublicKey(ctx, keyID) if err != nil { log.Error(ctx, "failed to get public key", "err", err) - return nil, err + return nil, ports.ErrKeyNotFound } authCoreClaim, err := getAuthCoreClaim(ctx, ks.claimService, did, publicKey) @@ -105,8 +105,15 @@ func (ks *Key) Delete(ctx context.Context, did *w3c.DID, keyID string) error { return ports.ErrAuthCoreClaimNotRevoked } } - log.Info(ctx, "can be deleted") - return nil + keyType, err := getKeyType(keyID) + if err != nil { + log.Error(ctx, "failed to get key type", "err", err) + } + kmsKeyID := kms.KeyID{ + ID: keyID, + Type: keyType, + } + return ks.kms.Delete(ctx, kmsKeyID) } // getPublicKey returns the public key for the given keyID diff --git a/internal/kms/aws_kms_eth_key_provider.go b/internal/kms/aws_kms_eth_key_provider.go index 3494c162f..71bc2d333 100644 --- a/internal/kms/aws_kms_eth_key_provider.go +++ b/internal/kms/aws_kms_eth_key_provider.go @@ -228,6 +228,14 @@ func (awsKeyProv *awsKmsEthKeyProvider) ListByIdentity(ctx context.Context, iden return keysToReturn, nil } +func (awsKeyProv *awsKmsEthKeyProvider) Delete(ctx context.Context, keyID KeyID) error { + _, err := awsKeyProv.kmsClient.ScheduleKeyDeletion(ctx, &kms.ScheduleKeyDeletionInput{ + KeyId: aws.String(keyID.ID), + PendingWindowInDays: aws.Int32(1), + }) + return err +} + // createAlias creates alias for key func (awsKeyProv *awsKmsEthKeyProvider) createAlias(ctx context.Context, aliasName, targetKeyId string) error { input := &kms.CreateAliasInput{ diff --git a/internal/kms/aws_secret_storage_provider.go b/internal/kms/aws_secret_storage_provider.go index 57bae4e1a..c1ca1fdb3 100644 --- a/internal/kms/aws_secret_storage_provider.go +++ b/internal/kms/aws_secret_storage_provider.go @@ -166,3 +166,35 @@ func (a *awsSecretStorageProvider) searchPrivateKey(ctx context.Context, keyID K } return secretValue.PrivateKey, nil } + +func (a *awsSecretStorageProvider) deleteKeyMaterial(ctx context.Context, keyID KeyID) error { + encodedSecretName := base64.StdEncoding.EncodeToString([]byte(keyID.ID)) + input := &secretsmanager.DeleteSecretInput{ + SecretId: aws.String(encodedSecretName), + ForceDeleteWithoutRecovery: aws.Bool(true), + } + _, err := a.secretManager.DeleteSecret(ctx, input) + return err +} + +func (a *awsSecretStorageProvider) getKeyMaterial(ctx context.Context, keyID KeyID) (map[string]string, error) { + encodedSecretName := base64.StdEncoding.EncodeToString([]byte(keyID.ID)) + input := &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(encodedSecretName), + } + result, err := a.secretManager.GetSecretValue(ctx, input) + if err != nil { + log.Error(ctx, "error getting secret value", "err", err) + return nil, errors.New("error getting secret value from AWS") + } + + var secretValue secretStorageProviderKeyMaterial + if err := json.Unmarshal([]byte(aws.ToString(result.SecretString)), &secretValue); err != nil { + return nil, err + } + + return map[string]string{ + jsonKeyType: secretValue.KeyType, + jsonKeyData: secretValue.PrivateKey, + }, nil +} diff --git a/internal/kms/aws_secret_storage_provider_test.go b/internal/kms/aws_secret_storage_provider_test.go index 3c074ca1d..afc656c2f 100644 --- a/internal/kms/aws_secret_storage_provider_test.go +++ b/internal/kms/aws_secret_storage_provider_test.go @@ -204,3 +204,88 @@ func Test_searchPrivateKey(t *testing.T) { assert.Equal(t, privateKey, privateKeyFromStore) }) } + +func Test_getKeyMaterial(t *testing.T) { + ctx := context.Background() + awsStorageProvider, err := NewAwsSecretStorageProvider(ctx, AwsSecretStorageProviderConfig{ + AccessKey: "access_key", + SecretKey: "secret_key", + Region: "local", + URL: "http://localhost:4566", + }) + require.NoError(t, err) + + t.Run("should get key material for bjj", func(t *testing.T) { + did := randomDID(t) + privateKey := "9d7abdd5a43573ab9b623c50b9fc8f4357329d3009fe0fc22c8931161d98a03d" + id := getKeyID(&did, KeyTypeBabyJubJub, "BJJ:2290140c920a31a596937095f18a9ae15c1fe7091091be485f353968a4310380") + err := awsStorageProvider.SaveKeyMaterial(ctx, map[string]string{ + jsonKeyType: string(KeyTypeBabyJubJub), + jsonKeyData: privateKey, + }, id) + assert.NoError(t, err) + + keyMaterial, err := awsStorageProvider.getKeyMaterial(ctx, KeyID{ + Type: babyjubjub, + ID: id, + }) + require.NoError(t, err) + assert.Equal(t, map[string]string{ + jsonKeyType: string(babyjubjub), + jsonKeyData: privateKey, + }, keyMaterial) + }) + + t.Run("should get an error for bjj", func(t *testing.T) { + _, err := awsStorageProvider.getKeyMaterial(ctx, KeyID{ + Type: babyjubjub, + ID: "wrong_id", + }) + require.Error(t, err) + }) +} + +func Test_deleteKeyMaterial(t *testing.T) { + ctx := context.Background() + awsStorageProvider, err := NewAwsSecretStorageProvider(ctx, AwsSecretStorageProviderConfig{ + AccessKey: "access_key", + SecretKey: "secret_key", + Region: "local", + URL: "http://localhost:4566", + }) + require.NoError(t, err) + + t.Run("should delete key material for bjj", func(t *testing.T) { + did := randomDID(t) + privateKey := "9d7abdd5a43573ab9b623c50b9fc8f4357329d3009fe0fc22c8931161d98a03d" + id := getKeyID(&did, KeyTypeBabyJubJub, "BJJ:2290140c920a31a596937095f18a9ae15c1fe7091091be485f353968a4310380") + err := awsStorageProvider.SaveKeyMaterial(ctx, map[string]string{ + jsonKeyType: string(KeyTypeBabyJubJub), + jsonKeyData: privateKey, + }, id) + assert.NoError(t, err) + + keyMaterial, err := awsStorageProvider.getKeyMaterial(ctx, KeyID{ + Type: babyjubjub, + ID: id, + }) + require.NoError(t, err) + assert.Equal(t, map[string]string{ + jsonKeyType: string(babyjubjub), + jsonKeyData: privateKey, + }, keyMaterial) + + err = awsStorageProvider.deleteKeyMaterial(ctx, KeyID{ + Type: babyjubjub, + ID: id, + }) + + require.NoError(t, err) + + _, err = awsStorageProvider.getKeyMaterial(ctx, KeyID{ + Type: babyjubjub, + ID: id, + }) + require.Error(t, err) + }) +} diff --git a/internal/kms/file_storage_manager.go b/internal/kms/file_storage_manager.go index 36643108b..742356939 100644 --- a/internal/kms/file_storage_manager.go +++ b/internal/kms/file_storage_manager.go @@ -109,3 +109,44 @@ func readContentFile(ctx context.Context, file string) ([]localStorageProviderFi return localStorageFileContent, nil } + +func (ls *fileStorageManager) deleteKeyMaterial(ctx context.Context, keyID KeyID) error { + localStorageFileContent, err := readContentFile(ctx, ls.file) + if err != nil { + return err + } + for i, keyMaterial := range localStorageFileContent { + if keyMaterial.KeyPath == keyID.ID { + localStorageFileContent = append(localStorageFileContent[:i], localStorageFileContent[i+1:]...) + break + } + } + + newFileContent, err := json.Marshal(localStorageFileContent) + if err != nil { + log.Error(ctx, "cannot marshal file content", "err", err) + return err + } + // nolint: all + if err := os.WriteFile(ls.file, newFileContent, 0644); err != nil { + log.Error(ctx, "cannot write file", "err", err) + return err + } + return nil +} + +func (ls *fileStorageManager) getKeyMaterial(ctx context.Context, keyID KeyID) (map[string]string, error) { + localStorageFileContent, err := readContentFile(ctx, ls.file) + if err != nil { + return nil, err + } + for _, keyMaterial := range localStorageFileContent { + if keyMaterial.KeyPath == keyID.ID { + return map[string]string{ + jsonKeyType: keyMaterial.KeyType, + jsonKeyData: keyMaterial.PrivateKey, + }, nil + } + } + return nil, errors.New("key not found") +} diff --git a/internal/kms/file_storage_manager_test.go b/internal/kms/file_storage_manager_test.go index 1125f94bd..28fad8a8f 100644 --- a/internal/kms/file_storage_manager_test.go +++ b/internal/kms/file_storage_manager_test.go @@ -164,6 +164,91 @@ func TestSearchPrivateKeyInFile_ReturnsErrorWhenKeyNotFound(t *testing.T) { assert.Error(t, err) } +func Test_GetKeyMaterial(t *testing.T) { + tmpFile, err := createTestFile(t) + assert.NoError(t, err) + //nolint:errcheck + defer os.Remove(tmpFile.Name()) + + ls := NewFileStorageManager(tmpFile.Name()) + ctx := context.Background() + + t.Run("should return key material", func(t *testing.T) { + did := randomDID(t) + privateKey := "9d7abdd5a43573ab9b623c50b9fc8f4357329d3009fe0fc22c8931161d98a03d" + id := getKeyID(&did, KeyTypeBabyJubJub, "BJJ:2290140c920a31a596937095f18a9ae15c1fe7091091be485f353968a4310380") + + err = ls.SaveKeyMaterial(ctx, map[string]string{ + jsonKeyType: string(KeyTypeBabyJubJub), + jsonKeyData: privateKey, + }, id) + assert.NoError(t, err) + + keyMaterial, err := ls.getKeyMaterial(ctx, KeyID{ + Type: babyjubjub, + ID: id, + }) + require.NoError(t, err) + assert.Equal(t, map[string]string{ + jsonKeyType: string(babyjubjub), + jsonKeyData: privateKey, + }, keyMaterial) + }) + + t.Run("should return an error", func(t *testing.T) { + _, err := ls.getKeyMaterial(ctx, KeyID{ + Type: babyjubjub, + ID: "wrong_id", + }) + require.Error(t, err) + }) +} + +func Test_DeleteKeyMaterial(t *testing.T) { + tmpFile, err := createTestFile(t) + require.NoError(t, err) + //nolint:errcheck + defer os.Remove(tmpFile.Name()) + + ls := NewFileStorageManager(tmpFile.Name()) + ctx := context.Background() + + t.Run("should delete key material", func(t *testing.T) { + did := randomDID(t) + privateKey := "9d7abdd5a43573ab9b623c50b9fc8f4357329d3009fe0fc22c8931161d98a03d" + id := getKeyID(&did, KeyTypeBabyJubJub, "BJJ:2290140c920a31a596937095f18a9ae15c1fe7091091be485f353968a4310380") + + err = ls.SaveKeyMaterial(ctx, map[string]string{ + jsonKeyType: string(KeyTypeBabyJubJub), + jsonKeyData: privateKey, + }, id) + assert.NoError(t, err) + + keyMaterial, err := ls.getKeyMaterial(ctx, KeyID{ + Type: babyjubjub, + ID: id, + }) + require.NoError(t, err) + assert.Equal(t, map[string]string{ + jsonKeyType: string(babyjubjub), + jsonKeyData: privateKey, + }, keyMaterial) + + err = ls.deleteKeyMaterial(ctx, KeyID{ + Type: babyjubjub, + ID: id, + }) + + require.NoError(t, err) + + _, err = ls.getKeyMaterial(ctx, KeyID{ + Type: babyjubjub, + ID: id, + }) + require.Error(t, err) + }) +} + func createTestFile(t *testing.T) (*os.File, error) { t.Helper() tmpFile, err := os.Create("./kms.json") diff --git a/internal/kms/kms.go b/internal/kms/kms.go index e3b7d2e57..087e620ee 100644 --- a/internal/kms/kms.go +++ b/internal/kms/kms.go @@ -20,6 +20,8 @@ type StorageManager interface { SaveKeyMaterial(ctx context.Context, keyMaterial map[string]string, id string) error searchByIdentity(ctx context.Context, identity w3c.DID, keyType KeyType) ([]KeyID, error) searchPrivateKey(ctx context.Context, keyID KeyID) (string, error) + deleteKeyMaterial(ctx context.Context, keyID KeyID) error + getKeyMaterial(ctx context.Context, keyID KeyID) (map[string]string, error) } // KMSType represents the KMS interface @@ -31,6 +33,7 @@ type KMSType interface { Sign(ctx context.Context, keyID KeyID, data []byte) ([]byte, error) KeysByIdentity(ctx context.Context, identity w3c.DID) ([]KeyID, error) LinkToIdentity(ctx context.Context, keyID KeyID, identity w3c.DID) (KeyID, error) + Delete(ctx context.Context, keyID KeyID) error } // ConfigProvider is a key provider configuration @@ -82,6 +85,8 @@ type KeyProvider interface { // KeyID can be changed after linking. // Returning new KeyID. LinkToIdentity(ctx context.Context, keyID KeyID, identity w3c.DID) (KeyID, error) + // Delete removes key from storage + Delete(ctx context.Context, keyID KeyID) error } // KMS stores keys and secrets @@ -222,6 +227,15 @@ func (k *KMS) LinkToIdentity(ctx context.Context, keyID KeyID, identity w3c.DID) return kp.LinkToIdentity(ctx, keyID, identity) } +// Delete removes key from storage +func (k *KMS) Delete(ctx context.Context, keyID KeyID) error { + kp, ok := k.registry[keyID.Type] + if !ok { + return errors.WithStack(ErrUnknownKeyType) + } + return kp.Delete(ctx, keyID) +} + // Open returns an initialized KMS func Open(pluginIden3MountPath string, vault *api.Client) (*KMS, error) { bjjKeyProvider, err := NewVaultPluginIden3KeyProvider(vault, pluginIden3MountPath, KeyTypeBabyJubJub) diff --git a/internal/kms/local_bjj_key_provider.go b/internal/kms/local_bjj_key_provider.go index fc6e0ad00..fe65d5fdb 100644 --- a/internal/kms/local_bjj_key_provider.go +++ b/internal/kms/local_bjj_key_provider.go @@ -60,10 +60,17 @@ func (ls *localBJJKeyProvider) New(identity *w3c.DID) (KeyID, error) { // PublicKey returns bytes representation for public key for specified key ID func (ls *localBJJKeyProvider) PublicKey(keyID KeyID) ([]byte, error) { + ctx := context.Background() if keyID.Type != ls.keyType { return nil, ErrIncorrectKeyType } + _, err := ls.storageManager.getKeyMaterial(ctx, keyID) + if err != nil { + log.Error(ctx, "cannot get public key", "err", err, "keyID", keyID) + return nil, err + } + ss := ls.reAnonKeyPathHex.FindStringSubmatch(keyID.ID) if ss == nil { ss = ls.reIdenKeyPathHex.FindStringSubmatch(keyID.ID) @@ -125,6 +132,10 @@ func (ls *localBJJKeyProvider) LinkToIdentity(ctx context.Context, keyID KeyID, return keyID, nil } +func (ls *localBJJKeyProvider) Delete(ctx context.Context, keyID KeyID) error { + return ls.storageManager.deleteKeyMaterial(ctx, keyID) +} + func (ls *localBJJKeyProvider) privateKey(ctx context.Context, keyID KeyID) ([]byte, error) { if keyID.Type != ls.keyType { return nil, ErrIncorrectKeyType diff --git a/internal/kms/local_bjj_key_provider_test.go b/internal/kms/local_bjj_key_provider_test.go index bf0de78a7..db378dba0 100644 --- a/internal/kms/local_bjj_key_provider_test.go +++ b/internal/kms/local_bjj_key_provider_test.go @@ -259,12 +259,14 @@ func Test_PublicKey_LocalBJJKeyProvider(t *testing.T) { AccessKey: "access_key", SecretKey: "secret_key", Region: "local", + URL: "http://localhost:4566", }) require.NoError(t, err) t.Run("should get public key using local storage manager", func(t *testing.T) { + did := randomDID(t) localbbjKeyProvider := NewLocalBJJKeyProvider(KeyTypeBabyJubJub, ls) - keyID, err := localbbjKeyProvider.New(nil) + keyID, err := localbbjKeyProvider.New(&did) assert.NoError(t, err) assert.NotEmpty(t, keyID.ID) @@ -274,8 +276,9 @@ func Test_PublicKey_LocalBJJKeyProvider(t *testing.T) { }) t.Run("should get public key using aws storage manager", func(t *testing.T) { + did := randomDID(t) localbbjKeyProvider := NewLocalBJJKeyProvider(KeyTypeBabyJubJub, awsStorageProvider) - keyID, err := localbbjKeyProvider.New(nil) + keyID, err := localbbjKeyProvider.New(&did) assert.NoError(t, err) assert.NotEmpty(t, keyID.ID) @@ -375,3 +378,42 @@ func Test_Sign_LocalBJJKeyProvider(t *testing.T) { assert.NotNil(t, signature) }) } + +func Test_DeleteKey_LocalBJJKeyProvider(t *testing.T) { + ctx := context.Background() + tmpFile, err := createTestFile(t) + assert.NoError(t, err) + //nolint:errcheck + defer os.Remove(tmpFile.Name()) + ls := NewFileStorageManager(tmpFile.Name()) + + awsStorageProvider, err := NewAwsSecretStorageProvider(ctx, AwsSecretStorageProviderConfig{ + AccessKey: "access_key", + SecretKey: "secret_key", + Region: "local", + URL: "http://localhost:4566", + }) + require.NoError(t, err) + + t.Run("should get public key using local storage manager", func(t *testing.T) { + did := randomDID(t) + localbbjKeyProvider := NewLocalBJJKeyProvider(KeyTypeBabyJubJub, ls) + keyID, err := localbbjKeyProvider.New(&did) + assert.NoError(t, err) + assert.NotEmpty(t, keyID.ID) + + err = localbbjKeyProvider.Delete(ctx, keyID) + assert.NoError(t, err) + }) + + t.Run("should get public key using aws storage manager", func(t *testing.T) { + did := randomDID(t) + localbbjKeyProvider := NewLocalBJJKeyProvider(KeyTypeBabyJubJub, awsStorageProvider) + keyID, err := localbbjKeyProvider.New(&did) + assert.NoError(t, err) + assert.NotEmpty(t, keyID.ID) + + err = localbbjKeyProvider.Delete(ctx, keyID) + assert.NoError(t, err) + }) +} diff --git a/internal/kms/local_eth_key_provider.go b/internal/kms/local_eth_key_provider.go index 80d4bec9d..a5d5c6a27 100644 --- a/internal/kms/local_eth_key_provider.go +++ b/internal/kms/local_eth_key_provider.go @@ -125,6 +125,10 @@ func (ls *localEthKeyProvider) ListByIdentity(ctx context.Context, identity w3c. return ls.storageManager.searchByIdentity(ctx, identity, ls.keyType) } +func (ls *localEthKeyProvider) Delete(ctx context.Context, keyID KeyID) error { + return nil +} + // nolint func (ls *localEthKeyProvider) privateKey(ctx context.Context, keyID KeyID) ([]byte, error) { if keyID.Type != ls.keyType { diff --git a/internal/kms/vaultPluginIden3KeyProvider.go b/internal/kms/vaultPluginIden3KeyProvider.go index f25ac181f..de70bcb1b 100644 --- a/internal/kms/vaultPluginIden3KeyProvider.go +++ b/internal/kms/vaultPluginIden3KeyProvider.go @@ -161,6 +161,11 @@ func (v *vaultPluginIden3KeyProvider) New(identity *w3c.DID) (KeyID, error) { return keyID, nil } +func (v *vaultPluginIden3KeyProvider) Delete(ctx context.Context, keyID KeyID) error { + _, err := v.vaultCli.Logical().Delete(v.keyPathFromID(keyID).keys()) + return err +} + func (v *vaultPluginIden3KeyProvider) randomKeyPath() (keyPathT, error) { var rnd [16]byte _, err := rand.Read(rnd[:]) diff --git a/internal/kms/vault_bjj_key_provider.go b/internal/kms/vault_bjj_key_provider.go index c99a248ec..d767c2727 100644 --- a/internal/kms/vault_bjj_key_provider.go +++ b/internal/kms/vault_bjj_key_provider.go @@ -134,6 +134,10 @@ func (v *vaultBJJKeyProvider) PublicKey(keyID KeyID) ([]byte, error) { return val, err } +func (v *vaultBJJKeyProvider) Delete(ctx context.Context, keyID KeyID) error { + return nil +} + func (v *vaultBJJKeyProvider) privateKey(keyID KeyID) ([]byte, error) { if keyID.Type != v.keyType { return nil, ErrIncorrectKeyType diff --git a/internal/kms/vault_eth_key_provider.go b/internal/kms/vault_eth_key_provider.go index aedc61abc..2074813dc 100644 --- a/internal/kms/vault_eth_key_provider.go +++ b/internal/kms/vault_eth_key_provider.go @@ -190,6 +190,11 @@ func (v *vaultETHKeyProvider) New(identity *w3c.DID) (KeyID, error) { return keyID, saveKeyMaterial(v.vaultCli, keyID.ID, keyMaterial) } +func (v *vaultETHKeyProvider) Delete(ctx context.Context, keyID KeyID) error { + _, err := v.vaultCli.Logical().Delete(absVaultSecretPath(keyID.ID)) + return err +} + // NewVaultEthProvider creates new provider for Ethereum keys stored in vault func NewVaultEthProvider(valutCli *api.Client, keyType KeyType) KeyProvider { reIdenKeyPathHex := regexp.MustCompile("^(?i).*/" + From 920647a36b18a42ed74f4c21ae03ceb232c0bc36 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Thu, 5 Dec 2024 09:58:45 -0300 Subject: [PATCH 08/62] chore: improvements --- api/api.yaml | 6 +-- internal/api/api.gen.go | 64 ++++++++++++------------- internal/api/identity.go | 39 ++++++++------- internal/api/keys.go | 38 +++++++-------- internal/api/keys_test.go | 12 ++--- internal/core/domain/claim.go | 24 ++++------ internal/core/domain/publicKey.go | 39 +++++++++++++++ internal/core/ports/claim_service.go | 3 +- internal/core/ports/identity_service.go | 2 +- internal/core/ports/key_service.go | 4 +- internal/core/services/claims.go | 17 ++++++- internal/core/services/identity.go | 21 ++++---- internal/core/services/key.go | 48 +++++++++---------- 13 files changed, 188 insertions(+), 129 deletions(-) create mode 100644 internal/core/domain/publicKey.go diff --git a/api/api.yaml b/api/api.yaml index 3a8654f58..233015dc6 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -427,11 +427,11 @@ paths: '500': $ref: '#/components/responses/500' - /v2/identities/{identifier}/create-auth-core-claim: + /v2/identities/{identifier}/create-auth-credential: post: summary: Add a new key to the identity - operationId: CreateAuthCoreClaim - description: Endpoint to create a new Auth Core Claim + operationId: CreateAuthCredential + description: Endpoint to create a new Auth Credential security: - basicAuth: [ ] parameters: diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 694a02f37..de4caf2c9 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -689,8 +689,8 @@ type DeleteConnectionParams struct { DeleteCredentials *bool `form:"deleteCredentials,omitempty" json:"deleteCredentials,omitempty"` } -// CreateAuthCoreClaimJSONBody defines parameters for CreateAuthCoreClaim. -type CreateAuthCoreClaimJSONBody struct { +// CreateAuthCredentialJSONBody defines parameters for CreateAuthCredential. +type CreateAuthCredentialJSONBody struct { KeyID string `json:"keyID"` } @@ -821,8 +821,8 @@ type UpdateIdentityJSONRequestBody UpdateIdentityJSONBody // CreateConnectionJSONRequestBody defines body for CreateConnection for application/json ContentType. type CreateConnectionJSONRequestBody = CreateConnectionRequest -// CreateAuthCoreClaimJSONRequestBody defines body for CreateAuthCoreClaim for application/json ContentType. -type CreateAuthCoreClaimJSONRequestBody CreateAuthCoreClaimJSONBody +// CreateAuthCredentialJSONRequestBody defines body for CreateAuthCredential for application/json ContentType. +type CreateAuthCredentialJSONRequestBody CreateAuthCredentialJSONBody // CreateCredentialJSONRequestBody defines body for CreateCredential for application/json ContentType. type CreateCredentialJSONRequestBody = CreateCredentialRequest @@ -893,8 +893,8 @@ type ServerInterface interface { // (POST /v2/identities/{identifier}/connections/{id}/credentials/revoke) RevokeConnectionCredentials(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, id Id) // Add a new key to the identity - // (POST /v2/identities/{identifier}/create-auth-core-claim) - CreateAuthCoreClaim(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) + // (POST /v2/identities/{identifier}/create-auth-credential) + CreateAuthCredential(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) // Get Credentials // (GET /v2/identities/{identifier}/credentials) GetCredentials(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetCredentialsParams) @@ -1082,8 +1082,8 @@ func (_ Unimplemented) RevokeConnectionCredentials(w http.ResponseWriter, r *htt } // Add a new key to the identity -// (POST /v2/identities/{identifier}/create-auth-core-claim) -func (_ Unimplemented) CreateAuthCoreClaim(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { +// (POST /v2/identities/{identifier}/create-auth-credential) +func (_ Unimplemented) CreateAuthCredential(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { w.WriteHeader(http.StatusNotImplemented) } @@ -1791,8 +1791,8 @@ func (siw *ServerInterfaceWrapper) RevokeConnectionCredentials(w http.ResponseWr handler.ServeHTTP(w, r) } -// CreateAuthCoreClaim operation middleware -func (siw *ServerInterfaceWrapper) CreateAuthCoreClaim(w http.ResponseWriter, r *http.Request) { +// CreateAuthCredential operation middleware +func (siw *ServerInterfaceWrapper) CreateAuthCredential(w http.ResponseWriter, r *http.Request) { var err error @@ -1812,7 +1812,7 @@ func (siw *ServerInterfaceWrapper) CreateAuthCoreClaim(w http.ResponseWriter, r r = r.WithContext(ctx) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateAuthCoreClaim(w, r, identifier) + siw.Handler.CreateAuthCredential(w, r, identifier) })) for _, middleware := range siw.HandlerMiddlewares { @@ -3085,7 +3085,7 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Post(options.BaseURL+"/v2/identities/{identifier}/connections/{id}/credentials/revoke", wrapper.RevokeConnectionCredentials) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/v2/identities/{identifier}/create-auth-core-claim", wrapper.CreateAuthCoreClaim) + r.Post(options.BaseURL+"/v2/identities/{identifier}/create-auth-credential", wrapper.CreateAuthCredential) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/identities/{identifier}/credentials", wrapper.GetCredentials) @@ -3827,38 +3827,38 @@ func (response RevokeConnectionCredentials500JSONResponse) VisitRevokeConnection return json.NewEncoder(w).Encode(response) } -type CreateAuthCoreClaimRequestObject struct { +type CreateAuthCredentialRequestObject struct { Identifier PathIdentifier2 `json:"identifier"` - Body *CreateAuthCoreClaimJSONRequestBody + Body *CreateAuthCredentialJSONRequestBody } -type CreateAuthCoreClaimResponseObject interface { - VisitCreateAuthCoreClaimResponse(w http.ResponseWriter) error +type CreateAuthCredentialResponseObject interface { + VisitCreateAuthCredentialResponse(w http.ResponseWriter) error } -type CreateAuthCoreClaim201JSONResponse struct { +type CreateAuthCredential201JSONResponse struct { Id uuid.UUID `json:"id"` } -func (response CreateAuthCoreClaim201JSONResponse) VisitCreateAuthCoreClaimResponse(w http.ResponseWriter) error { +func (response CreateAuthCredential201JSONResponse) VisitCreateAuthCredentialResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(201) return json.NewEncoder(w).Encode(response) } -type CreateAuthCoreClaim400JSONResponse struct{ N400JSONResponse } +type CreateAuthCredential400JSONResponse struct{ N400JSONResponse } -func (response CreateAuthCoreClaim400JSONResponse) VisitCreateAuthCoreClaimResponse(w http.ResponseWriter) error { +func (response CreateAuthCredential400JSONResponse) VisitCreateAuthCredentialResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type CreateAuthCoreClaim500JSONResponse struct{ N500JSONResponse } +type CreateAuthCredential500JSONResponse struct{ N500JSONResponse } -func (response CreateAuthCoreClaim500JSONResponse) VisitCreateAuthCoreClaimResponse(w http.ResponseWriter) error { +func (response CreateAuthCredential500JSONResponse) VisitCreateAuthCredentialResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) @@ -5154,8 +5154,8 @@ type StrictServerInterface interface { // (POST /v2/identities/{identifier}/connections/{id}/credentials/revoke) RevokeConnectionCredentials(ctx context.Context, request RevokeConnectionCredentialsRequestObject) (RevokeConnectionCredentialsResponseObject, error) // Add a new key to the identity - // (POST /v2/identities/{identifier}/create-auth-core-claim) - CreateAuthCoreClaim(ctx context.Context, request CreateAuthCoreClaimRequestObject) (CreateAuthCoreClaimResponseObject, error) + // (POST /v2/identities/{identifier}/create-auth-credential) + CreateAuthCredential(ctx context.Context, request CreateAuthCredentialRequestObject) (CreateAuthCredentialResponseObject, error) // Get Credentials // (GET /v2/identities/{identifier}/credentials) GetCredentials(ctx context.Context, request GetCredentialsRequestObject) (GetCredentialsResponseObject, error) @@ -5729,13 +5729,13 @@ func (sh *strictHandler) RevokeConnectionCredentials(w http.ResponseWriter, r *h } } -// CreateAuthCoreClaim operation middleware -func (sh *strictHandler) CreateAuthCoreClaim(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { - var request CreateAuthCoreClaimRequestObject +// CreateAuthCredential operation middleware +func (sh *strictHandler) CreateAuthCredential(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { + var request CreateAuthCredentialRequestObject request.Identifier = identifier - var body CreateAuthCoreClaimJSONRequestBody + var body CreateAuthCredentialJSONRequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) return @@ -5743,18 +5743,18 @@ func (sh *strictHandler) CreateAuthCoreClaim(w http.ResponseWriter, r *http.Requ request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.CreateAuthCoreClaim(ctx, request.(CreateAuthCoreClaimRequestObject)) + return sh.ssi.CreateAuthCredential(ctx, request.(CreateAuthCredentialRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "CreateAuthCoreClaim") + handler = middleware(handler, "CreateAuthCredential") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(CreateAuthCoreClaimResponseObject); ok { - if err := validResponse.VisitCreateAuthCoreClaimResponse(w); err != nil { + } else if validResponse, ok := response.(CreateAuthCredentialResponseObject); ok { + if err := validResponse.VisitCreateAuthCredentialResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { diff --git a/internal/api/identity.go b/internal/api/identity.go index 6aafc9754..95ed25991 100644 --- a/internal/api/identity.go +++ b/internal/api/identity.go @@ -312,41 +312,46 @@ func (s *Server) GetIdentityDetails(ctx context.Context, request GetIdentityDeta return response, nil } -// CreateAuthCoreClaim is the controller to create an auth core claim -func (s *Server) CreateAuthCoreClaim(ctx context.Context, request CreateAuthCoreClaimRequestObject) (CreateAuthCoreClaimResponseObject, error) { +// CreateAuthCredential is the controller to create an auth credential +func (s *Server) CreateAuthCredential(ctx context.Context, request CreateAuthCredentialRequestObject) (CreateAuthCredentialResponseObject, error) { decodedKeyID, err := b64.StdEncoding.DecodeString(request.Body.KeyID) if err != nil { - log.Error(ctx, "add key. Decoding key id", "err", err) - return CreateAuthCoreClaim400JSONResponse{ + log.Error(ctx, "decoding base64 key id", "err", err) + return CreateAuthCredential400JSONResponse{ N400JSONResponse{ - Message: "invalid key id", + Message: "error decoding base64 key id", }, - }, err + }, nil } - authCoreClaimID, err := s.identityService.AddKey(ctx, request.Identifier.w3cDID, string(decodedKeyID)) + autCredentialID, err := s.identityService.CreateAuthCredential(ctx, request.Identifier.w3cDID, string(decodedKeyID)) if err != nil { - log.Error(ctx, "add key. Adding key", "err", err) - if errors.Is(err, services.ErrSavingAuthCoreClaim) { - message := fmt.Sprintf("%s. This means an auth core claim was already created with this key", err.Error()) - return CreateAuthCoreClaim400JSONResponse{ + log.Error(ctx, "creating auth credential", "err", err) + if errors.Is(err, services.ErrDuplicatedHash) { + message := fmt.Sprintf("%s. This means an auth credential was already created with this key", err.Error()) + return CreateAuthCredential400JSONResponse{ N400JSONResponse{ Message: message, }, }, nil } - if errors.Is(err, repositories.ErrClaimDoesNotExist) { - return CreateAuthCoreClaim500JSONResponse{N500JSONResponse{Message: "If this identity has keyType=ETH you must to publish the state first"}}, nil + if errors.Is(err, services.ErrInvalidKeyType) { + return CreateAuthCredential400JSONResponse{ + N400JSONResponse{ + Message: "invalid key type. Only BJJ keys are supported", + }, + }, nil } - return CreateAuthCoreClaim500JSONResponse{ + + return CreateAuthCredential500JSONResponse{ N500JSONResponse{ Message: err.Error(), }, - }, err + }, nil } - return CreateAuthCoreClaim201JSONResponse{ - Id: authCoreClaimID, + return CreateAuthCredential201JSONResponse{ + Id: autCredentialID, }, nil } diff --git a/internal/api/keys.go b/internal/api/keys.go index 81805c451..c3c88adad 100644 --- a/internal/api/keys.go +++ b/internal/api/keys.go @@ -12,23 +12,23 @@ import ( // CreateKey is the handler for the POST /keys endpoint. func (s *Server) CreateKey(ctx context.Context, request CreateKeyRequestObject) (CreateKeyResponseObject, error) { - if string(request.Body.KeyType) != string(BJJ) { - log.Error(ctx, "create key. Invalid key type. BJJ and ETH Keys are supported") + if string(request.Body.KeyType) != string(BJJ) && string(request.Body.KeyType) != string(ETH) { + log.Error(ctx, "invalid key type. BJJ and ETH Keys are supported") return CreateKey400JSONResponse{ N400JSONResponse{ - Message: "Invalid key type. BJJ Keys are supported", + Message: "invalid key type. BJJ and ETH Keys are supported", }, }, nil } keyID, err := s.keyService.CreateKey(ctx, request.Identifier.did(), kms.KeyType(request.Body.KeyType)) if err != nil { - log.Error(ctx, "add key. Creating key", "err", err) + log.Error(ctx, "creating key", "err", err) return CreateKey500JSONResponse{ N500JSONResponse{ - Message: "internal error", + Message: err.Error(), }, - }, err + }, nil } encodedKeyID := b64.StdEncoding.EncodeToString([]byte(keyID.ID)) @@ -41,17 +41,17 @@ func (s *Server) CreateKey(ctx context.Context, request CreateKeyRequestObject) func (s *Server) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKeyResponseObject, error) { decodedKeyID, err := b64.StdEncoding.DecodeString(request.KeyID) if err != nil { - log.Error(ctx, "get key. Decoding key id", "err", err) + log.Error(ctx, "the key id can not be decoded from base64", "err", err) return GetKey400JSONResponse{ N400JSONResponse{ - Message: "invalid key id", + Message: "the key id can not be decoded from base64", }, }, nil } key, err := s.keyService.Get(ctx, request.Identifier.did(), string(decodedKeyID)) if err != nil { - log.Error(ctx, "get key. Getting key", "err", err) + log.Error(ctx, "error getting the key", "err", err) if errors.Is(err, ports.ErrInvalidKeyType) { return GetKey400JSONResponse{ N400JSONResponse{ @@ -70,7 +70,7 @@ func (s *Server) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKe return GetKey500JSONResponse{ N500JSONResponse{ - Message: "internal error", + Message: err.Error(), }, }, nil } @@ -88,10 +88,10 @@ func (s *Server) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKe func (s *Server) GetKeys(ctx context.Context, request GetKeysRequestObject) (GetKeysResponseObject, error) { keys, err := s.keyService.GetAll(ctx, request.Identifier.did()) if err != nil { - log.Error(ctx, "get keys. Getting keys", "err", err) + log.Error(ctx, "getting keys", "err", err) return GetKeys500JSONResponse{ N500JSONResponse{ - Message: "internal error", + Message: err.Error(), }, }, nil } @@ -113,27 +113,27 @@ func (s *Server) GetKeys(ctx context.Context, request GetKeysRequestObject) (Get func (s *Server) DeleteKey(ctx context.Context, request DeleteKeyRequestObject) (DeleteKeyResponseObject, error) { decodedKeyID, err := b64.StdEncoding.DecodeString(request.KeyID) if err != nil { - log.Error(ctx, "delete key. Decoding key id", "err", err) + log.Error(ctx, "the key id can not be decoded from base64", "err", err) return DeleteKey400JSONResponse{ N400JSONResponse{ - Message: "invalid key id", + Message: "the key id can not be decoded from base64", }, }, nil } err = s.keyService.Delete(ctx, request.Identifier.did(), string(decodedKeyID)) if err != nil { - if errors.Is(err, ports.ErrAuthCoreClaimNotRevoked) { + if errors.Is(err, ports.ErrAuthCredentialNotRevoked) { log.Error(ctx, "delete key. Auth core claim not revoked", "err", err) return DeleteKey400JSONResponse{ N400JSONResponse{ - Message: "associated auth core claim is not revoked", + Message: "associated auth credential is not revoked", }, }, nil } if errors.Is(err, ports.ErrKeyNotFound) { - log.Error(ctx, "delete key. Key not found", "err", err) + log.Error(ctx, "key not found", "err", err) return DeleteKey404JSONResponse{ N404JSONResponse{ Message: "key not found", @@ -141,10 +141,10 @@ func (s *Server) DeleteKey(ctx context.Context, request DeleteKeyRequestObject) }, nil } - log.Error(ctx, "delete key. Deleting key", "err", err) + log.Error(ctx, "deleting key", "err", err) return DeleteKey500JSONResponse{ N500JSONResponse{ - Message: "internal error", + Message: err.Error(), }, }, nil } diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index 7f43cd6d4..ea4e4d918 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -75,7 +75,7 @@ func TestServer_CreateKey(t *testing.T) { httpCode: http.StatusBadRequest, response: CreateKey400JSONResponse{ N400JSONResponse: N400JSONResponse{ - Message: "Invalid key type. BJJ Keys are supported", + Message: "invalid key type. BJJ and ETH Keys are supported", }, }, }, @@ -163,7 +163,7 @@ func TestServer_GetKey(t *testing.T) { httpCode: http.StatusBadRequest, response: GetKey400JSONResponse{ N400JSONResponse: N400JSONResponse{ - Message: "invalid key id", + Message: "the key id can not be decoded from base64", }, }, }, @@ -306,7 +306,7 @@ func TestServer_DeleteKey(t *testing.T) { encodedKeyIDForAuthCoreClaimID := b64.StdEncoding.EncodeToString([]byte(keyIDForAuthCoreClaimID.ID)) - _, err = server.Services.identity.AddKey(ctx, did, keyIDForAuthCoreClaimID.ID) + _, err = server.Services.identity.CreateAuthCredential(ctx, did, keyIDForAuthCoreClaimID.ID) require.NoError(t, err) handler := getHandler(ctx, server) @@ -348,20 +348,20 @@ func TestServer_DeleteKey(t *testing.T) { httpCode: http.StatusBadRequest, response: DeleteKey400JSONResponse{ N400JSONResponse: N400JSONResponse{ - Message: "invalid key id", + Message: "the key id can not be decoded from base64", }, }, }, }, { - name: "should get an error - key is an auth core claim", + name: "should get an error - associated auth credential is not revoked", auth: authOk, KeyID: encodedKeyIDForAuthCoreClaimID, expected: expected{ httpCode: http.StatusBadRequest, response: DeleteKey400JSONResponse{ N400JSONResponse: N400JSONResponse{ - Message: "associated auth core claim is not revoked", + Message: "associated auth credential is not revoked", }, }, }, diff --git a/internal/core/domain/claim.go b/internal/core/domain/claim.go index 48ce63a56..b9809966c 100644 --- a/internal/core/domain/claim.go +++ b/internal/core/domain/claim.go @@ -1,7 +1,6 @@ package domain import ( - "bytes" "database/sql/driver" "encoding/hex" "encoding/json" @@ -13,7 +12,6 @@ import ( "github.com/iden3/go-circuits/v2" core "github.com/iden3/go-iden3-core/v2" "github.com/iden3/go-iden3-core/v2/w3c" - "github.com/iden3/go-iden3-crypto/babyjub" "github.com/iden3/go-schema-processor/v2/verifiable" "github.com/jackc/pgtype" @@ -126,19 +124,6 @@ func (c *CoreClaim) Get() *core.Claim { return (*core.Claim)(c) } -// HasPublicKey returns true if the claim has the given public key -func (c *CoreClaim) HasPublicKey(pubKey []byte) bool { - entry := c.Get() - bjjClaim := entry.RawSlotsAsInts() - - var authCoreClaimPublicKey babyjub.PublicKey - authCoreClaimPublicKey.X = bjjClaim[2] - authCoreClaimPublicKey.Y = bjjClaim[3] - - compPubKey := authCoreClaimPublicKey.Compress() - return bytes.Equal(pubKey, compPubKey[:]) -} - // ValidProof returns true if the claim has a valid proof func (c *Claim) ValidProof() bool { if !c.MtProof || c.SignatureProof.Status != pgtype.Null { // this second condition is for credentials that has both types of proofs @@ -256,3 +241,12 @@ func (c *Claim) GetCredentialStatus() (*verifiable.CredentialStatus, error) { } return cStatus, nil } + +// GetPublicKey returns the public key of the claim +// If the schema is not supported, it returns an unSupportedPublicKeyType +func (c *Claim) GetPublicKey() PublicKey { + if c.SchemaURL == bjjAuthSchemaURL { + return newBJJPublicKey(*c) + } + return unSupportedPublicKeyType{} +} diff --git a/internal/core/domain/publicKey.go b/internal/core/domain/publicKey.go new file mode 100644 index 000000000..ef441e6f0 --- /dev/null +++ b/internal/core/domain/publicKey.go @@ -0,0 +1,39 @@ +package domain + +import ( + "bytes" + + "github.com/iden3/go-iden3-crypto/babyjub" +) + +const bjjAuthSchemaURL = "https://schema.iden3.io/core/json/auth.json" + +// PublicKey - defines the interface for public keys +type PublicKey interface { + Equal([]byte) bool +} + +type bjjPublicKey struct { + publicKey babyjub.PublicKey +} + +// newBJJPublicKey creates a new PublicKey from a Claim +func newBJJPublicKey(claim Claim) PublicKey { + entry := claim.CoreClaim.Get() + bjjClaim := entry.RawSlotsAsInts() + var authCoreClaimPublicKey babyjub.PublicKey + authCoreClaimPublicKey.X = bjjClaim[2] + authCoreClaimPublicKey.Y = bjjClaim[3] + return bjjPublicKey{publicKey: authCoreClaimPublicKey} +} + +func (b bjjPublicKey) Equal(pubKey []byte) bool { + compPubKey := b.publicKey.Compress() + return bytes.Equal(pubKey, compPubKey[:]) +} + +type unSupportedPublicKeyType struct{} + +func (u unSupportedPublicKeyType) Equal([]byte) bool { + return false +} diff --git a/internal/core/ports/claim_service.go b/internal/core/ports/claim_service.go index 50a7708b5..ccef1a05d 100644 --- a/internal/core/ports/claim_service.go +++ b/internal/core/ports/claim_service.go @@ -227,5 +227,6 @@ type ClaimService interface { UpdateClaimsMTPAndState(ctx context.Context, currentState *domain.IdentityState) error Delete(ctx context.Context, id uuid.UUID) error GetByStateIDWithMTPProof(ctx context.Context, did *w3c.DID, state string) ([]*domain.Claim, error) - GetAuthCoreClaims(ctx context.Context, identifier *w3c.DID) ([]*domain.Claim, error) + GetAuthCredentials(ctx context.Context, identifier *w3c.DID) ([]*domain.Claim, error) + GetAuthCredentialWithPublicKey(ctx context.Context, identifier *w3c.DID, pubKey []byte) (*domain.Claim, error) } diff --git a/internal/core/ports/identity_service.go b/internal/core/ports/identity_service.go index 4b226b1bd..b86884b85 100644 --- a/internal/core/ports/identity_service.go +++ b/internal/core/ports/identity_service.go @@ -58,5 +58,5 @@ type IdentityService interface { GetFailedState(ctx context.Context, identifier w3c.DID) (*domain.IdentityState, error) PublishGenesisStateToRHS(ctx context.Context, did *w3c.DID) error UpdateIdentityDisplayName(ctx context.Context, did w3c.DID, displayName string) error - AddKey(ctx context.Context, did *w3c.DID, keyID string) (uuid.UUID, error) + CreateAuthCredential(ctx context.Context, did *w3c.DID, keyID string) (uuid.UUID, error) } diff --git a/internal/core/ports/key_service.go b/internal/core/ports/key_service.go index f38f3d33a..f3c41eb1f 100644 --- a/internal/core/ports/key_service.go +++ b/internal/core/ports/key_service.go @@ -13,8 +13,8 @@ import ( var ( // ErrInvalidKeyType is returned when the key type is invalid ErrInvalidKeyType = errors.New("invalid key type") - // ErrAuthCoreClaimNotRevoked is returned when the associated auth core claim is not revoked - ErrAuthCoreClaimNotRevoked = errors.New("associated auth core claim not revoked") + // ErrAuthCredentialNotRevoked is returned when the associated auth core claim is not revoked + ErrAuthCredentialNotRevoked = errors.New("associated auth core claim not revoked") // ErrKeyNotFound is returned when the key is not found ErrKeyNotFound = errors.New("key not found") ) diff --git a/internal/core/services/claims.go b/internal/core/services/claims.go index a830c65ea..3fe2b8934 100644 --- a/internal/core/services/claims.go +++ b/internal/core/services/claims.go @@ -587,7 +587,7 @@ func (c *claim) GetByStateIDWithMTPProof(ctx context.Context, did *w3c.DID, stat return c.icRepo.GetByStateIDWithMTPProof(ctx, c.storage.Pgx, did, state) } -func (c *claim) GetAuthCoreClaims(ctx context.Context, identifier *w3c.DID) ([]*domain.Claim, error) { +func (c *claim) GetAuthCredentials(ctx context.Context, identifier *w3c.DID) ([]*domain.Claim, error) { authHash, err := core.AuthSchemaHash.MarshalText() if err != nil { return nil, err @@ -595,6 +595,21 @@ func (c *claim) GetAuthCoreClaims(ctx context.Context, identifier *w3c.DID) ([]* return c.icRepo.GetAuthCoreClaims(ctx, c.storage.Pgx, identifier, string(authHash)) } +// GetAuthCredentialWithPublicKey returns the auth credential with the given public key +func (c *claim) GetAuthCredentialWithPublicKey(ctx context.Context, identifier *w3c.DID, publicKey []byte) (*domain.Claim, error) { + authCredentials, err := c.GetAuthCredentials(ctx, identifier) + if err != nil { + log.Error(ctx, "failed to get auth credentials", "err", err) + return nil, err + } + for _, authCredential := range authCredentials { + if authCredential.GetPublicKey().Equal(publicKey) { + return authCredential, nil + } + } + return nil, errors.New("auth credential not found") +} + func (c *claim) revoke(ctx context.Context, did *w3c.DID, nonce uint64, description string, querier db.Querier) error { rID := new(big.Int).SetUint64(nonce) revocation := domain.Revocation{ diff --git a/internal/core/services/identity.go b/internal/core/services/identity.go index 14bf02157..bf774e6bd 100644 --- a/internal/core/services/identity.go +++ b/internal/core/services/identity.go @@ -65,8 +65,11 @@ var ( // ErrWrongDIDMetada - represents an error in the identity metadata ErrWrongDIDMetada = errors.New("wrong DID Metadata") - // ErrSavingAuthCoreClaim - represents an error saving the claim - ErrSavingAuthCoreClaim = errors.New("error saving the AuthCoreClaim. Hash already exists") + // ErrDuplicatedHash - represents an error saving the claim + ErrDuplicatedHash = errors.New("hash already exists") + + // ErrInvalidKeyType - represents an error in the key type + ErrInvalidKeyType = errors.New("invalid key type. Only BJJ keys are supported") ) type identity struct { @@ -261,7 +264,8 @@ func (i *identity) GetKeyIDFromAuthClaim(ctx context.Context, authClaim *domain. if err != nil { return keyID, err } - if authClaim.CoreClaim.HasPublicKey(pubKeyBytes) { + + if authClaim.GetPublicKey().Equal(pubKeyBytes) { log.Info(ctx, "key found", "keyID", keyID) return keyID, nil } @@ -801,8 +805,8 @@ func (i *identity) createIdentity(ctx context.Context, tx db.Querier, hostURL st return did, identity.State.TreeState().State.BigInt(), nil } -// AddKey adds a new key to the identity -func (i *identity) AddKey(ctx context.Context, did *w3c.DID, keyID string) (uuid.UUID, error) { +// CreateAuthCredential creates a new auth credential +func (i *identity) CreateAuthCredential(ctx context.Context, did *w3c.DID, keyID string) (uuid.UUID, error) { revNonce, err := common.RandInt64() if err != nil { log.Error(ctx, "generating revocation nonce", "err", err) @@ -813,9 +817,10 @@ func (i *identity) AddKey(ctx context.Context, did *w3c.DID, keyID string) (uuid var keyType kms.KeyType if strings.Contains(keyID, "BJJ") { keyType = kms.KeyTypeBabyJubJub - } else if strings.Contains(keyID, "ETH") { - keyType = kms.KeyTypeEthereum + } else { + return uuid.Nil, ErrInvalidKeyType } + kmsKeyID := kms.KeyID{ ID: keyID, Type: keyType, @@ -877,7 +882,7 @@ func (i *identity) AddKey(ctx context.Context, did *w3c.DID, keyID string) (uuid newAuthCoreClaimID, err = i.claimsRepository.Save(ctx, tx, authClaimModel) if err != nil { if strings.Contains(err.Error(), "claims_identifier_issuer_index_hash_key") { - return ErrSavingAuthCoreClaim + return ErrDuplicatedHash } return errors.Join(err, errors.New("can't save auth claim")) } diff --git a/internal/core/services/key.go b/internal/core/services/key.go index fa9c1e678..403f55fc3 100644 --- a/internal/core/services/key.go +++ b/internal/core/services/key.go @@ -48,7 +48,7 @@ func (ks *Key) Get(ctx context.Context, did *w3c.DID, keyID string) (*ports.KMSK authCoreClaim, err := getAuthCoreClaim(ctx, ks.claimService, did, publicKey) if err != nil { - log.Error(ctx, "failed to check if key has associated auth core claim", "err", err) + log.Error(ctx, "failed to check if key has associated auth credential", "err", err) return nil, err } return &ports.KMSKey{ @@ -59,6 +59,22 @@ func (ks *Key) Get(ctx context.Context, did *w3c.DID, keyID string) (*ports.KMSK }, nil } +// getAuthCoreClaim returns the keyID for the given DID and keyType +func getAuthCoreClaim(ctx context.Context, claimService ports.ClaimService, did *w3c.DID, publicKey []byte) (*domain.Claim, error) { + authCoreClaims, err := claimService.GetAuthCredentials(ctx, did) + if err != nil { + log.Error(ctx, "failed to get auth core claims", "err", err) + return nil, err + } + for _, authCoreClaim := range authCoreClaims { + if authCoreClaim.GetPublicKey().Equal(publicKey) { + return authCoreClaim, nil + } + } + + return nil, nil +} + // GetAll returns all the keys for the given DID func (ks *Key) GetAll(ctx context.Context, did *w3c.DID) ([]*ports.KMSKey, error) { keyIDs, err := ks.kms.KeysByIdentity(ctx, *did) @@ -86,23 +102,23 @@ func (ks *Key) Delete(ctx context.Context, did *w3c.DID, keyID string) error { log.Error(ctx, "failed to get public key", "err", err) return err } - authCoreClaim, err := getAuthCoreClaim(ctx, ks.claimService, did, publicKey) + authCredential, err := ks.claimService.GetAuthCredentialWithPublicKey(ctx, did, publicKey) if err != nil { - log.Error(ctx, "failed to check if key has associated auth core claim", "err", err) + log.Error(ctx, "failed to check if key has associated auth credential", "err", err) return err } - if authCoreClaim != nil { - log.Info(ctx, "can not be deleted because it has an associated auth core claim. Have to check revocation status") - revStatus, err := ks.claimService.GetRevocationStatus(ctx, *did, uint64(authCoreClaim.RevNonce)) + if authCredential != nil { + log.Info(ctx, "can not be deleted because it has an associated auth credential. Have to check revocation status") + revStatus, err := ks.claimService.GetRevocationStatus(ctx, *did, uint64(authCredential.RevNonce)) if err != nil { log.Error(ctx, "failed to get revocation status", "err", err) return err } if revStatus != nil && !revStatus.MTP.Existence { - log.Info(ctx, "Auth core claim is non revoked. Can not be deleted") - return ports.ErrAuthCoreClaimNotRevoked + log.Info(ctx, "auth credential is non revoked. Can not be deleted") + return ports.ErrAuthCredentialNotRevoked } } keyType, err := getKeyType(keyID) @@ -144,19 +160,3 @@ func getKeyType(keyID string) (kms.KeyType, error) { return keyType, nil } - -// getAuthCoreClaim returns the keyID for the given DID and keyType -func getAuthCoreClaim(ctx context.Context, claimService ports.ClaimService, did *w3c.DID, publicKey []byte) (*domain.Claim, error) { - authCoreClaims, err := claimService.GetAuthCoreClaims(ctx, did) - if err != nil { - log.Error(ctx, "failed to get auth core claims", "err", err) - return nil, err - } - for _, authCoreClaim := range authCoreClaims { - if authCoreClaim.CoreClaim.HasPublicKey(publicKey) { - return authCoreClaim, nil - } - } - - return nil, nil -} From 631fe57ab8609e7428f829e8595348c1f1b88d5b Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Thu, 5 Dec 2024 11:42:56 -0300 Subject: [PATCH 09/62] chore: improve code and fix get key --- internal/core/domain/claim.go | 2 +- internal/core/domain/publicKey.go | 10 +++--- internal/core/services/claims.go | 2 +- internal/core/services/key.go | 34 ++++++++++----------- internal/kms/aws_kms_eth_key_provider.go | 5 +++ internal/kms/aws_secret_storage_provider.go | 3 ++ internal/kms/file_storage_manager.go | 2 +- internal/kms/kms.go | 15 +++++++++ internal/kms/local_bjj_key_provider.go | 17 ++++++----- internal/kms/local_eth_key_provider.go | 6 +++- internal/kms/vaultPluginIden3KeyProvider.go | 11 +++++++ internal/kms/vault_bjj_key_provider.go | 4 +++ internal/kms/vault_eth_key_provider.go | 4 +++ internal/repositories/claim.go | 2 +- 14 files changed, 83 insertions(+), 34 deletions(-) diff --git a/internal/core/domain/claim.go b/internal/core/domain/claim.go index b9809966c..1b23ae386 100644 --- a/internal/core/domain/claim.go +++ b/internal/core/domain/claim.go @@ -248,5 +248,5 @@ func (c *Claim) GetPublicKey() PublicKey { if c.SchemaURL == bjjAuthSchemaURL { return newBJJPublicKey(*c) } - return unSupportedPublicKeyType{} + return &unSupportedPublicKeyType{} } diff --git a/internal/core/domain/publicKey.go b/internal/core/domain/publicKey.go index ef441e6f0..7cf2232c4 100644 --- a/internal/core/domain/publicKey.go +++ b/internal/core/domain/publicKey.go @@ -6,7 +6,9 @@ import ( "github.com/iden3/go-iden3-crypto/babyjub" ) -const bjjAuthSchemaURL = "https://schema.iden3.io/core/json/auth.json" +const ( + bjjAuthSchemaURL = "https://schema.iden3.io/core/json/auth.json" +) // PublicKey - defines the interface for public keys type PublicKey interface { @@ -24,16 +26,16 @@ func newBJJPublicKey(claim Claim) PublicKey { var authCoreClaimPublicKey babyjub.PublicKey authCoreClaimPublicKey.X = bjjClaim[2] authCoreClaimPublicKey.Y = bjjClaim[3] - return bjjPublicKey{publicKey: authCoreClaimPublicKey} + return &bjjPublicKey{publicKey: authCoreClaimPublicKey} } -func (b bjjPublicKey) Equal(pubKey []byte) bool { +func (b *bjjPublicKey) Equal(pubKey []byte) bool { compPubKey := b.publicKey.Compress() return bytes.Equal(pubKey, compPubKey[:]) } type unSupportedPublicKeyType struct{} -func (u unSupportedPublicKeyType) Equal([]byte) bool { +func (u *unSupportedPublicKeyType) Equal([]byte) bool { return false } diff --git a/internal/core/services/claims.go b/internal/core/services/claims.go index 3fe2b8934..39f2b2266 100644 --- a/internal/core/services/claims.go +++ b/internal/core/services/claims.go @@ -607,7 +607,7 @@ func (c *claim) GetAuthCredentialWithPublicKey(ctx context.Context, identifier * return authCredential, nil } } - return nil, errors.New("auth credential not found") + return nil, nil } func (c *claim) revoke(ctx context.Context, did *w3c.DID, nonce uint64, description string, querier db.Querier) error { diff --git a/internal/core/services/key.go b/internal/core/services/key.go index 403f55fc3..8533e7fce 100644 --- a/internal/core/services/key.go +++ b/internal/core/services/key.go @@ -7,7 +7,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/iden3/go-iden3-core/v2/w3c" - "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/kms" "github.com/polygonid/sh-id-platform/internal/log" @@ -40,13 +39,28 @@ func (ks *Key) Get(ctx context.Context, did *w3c.DID, keyID string) (*ports.KMSK return nil, err } + kmsKeyID := kms.KeyID{ + ID: keyID, + Type: keyType, + } + + exists, err := ks.kms.Exists(ctx, kmsKeyID) + if err != nil { + log.Error(ctx, "failed to check if key exists", "err", err) + return nil, err + } + + if !exists { + return nil, ports.ErrKeyNotFound + } + publicKey, err := ks.getPublicKey(ctx, keyID) if err != nil { log.Error(ctx, "failed to get public key", "err", err) return nil, ports.ErrKeyNotFound } - authCoreClaim, err := getAuthCoreClaim(ctx, ks.claimService, did, publicKey) + authCoreClaim, err := ks.claimService.GetAuthCredentialWithPublicKey(ctx, did, publicKey) if err != nil { log.Error(ctx, "failed to check if key has associated auth credential", "err", err) return nil, err @@ -59,22 +73,6 @@ func (ks *Key) Get(ctx context.Context, did *w3c.DID, keyID string) (*ports.KMSK }, nil } -// getAuthCoreClaim returns the keyID for the given DID and keyType -func getAuthCoreClaim(ctx context.Context, claimService ports.ClaimService, did *w3c.DID, publicKey []byte) (*domain.Claim, error) { - authCoreClaims, err := claimService.GetAuthCredentials(ctx, did) - if err != nil { - log.Error(ctx, "failed to get auth core claims", "err", err) - return nil, err - } - for _, authCoreClaim := range authCoreClaims { - if authCoreClaim.GetPublicKey().Equal(publicKey) { - return authCoreClaim, nil - } - } - - return nil, nil -} - // GetAll returns all the keys for the given DID func (ks *Key) GetAll(ctx context.Context, did *w3c.DID) ([]*ports.KMSKey, error) { keyIDs, err := ks.kms.KeysByIdentity(ctx, *did) diff --git a/internal/kms/aws_kms_eth_key_provider.go b/internal/kms/aws_kms_eth_key_provider.go index 71bc2d333..c6c614d2e 100644 --- a/internal/kms/aws_kms_eth_key_provider.go +++ b/internal/kms/aws_kms_eth_key_provider.go @@ -236,6 +236,11 @@ func (awsKeyProv *awsKmsEthKeyProvider) Delete(ctx context.Context, keyID KeyID) return err } +func (awsKeyProv *awsKmsEthKeyProvider) Exists(ctx context.Context, keyID KeyID) (bool, error) { + // TODO: Implement + return false, nil +} + // createAlias creates alias for key func (awsKeyProv *awsKmsEthKeyProvider) createAlias(ctx context.Context, aliasName, targetKeyId string) error { input := &kms.CreateAliasInput{ diff --git a/internal/kms/aws_secret_storage_provider.go b/internal/kms/aws_secret_storage_provider.go index c1ca1fdb3..9cb6cbecd 100644 --- a/internal/kms/aws_secret_storage_provider.go +++ b/internal/kms/aws_secret_storage_provider.go @@ -185,6 +185,9 @@ func (a *awsSecretStorageProvider) getKeyMaterial(ctx context.Context, keyID Key result, err := a.secretManager.GetSecretValue(ctx, input) if err != nil { log.Error(ctx, "error getting secret value", "err", err) + if strings.Contains(err.Error(), "ResourceNotFoundException") { + return nil, ErrKeyNotFound + } return nil, errors.New("error getting secret value from AWS") } diff --git a/internal/kms/file_storage_manager.go b/internal/kms/file_storage_manager.go index 742356939..b6d55ae03 100644 --- a/internal/kms/file_storage_manager.go +++ b/internal/kms/file_storage_manager.go @@ -148,5 +148,5 @@ func (ls *fileStorageManager) getKeyMaterial(ctx context.Context, keyID KeyID) ( }, nil } } - return nil, errors.New("key not found") + return nil, ErrKeyNotFound } diff --git a/internal/kms/kms.go b/internal/kms/kms.go index 087e620ee..2b921df46 100644 --- a/internal/kms/kms.go +++ b/internal/kms/kms.go @@ -34,6 +34,7 @@ type KMSType interface { KeysByIdentity(ctx context.Context, identity w3c.DID) ([]KeyID, error) LinkToIdentity(ctx context.Context, keyID KeyID, identity w3c.DID) (KeyID, error) Delete(ctx context.Context, keyID KeyID) error + Exists(ctx context.Context, keyID KeyID) (bool, error) } // ConfigProvider is a key provider configuration @@ -87,6 +88,8 @@ type KeyProvider interface { LinkToIdentity(ctx context.Context, keyID KeyID, identity w3c.DID) (KeyID, error) // Delete removes key from storage Delete(ctx context.Context, keyID KeyID) error + // Exists checks if key exists + Exists(ctx context.Context, keyID KeyID) (bool, error) } // KMS stores keys and secrets @@ -116,6 +119,9 @@ var ErrKeyTypeConflict = stderr.New("key type already registered") // ErrPermissionDenied raises when we register new key provider with key type var ErrPermissionDenied = stderr.New("permission denied") +// ErrKeyNotFound raises when key is not found +var ErrKeyNotFound = stderr.New("key not found") + // KeyID is a key unique identifier type KeyID struct { Type KeyType @@ -236,6 +242,15 @@ func (k *KMS) Delete(ctx context.Context, keyID KeyID) error { return kp.Delete(ctx, keyID) } +// Exists checks if key exists +func (k *KMS) Exists(ctx context.Context, keyID KeyID) (bool, error) { + kp, ok := k.registry[keyID.Type] + if !ok { + return false, errors.WithStack(ErrUnknownKeyType) + } + return kp.Exists(ctx, keyID) +} + // Open returns an initialized KMS func Open(pluginIden3MountPath string, vault *api.Client) (*KMS, error) { bjjKeyProvider, err := NewVaultPluginIden3KeyProvider(vault, pluginIden3MountPath, KeyTypeBabyJubJub) diff --git a/internal/kms/local_bjj_key_provider.go b/internal/kms/local_bjj_key_provider.go index fe65d5fdb..728a2d126 100644 --- a/internal/kms/local_bjj_key_provider.go +++ b/internal/kms/local_bjj_key_provider.go @@ -60,17 +60,10 @@ func (ls *localBJJKeyProvider) New(identity *w3c.DID) (KeyID, error) { // PublicKey returns bytes representation for public key for specified key ID func (ls *localBJJKeyProvider) PublicKey(keyID KeyID) ([]byte, error) { - ctx := context.Background() if keyID.Type != ls.keyType { return nil, ErrIncorrectKeyType } - _, err := ls.storageManager.getKeyMaterial(ctx, keyID) - if err != nil { - log.Error(ctx, "cannot get public key", "err", err, "keyID", keyID) - return nil, err - } - ss := ls.reAnonKeyPathHex.FindStringSubmatch(keyID.ID) if ss == nil { ss = ls.reIdenKeyPathHex.FindStringSubmatch(keyID.ID) @@ -166,3 +159,13 @@ func (ls *localBJJKeyProvider) privateKey(ctx context.Context, keyID KeyID) ([]b return val, nil } + +func (ls *localBJJKeyProvider) Exists(ctx context.Context, keyID KeyID) (bool, error) { + _, err := ls.storageManager.getKeyMaterial(ctx, keyID) + if err != nil { + if errors.Is(err, ErrKeyNotFound) { + return false, nil + } + } + return true, nil +} diff --git a/internal/kms/local_eth_key_provider.go b/internal/kms/local_eth_key_provider.go index a5d5c6a27..368dcd780 100644 --- a/internal/kms/local_eth_key_provider.go +++ b/internal/kms/local_eth_key_provider.go @@ -126,7 +126,11 @@ func (ls *localEthKeyProvider) ListByIdentity(ctx context.Context, identity w3c. } func (ls *localEthKeyProvider) Delete(ctx context.Context, keyID KeyID) error { - return nil + return errors.New("not implemented") +} + +func (ls *localEthKeyProvider) Exists(ctx context.Context, keyID KeyID) (bool, error) { + return false, errors.New("not implemented") } // nolint diff --git a/internal/kms/vaultPluginIden3KeyProvider.go b/internal/kms/vaultPluginIden3KeyProvider.go index de70bcb1b..2f7110fc2 100644 --- a/internal/kms/vaultPluginIden3KeyProvider.go +++ b/internal/kms/vaultPluginIden3KeyProvider.go @@ -166,6 +166,17 @@ func (v *vaultPluginIden3KeyProvider) Delete(ctx context.Context, keyID KeyID) e return err } +func (v *vaultPluginIden3KeyProvider) Exists(ctx context.Context, keyID KeyID) (bool, error) { + _, err := publicKey(v.vaultCli, v.keyPathFromID(keyID)) + if err != nil { + if strings.Contains(err.Error(), "secret is nil") { + return false, nil + } + return false, err + } + return true, nil +} + func (v *vaultPluginIden3KeyProvider) randomKeyPath() (keyPathT, error) { var rnd [16]byte _, err := rand.Read(rnd[:]) diff --git a/internal/kms/vault_bjj_key_provider.go b/internal/kms/vault_bjj_key_provider.go index d767c2727..4ebdb28f2 100644 --- a/internal/kms/vault_bjj_key_provider.go +++ b/internal/kms/vault_bjj_key_provider.go @@ -138,6 +138,10 @@ func (v *vaultBJJKeyProvider) Delete(ctx context.Context, keyID KeyID) error { return nil } +func (v *vaultBJJKeyProvider) Exists(ctx context.Context, keyID KeyID) (bool, error) { + return false, errors.New("not implemented") +} + func (v *vaultBJJKeyProvider) privateKey(keyID KeyID) ([]byte, error) { if keyID.Type != v.keyType { return nil, ErrIncorrectKeyType diff --git a/internal/kms/vault_eth_key_provider.go b/internal/kms/vault_eth_key_provider.go index 2074813dc..4fee096bd 100644 --- a/internal/kms/vault_eth_key_provider.go +++ b/internal/kms/vault_eth_key_provider.go @@ -195,6 +195,10 @@ func (v *vaultETHKeyProvider) Delete(ctx context.Context, keyID KeyID) error { return err } +func (v *vaultETHKeyProvider) Exists(ctx context.Context, keyID KeyID) (bool, error) { + return false, errors.New("not implemented") +} + // NewVaultEthProvider creates new provider for Ethereum keys stored in vault func NewVaultEthProvider(valutCli *api.Client, keyType KeyType) KeyProvider { reIdenKeyPathHex := regexp.MustCompile("^(?i).*/" + diff --git a/internal/repositories/claim.go b/internal/repositories/claim.go index 69541f288..d32510914 100644 --- a/internal/repositories/claim.go +++ b/internal/repositories/claim.go @@ -731,8 +731,8 @@ func processClaims(rows pgx.Rows) ([]*domain.Claim, error) { err := rows.Scan(&claim.ID, &claim.Issuer, &claim.SchemaHash, - &claim.SchemaURL, &claim.SchemaType, + &claim.SchemaURL, &claim.OtherIdentifier, &claim.Expiration, &claim.Updatable, From 96d85ae63a0e4729214ee6ed0dc2b928d5647332 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Thu, 5 Dec 2024 12:10:31 -0300 Subject: [PATCH 10/62] chore: update create key --- internal/core/services/key.go | 17 +++++++++++++++-- internal/kms/local_eth_key_provider.go | 8 +++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/internal/core/services/key.go b/internal/core/services/key.go index 8533e7fce..4ca20d96c 100644 --- a/internal/core/services/key.go +++ b/internal/core/services/key.go @@ -27,8 +27,21 @@ func NewKey(kms *kms.KMS, claimService ports.ClaimService) ports.KeyService { } // CreateKey creates a new key for the given DID -func (ks *Key) CreateKey(_ context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) { - return ks.kms.CreateKey(keyType, did) +func (ks *Key) CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) { + + keyID, err := ks.kms.CreateKey(keyType, did) + if err != nil { + log.Error(context.Background(), "failed to create key", "err", err) + return kms.KeyID{}, err + } + if keyType == kms.KeyTypeEthereum { + _, err := ks.kms.LinkToIdentity(ctx, keyID, *did) + if err != nil { + log.Error(ctx, "failed to link key to identity", "err", err) + return kms.KeyID{}, err + } + } + return keyID, nil } // Get returns the public key for the given keyID diff --git a/internal/kms/local_eth_key_provider.go b/internal/kms/local_eth_key_provider.go index 368dcd780..efbff3a57 100644 --- a/internal/kms/local_eth_key_provider.go +++ b/internal/kms/local_eth_key_provider.go @@ -130,7 +130,13 @@ func (ls *localEthKeyProvider) Delete(ctx context.Context, keyID KeyID) error { } func (ls *localEthKeyProvider) Exists(ctx context.Context, keyID KeyID) (bool, error) { - return false, errors.New("not implemented") + _, err := ls.storageManager.getKeyMaterial(ctx, keyID) + if err != nil { + if errors.Is(err, ErrKeyNotFound) { + return false, nil + } + } + return true, nil } // nolint From c0562f4baab472d92201126d4f441003e2fb8e7e Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Thu, 5 Dec 2024 18:21:12 -0300 Subject: [PATCH 11/62] chore: improve code and fix errors --- internal/api/identity.go | 8 ++ internal/api/keys.go | 4 +- internal/api/keys_test.go | 29 ++--- internal/core/services/identity.go | 11 ++ internal/core/services/key.go | 26 +++- internal/kms/aws_kms_eth_key_provider.go | 122 +++++++++++------- internal/kms/aws_kms_eth_key_provider_test.go | 6 +- internal/kms/local_eth_key_provider.go | 4 +- internal/kms/local_eth_key_provider_test.go | 8 +- internal/kms/vault_providers_helpers.go | 8 -- 10 files changed, 135 insertions(+), 91 deletions(-) diff --git a/internal/api/identity.go b/internal/api/identity.go index 95ed25991..34259d1ec 100644 --- a/internal/api/identity.go +++ b/internal/api/identity.go @@ -344,6 +344,14 @@ func (s *Server) CreateAuthCredential(ctx context.Context, request CreateAuthCre }, nil } + if errors.Is(err, services.ErrKeyNotFound) { + return CreateAuthCredential400JSONResponse{ + N400JSONResponse{ + Message: "key not found", + }, + }, nil + } + return CreateAuthCredential500JSONResponse{ N500JSONResponse{ Message: err.Error(), diff --git a/internal/api/keys.go b/internal/api/keys.go index c3c88adad..6fc5794e2 100644 --- a/internal/api/keys.go +++ b/internal/api/keys.go @@ -30,10 +30,8 @@ func (s *Server) CreateKey(ctx context.Context, request CreateKeyRequestObject) }, }, nil } - - encodedKeyID := b64.StdEncoding.EncodeToString([]byte(keyID.ID)) return CreateKey201JSONResponse{ - KeyID: encodedKeyID, + KeyID: keyID.ID, }, nil } diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index ea4e4d918..900fab54b 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -49,7 +49,7 @@ func TestServer_CreateKey(t *testing.T) { for _, tc := range []testConfig{ { - name: "No auth header", + name: "no auth header", auth: authWrong, expected: expected{ httpCode: http.StatusUnauthorized, @@ -122,8 +122,6 @@ func TestServer_GetKey(t *testing.T) { keyID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) require.NoError(t, err) - encodedKeyID := b64.StdEncoding.EncodeToString([]byte(keyID.ID)) - handler := getHandler(ctx, server) type expected struct { @@ -140,9 +138,9 @@ func TestServer_GetKey(t *testing.T) { for _, tc := range []testConfig{ { - name: "No auth header", + name: "no auth header", auth: authWrong, - KeyID: encodedKeyID, + KeyID: keyID.ID, expected: expected{ httpCode: http.StatusUnauthorized, }, @@ -150,7 +148,7 @@ func TestServer_GetKey(t *testing.T) { { name: "should get a key", auth: authOk, - KeyID: encodedKeyID, + KeyID: keyID.ID, expected: expected{ httpCode: http.StatusOK, }, @@ -235,7 +233,7 @@ func TestServer_GetKeys(t *testing.T) { for _, tc := range []testConfig{ { - name: "No auth header", + name: "no auth header", auth: authWrong, expected: expected{ httpCode: http.StatusUnauthorized, @@ -299,14 +297,13 @@ func TestServer_DeleteKey(t *testing.T) { keyID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) require.NoError(t, err) - encodedKeyID := b64.StdEncoding.EncodeToString([]byte(keyID.ID)) - keyIDForAuthCoreClaimID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) require.NoError(t, err) - encodedKeyIDForAuthCoreClaimID := b64.StdEncoding.EncodeToString([]byte(keyIDForAuthCoreClaimID.ID)) + keyIDForAuthCoreClaimIDASByteArr, err := b64.StdEncoding.DecodeString(keyIDForAuthCoreClaimID.ID) + require.NoError(t, err) - _, err = server.Services.identity.CreateAuthCredential(ctx, did, keyIDForAuthCoreClaimID.ID) + _, err = server.Services.identity.CreateAuthCredential(ctx, did, string(keyIDForAuthCoreClaimIDASByteArr)) require.NoError(t, err) handler := getHandler(ctx, server) @@ -325,9 +322,9 @@ func TestServer_DeleteKey(t *testing.T) { for _, tc := range []testConfig{ { - name: "No auth header", + name: "no auth header", auth: authWrong, - KeyID: encodedKeyID, + KeyID: keyID.ID, expected: expected{ httpCode: http.StatusUnauthorized, }, @@ -335,13 +332,13 @@ func TestServer_DeleteKey(t *testing.T) { { name: "should delete a key", auth: authOk, - KeyID: encodedKeyID, + KeyID: keyID.ID, expected: expected{ httpCode: http.StatusOK, }, }, { - name: "should get an error", + name: "should get an error - wrong keyID", auth: authOk, KeyID: "123", expected: expected{ @@ -356,7 +353,7 @@ func TestServer_DeleteKey(t *testing.T) { { name: "should get an error - associated auth credential is not revoked", auth: authOk, - KeyID: encodedKeyIDForAuthCoreClaimID, + KeyID: keyIDForAuthCoreClaimID.ID, expected: expected{ httpCode: http.StatusBadRequest, response: DeleteKey400JSONResponse{ diff --git a/internal/core/services/identity.go b/internal/core/services/identity.go index bf774e6bd..ac0e0403d 100644 --- a/internal/core/services/identity.go +++ b/internal/core/services/identity.go @@ -70,6 +70,9 @@ var ( // ErrInvalidKeyType - represents an error in the key type ErrInvalidKeyType = errors.New("invalid key type. Only BJJ keys are supported") + + // ErrKeyNotFound - represents an error when the key is not found + ErrKeyNotFound = errors.New("key not found") ) type identity struct { @@ -850,6 +853,14 @@ func (i *identity) CreateAuthCredential(ctx context.Context, did *w3c.DID, keyID return err } + exists, err := i.kms.Exists(ctx, kmsKeyID) + if err != nil { + return err + } + if !exists { + return ErrKeyNotFound + } + bjjPubKey, err := bjjPubKey(i.kms, kmsKeyID) if err != nil { return err diff --git a/internal/core/services/key.go b/internal/core/services/key.go index 4ca20d96c..12cd53099 100644 --- a/internal/core/services/key.go +++ b/internal/core/services/key.go @@ -2,6 +2,7 @@ package services import ( "context" + b64 "encoding/base64" "strings" "github.com/ethereum/go-ethereum/common/hexutil" @@ -28,19 +29,32 @@ func NewKey(kms *kms.KMS, claimService ports.ClaimService) ports.KeyService { // CreateKey creates a new key for the given DID func (ks *Key) CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) { - - keyID, err := ks.kms.CreateKey(keyType, did) - if err != nil { - log.Error(context.Background(), "failed to create key", "err", err) - return kms.KeyID{}, err + var keyID kms.KeyID + var err error + if keyType == kms.KeyTypeBabyJubJub { + keyID, err = ks.kms.CreateKey(keyType, did) + if err != nil { + log.Error(ctx, "failed to create key", "err", err) + return kms.KeyID{}, err + } } + if keyType == kms.KeyTypeEthereum { - _, err := ks.kms.LinkToIdentity(ctx, keyID, *did) + keyID, err = ks.kms.CreateKey(keyType, nil) + if err != nil { + log.Error(ctx, "failed to create key", "err", err) + return kms.KeyID{}, err + } + keyID, err = ks.kms.LinkToIdentity(ctx, keyID, *did) if err != nil { log.Error(ctx, "failed to link key to identity", "err", err) return kms.KeyID{}, err } } + + encodedKeyID := b64.StdEncoding.EncodeToString([]byte(keyID.ID)) + log.Info(ctx, "key created successfully", "keyID", encodedKeyID) + keyID.ID = encodedKeyID return keyID, nil } diff --git a/internal/kms/aws_kms_eth_key_provider.go b/internal/kms/aws_kms_eth_key_provider.go index c6c614d2e..c6220b294 100644 --- a/internal/kms/aws_kms_eth_key_provider.go +++ b/internal/kms/aws_kms_eth_key_provider.go @@ -2,7 +2,6 @@ package kms import ( "context" - "encoding/hex" "fmt" "regexp" "strings" @@ -18,7 +17,11 @@ import ( "github.com/polygonid/sh-id-platform/internal/log" ) -const aliasPrefix = "alias/" +const ( + aliasPrefix = "alias/" + awsKmdKeyIDPrefix = "ETH/" + awsKmsKeyIDParts = 2 +) type awsKmsEthKeyProvider struct { keyType KeyType @@ -79,37 +82,46 @@ func (awsKeyProv *awsKmsEthKeyProvider) New(identity *w3c.DID) (KeyID, error) { keyArn, err := awsKeyProv.kmsClient.CreateKey(ctx, input) if err != nil { - log.Error(ctx, "failed to create key: %v", err) + log.Error(ctx, "failed to create key", "err", err) return KeyID{}, fmt.Errorf("failed to create key: %v", err) } - log.Info(ctx, "keyArn:", keyArn.KeyMetadata.Arn) - inputPublicKey := &kms.GetPublicKeyInput{ - KeyId: aws.String(*keyArn.KeyMetadata.Arn), - } - - publicKeyResult, err := awsKeyProv.kmsClient.GetPublicKey(ctx, inputPublicKey) - if err != nil { - return KeyID{}, fmt.Errorf("failed to get public key: %v", err) - } - pubKeyHex := hex.EncodeToString(publicKeyResult.PublicKey) - keyID.ID = keyPathForAws(identity, awsKeyProv.keyType, pubKeyHex) - - aliasName := aliasPrefix + keyID.ID - err = awsKeyProv.createAlias(ctx, aliasName, *keyArn.KeyMetadata.KeyId) - if err != nil { - log.Error(ctx, "failed to create alias: %v", err) - return KeyID{}, fmt.Errorf("failed to create alias: %v", err) - } - keyID.ID = aliasName + log.Info(ctx, "keyArn", "keyArn", keyArn.KeyMetadata.Arn) + + //inputPublicKey := &kms.GetPublicKeyInput{ + // KeyId: aws.String(*keyArn.KeyMetadata.Arn), + //} + + //publicKeyResult, err := awsKeyProv.kmsClient.GetPublicKey(ctx, inputPublicKey) + //if err != nil { + // return KeyID{}, fmt.Errorf("failed to get public key: %v", err) + //} + //pubKeyHex := hex.EncodeToString(publicKeyResult.PublicKey) + //keyID.ID = keyPathForAws(identity, awsKeyProv.keyType, pubKeyHex) + //base64ID := base64.StdEncoding.EncodeToString([]byte(keyID.ID)) + // + //aliasName := aliasPrefix + base64ID + //err = awsKeyProv.createAlias(ctx, aliasName, *keyArn.KeyMetadata.KeyId) + // + //if err != nil { + // log.Error(ctx, "failed to create alias: %v", err) + // return KeyID{}, fmt.Errorf("failed to create alias: %v", err) + //} + keyID.ID = awsKmdKeyIDPrefix + *keyArn.KeyMetadata.KeyId return keyID, nil } // PublicKey returns public key for given keyID func (awsKeyProv *awsKmsEthKeyProvider) PublicKey(keyID KeyID) ([]byte, error) { + ctx := context.Background() if keyID.ID == awsKeyProv.issuerETHTransferKeyPath { keyID.ID = aliasPrefix + awsKeyProv.issuerETHTransferKeyPath + } else { + keyIDParts := strings.Split(keyID.ID, "ETH/") + if len(keyIDParts) == awsKmsKeyIDParts { + keyID.ID = keyIDParts[1] + } } - ctx := context.Background() + inputPublicKey := &kms.GetPublicKeyInput{ KeyId: aws.String(keyID.ID), } @@ -124,7 +136,14 @@ func (awsKeyProv *awsKmsEthKeyProvider) PublicKey(keyID KeyID) ([]byte, error) { func (awsKeyProv *awsKmsEthKeyProvider) Sign(ctx context.Context, keyID KeyID, data []byte) ([]byte, error) { if keyID.ID == awsKeyProv.issuerETHTransferKeyPath { keyID.ID = aliasPrefix + awsKeyProv.issuerETHTransferKeyPath + } else { + keyIDParts := strings.Split(keyID.ID, awsKmdKeyIDPrefix) + if len(keyIDParts) != awsKmsKeyIDParts { + return nil, fmt.Errorf("invalid keyID: %v", keyID.ID) + } + keyID.ID = keyIDParts[1] } + signInput := &kms.SignInput{ KeyId: aws.String(keyID.ID), Message: data, @@ -165,14 +184,21 @@ func (awsKeyProv *awsKmsEthKeyProvider) Sign(ctx context.Context, keyID KeyID, d // LinkToIdentity links key to identity func (awsKeyProv *awsKmsEthKeyProvider) LinkToIdentity(ctx context.Context, keyID KeyID, identity w3c.DID) (KeyID, error) { - keyMetadata, err := awsKeyProv.getKeyInfoByAlias(ctx, keyID.ID) - if err != nil { - log.Error(ctx, "failed to get key metadata", "keyMetadata", keyMetadata, "err", err) - return KeyID{}, fmt.Errorf("failed to get key metadata: %v", err) + //base64ID := base64.StdEncoding.EncodeToString([]byte(keyID.ID)) + //alias := aliasPrefix + base64ID + //keyMetadata, err := awsKeyProv.getKeyInfoByAlias(ctx, alias) + //if err != nil { + // log.Error(ctx, "failed to get key metadata", "keyMetadata", keyMetadata, "err", err) + // return KeyID{}, fmt.Errorf("failed to get key metadata: %v", err) + //} + + keyIDParts := strings.Split(keyID.ID, awsKmdKeyIDPrefix) + if len(keyIDParts) != awsKmsKeyIDParts { + return KeyID{}, fmt.Errorf("invalid keyID: %v", keyID.ID) } tagResourceInput := &kms.TagResourceInput{ - KeyId: keyMetadata.KeyId, + KeyId: aws.String(keyIDParts[1]), Tags: []types.Tag{ { TagKey: aws.String("keyType"), @@ -187,18 +213,20 @@ func (awsKeyProv *awsKmsEthKeyProvider) LinkToIdentity(ctx context.Context, keyI resourceOutput, err := awsKeyProv.kmsClient.TagResource(ctx, tagResourceInput) if err != nil { - log.Error(ctx, "failed to tag resource: %v", err) + log.Error(ctx, "failed to tag resource", "err", err) return KeyID{}, fmt.Errorf("failed to tag resource: %v", err) } log.Info(ctx, "resource tagged:", "resourceOutput:", resourceOutput.ResultMetadata) - keyID.ID = identity.String() return keyID, nil } // ListByIdentity returns list of keyIDs for given identity func (awsKeyProv *awsKmsEthKeyProvider) ListByIdentity(ctx context.Context, identity w3c.DID) ([]KeyID, error) { - listKeysInput := &kms.ListKeysInput{} + const limit = 500 + listKeysInput := &kms.ListKeysInput{ + Limit: aws.Int32(limit), + } listKeysOutput, err := awsKeyProv.kmsClient.ListKeys(ctx, listKeysInput) if err != nil { return nil, fmt.Errorf("failed to list keys: %w", err) @@ -217,9 +245,10 @@ func (awsKeyProv *awsKmsEthKeyProvider) ListByIdentity(ctx context.Context, iden for _, tag := range tagOutput.Tags { if aws.ToString(tag.TagKey) == "did" && aws.ToString(tag.TagValue) == identity.String() { + id := "ETH/" + *key.KeyId keysToReturn = append(keysToReturn, KeyID{ Type: KeyTypeEthereum, - ID: aws.ToString(key.KeyId), + ID: aws.ToString(&id), }) } } @@ -229,31 +258,28 @@ func (awsKeyProv *awsKmsEthKeyProvider) ListByIdentity(ctx context.Context, iden } func (awsKeyProv *awsKmsEthKeyProvider) Delete(ctx context.Context, keyID KeyID) error { + const pendingWindowInDays = 7 + keyIDParts := strings.Split(keyID.ID, awsKmdKeyIDPrefix) + if len(keyIDParts) != awsKmsKeyIDParts { + return fmt.Errorf("invalid keyID: %v", keyID.ID) + } _, err := awsKeyProv.kmsClient.ScheduleKeyDeletion(ctx, &kms.ScheduleKeyDeletionInput{ - KeyId: aws.String(keyID.ID), - PendingWindowInDays: aws.Int32(1), + KeyId: aws.String(keyIDParts[1]), + PendingWindowInDays: aws.Int32(pendingWindowInDays), }) return err } func (awsKeyProv *awsKmsEthKeyProvider) Exists(ctx context.Context, keyID KeyID) (bool, error) { - // TODO: Implement - return false, nil -} - -// createAlias creates alias for key -func (awsKeyProv *awsKmsEthKeyProvider) createAlias(ctx context.Context, aliasName, targetKeyId string) error { - input := &kms.CreateAliasInput{ - AliasName: aws.String(aliasName), - TargetKeyId: aws.String(targetKeyId), + keyIDParts := strings.Split(keyID.ID, awsKmdKeyIDPrefix) + if len(keyIDParts) != awsKmsKeyIDParts { + return false, fmt.Errorf("invalid keyID: %v", keyID.ID) } - _, err := awsKeyProv.kmsClient.CreateAlias(ctx, input) + _, err := awsKeyProv.getKeyInfoByAlias(ctx, keyIDParts[1]) if err != nil { - return fmt.Errorf("failed to create alias: %v", err) + return false, nil } - - log.Info(ctx, "alias created:", "aliasName:", aliasName) - return nil + return true, nil } // getKeyInfoByAlias returns key metadata by alias diff --git a/internal/kms/aws_kms_eth_key_provider_test.go b/internal/kms/aws_kms_eth_key_provider_test.go index ac55ecc10..c4c4fbcf9 100644 --- a/internal/kms/aws_kms_eth_key_provider_test.go +++ b/internal/kms/aws_kms_eth_key_provider_test.go @@ -71,12 +71,10 @@ func Test_LinkToIdentityInAWSKMS(t *testing.T) { assert.NoError(t, err) assert.NotEmpty(t, keyID.ID) assert.Equal(t, ethereum, string(keyID.Type)) - identity := randomDID(t) - - keyID, err = awsStorageProvider.LinkToIdentity(ctx, keyID, identity) + newKeyID, err := awsStorageProvider.LinkToIdentity(ctx, keyID, identity) assert.NoError(t, err) - assert.Equal(t, identity.String(), keyID.ID) + assert.Equal(t, keyID.ID, newKeyID.ID) }) t.Run("should get an error", func(t *testing.T) { diff --git a/internal/kms/local_eth_key_provider.go b/internal/kms/local_eth_key_provider.go index efbff3a57..28115af1b 100644 --- a/internal/kms/local_eth_key_provider.go +++ b/internal/kms/local_eth_key_provider.go @@ -116,7 +116,7 @@ func (ls *localEthKeyProvider) LinkToIdentity(ctx context.Context, keyID KeyID, return KeyID{}, err } - keyID.ID = identity.String() + keyID.ID = identity.String() + "/" + keyID.ID return keyID, nil } @@ -126,7 +126,7 @@ func (ls *localEthKeyProvider) ListByIdentity(ctx context.Context, identity w3c. } func (ls *localEthKeyProvider) Delete(ctx context.Context, keyID KeyID) error { - return errors.New("not implemented") + return ls.storageManager.deleteKeyMaterial(ctx, keyID) } func (ls *localEthKeyProvider) Exists(ctx context.Context, keyID KeyID) (bool, error) { diff --git a/internal/kms/local_eth_key_provider_test.go b/internal/kms/local_eth_key_provider_test.go index db6b6e440..28b524526 100644 --- a/internal/kms/local_eth_key_provider_test.go +++ b/internal/kms/local_eth_key_provider_test.go @@ -96,10 +96,10 @@ func Test_LinkToIdentity_LocalETHKeyProvider(t *testing.T) { assert.NotEmpty(t, keyID.ID) did := randomDID(t) - keyID, err = localETHKeyProvider.LinkToIdentity(ctx, keyID, did) + newKeyID, err := localETHKeyProvider.LinkToIdentity(ctx, keyID, did) assert.NoError(t, err) assert.NotNil(t, keyID) - assert.Equal(t, did.String(), keyID.ID) + assert.Equal(t, did.String()+"/"+keyID.ID, newKeyID.ID) assert.Equal(t, KeyTypeEthereum, keyID.Type) }) @@ -110,10 +110,10 @@ func Test_LinkToIdentity_LocalETHKeyProvider(t *testing.T) { assert.NotEmpty(t, keyID.ID) did := randomDID(t) - keyID, err = localETHKeyProvider.LinkToIdentity(ctx, keyID, did) + newKeyID, err := localETHKeyProvider.LinkToIdentity(ctx, keyID, did) assert.NoError(t, err) assert.NotNil(t, keyID) - assert.Equal(t, did.String(), keyID.ID) + assert.Equal(t, did.String()+"/"+keyID.ID, newKeyID.ID) assert.Equal(t, KeyTypeEthereum, keyID.Type) }) } diff --git a/internal/kms/vault_providers_helpers.go b/internal/kms/vault_providers_helpers.go index 6306cbee4..2073fc591 100644 --- a/internal/kms/vault_providers_helpers.go +++ b/internal/kms/vault_providers_helpers.go @@ -73,14 +73,6 @@ func keyPath(identity *w3c.DID, keyType KeyType, keyID string) string { return basePath + string(keyType) + ":" + keyID } -func keyPathForAws(identity *w3c.DID, keyType KeyType, keyID string) string { - basePath := "" - if identity != nil { - basePath = identityPath(identity) + "/" - } - return basePath + string(keyType) + keyID -} - func absVaultSecretPath(path string) string { return kvStoragePath + "/data/" + strings.TrimPrefix(path, "/") } From 86a90397c0cec3a6d3cc6b3107cead53a9a3002c Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Fri, 6 Dec 2024 08:32:22 -0300 Subject: [PATCH 12/62] chore: improve code and fix errors --- internal/api/keys.go | 3 +- internal/core/services/key.go | 29 +++++++++++++----- internal/kms/aws_kms_eth_key_provider.go | 34 +++------------------ internal/kms/vaultPluginIden3KeyProvider.go | 3 ++ 4 files changed, 31 insertions(+), 38 deletions(-) diff --git a/internal/api/keys.go b/internal/api/keys.go index 6fc5794e2..d304b8bcf 100644 --- a/internal/api/keys.go +++ b/internal/api/keys.go @@ -94,7 +94,8 @@ func (s *Server) GetKeys(ctx context.Context, request GetKeysRequestObject) (Get }, nil } - var keysResponse GetKeys200JSONResponse + keysResponse := make(GetKeys200JSONResponse, 0) + for _, key := range keys { encodedKeyID := b64.StdEncoding.EncodeToString([]byte(key.KeyID)) keysResponse = append(keysResponse, Key{ diff --git a/internal/core/services/key.go b/internal/core/services/key.go index 12cd53099..109fb2201 100644 --- a/internal/core/services/key.go +++ b/internal/core/services/key.go @@ -122,6 +122,27 @@ func (ks *Key) GetAll(ctx context.Context, did *w3c.DID) ([]*ports.KMSKey, error // Delete deletes the key with the given keyID func (ks *Key) Delete(ctx context.Context, did *w3c.DID, keyID string) error { + keyType, err := getKeyType(keyID) + if err != nil { + log.Error(ctx, "failed to get key type", "err", err) + return err + } + + kmsKeyID := kms.KeyID{ + ID: keyID, + Type: keyType, + } + + exists, err := ks.kms.Exists(ctx, kmsKeyID) + if err != nil { + log.Error(ctx, "failed to check if key exists", "err", err) + return err + } + + if !exists { + return ports.ErrKeyNotFound + } + publicKey, err := ks.getPublicKey(ctx, keyID) if err != nil { log.Error(ctx, "failed to get public key", "err", err) @@ -146,14 +167,6 @@ func (ks *Key) Delete(ctx context.Context, did *w3c.DID, keyID string) error { return ports.ErrAuthCredentialNotRevoked } } - keyType, err := getKeyType(keyID) - if err != nil { - log.Error(ctx, "failed to get key type", "err", err) - } - kmsKeyID := kms.KeyID{ - ID: keyID, - Type: keyType, - } return ks.kms.Delete(ctx, kmsKeyID) } diff --git a/internal/kms/aws_kms_eth_key_provider.go b/internal/kms/aws_kms_eth_key_provider.go index c6220b294..54b74d703 100644 --- a/internal/kms/aws_kms_eth_key_provider.go +++ b/internal/kms/aws_kms_eth_key_provider.go @@ -86,26 +86,6 @@ func (awsKeyProv *awsKmsEthKeyProvider) New(identity *w3c.DID) (KeyID, error) { return KeyID{}, fmt.Errorf("failed to create key: %v", err) } log.Info(ctx, "keyArn", "keyArn", keyArn.KeyMetadata.Arn) - - //inputPublicKey := &kms.GetPublicKeyInput{ - // KeyId: aws.String(*keyArn.KeyMetadata.Arn), - //} - - //publicKeyResult, err := awsKeyProv.kmsClient.GetPublicKey(ctx, inputPublicKey) - //if err != nil { - // return KeyID{}, fmt.Errorf("failed to get public key: %v", err) - //} - //pubKeyHex := hex.EncodeToString(publicKeyResult.PublicKey) - //keyID.ID = keyPathForAws(identity, awsKeyProv.keyType, pubKeyHex) - //base64ID := base64.StdEncoding.EncodeToString([]byte(keyID.ID)) - // - //aliasName := aliasPrefix + base64ID - //err = awsKeyProv.createAlias(ctx, aliasName, *keyArn.KeyMetadata.KeyId) - // - //if err != nil { - // log.Error(ctx, "failed to create alias: %v", err) - // return KeyID{}, fmt.Errorf("failed to create alias: %v", err) - //} keyID.ID = awsKmdKeyIDPrefix + *keyArn.KeyMetadata.KeyId return keyID, nil } @@ -184,14 +164,6 @@ func (awsKeyProv *awsKmsEthKeyProvider) Sign(ctx context.Context, keyID KeyID, d // LinkToIdentity links key to identity func (awsKeyProv *awsKmsEthKeyProvider) LinkToIdentity(ctx context.Context, keyID KeyID, identity w3c.DID) (KeyID, error) { - //base64ID := base64.StdEncoding.EncodeToString([]byte(keyID.ID)) - //alias := aliasPrefix + base64ID - //keyMetadata, err := awsKeyProv.getKeyInfoByAlias(ctx, alias) - //if err != nil { - // log.Error(ctx, "failed to get key metadata", "keyMetadata", keyMetadata, "err", err) - // return KeyID{}, fmt.Errorf("failed to get key metadata: %v", err) - //} - keyIDParts := strings.Split(keyID.ID, awsKmdKeyIDPrefix) if len(keyIDParts) != awsKmsKeyIDParts { return KeyID{}, fmt.Errorf("invalid keyID: %v", keyID.ID) @@ -275,10 +247,14 @@ func (awsKeyProv *awsKmsEthKeyProvider) Exists(ctx context.Context, keyID KeyID) if len(keyIDParts) != awsKmsKeyIDParts { return false, fmt.Errorf("invalid keyID: %v", keyID.ID) } - _, err := awsKeyProv.getKeyInfoByAlias(ctx, keyIDParts[1]) + keyInfo, err := awsKeyProv.getKeyInfoByAlias(ctx, keyIDParts[1]) if err != nil { return false, nil } + + if keyInfo != nil && keyInfo.DeletionDate != nil { + return false, nil + } return true, nil } diff --git a/internal/kms/vaultPluginIden3KeyProvider.go b/internal/kms/vaultPluginIden3KeyProvider.go index 2f7110fc2..aea4b4b0f 100644 --- a/internal/kms/vaultPluginIden3KeyProvider.go +++ b/internal/kms/vaultPluginIden3KeyProvider.go @@ -128,6 +128,9 @@ func (v *vaultPluginIden3KeyProvider) PublicKey(keyID KeyID) ([]byte, error) { publicKeyStr, err := publicKey(v.vaultCli, v.keyPathFromID(keyID)) if err != nil { + if strings.Contains(err.Error(), "secret is nil") { + return nil, ErrKeyNotFound + } return nil, err } From af01253f46ffecc11bf287988436e4da8959686e Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Fri, 6 Dec 2024 11:52:56 -0300 Subject: [PATCH 13/62] chore: improve how to get kms key --- internal/kms/aws_kms_eth_key_provider.go | 42 +++++++++++++++--------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/internal/kms/aws_kms_eth_key_provider.go b/internal/kms/aws_kms_eth_key_provider.go index 54b74d703..39972e235 100644 --- a/internal/kms/aws_kms_eth_key_provider.go +++ b/internal/kms/aws_kms_eth_key_provider.go @@ -164,13 +164,13 @@ func (awsKeyProv *awsKmsEthKeyProvider) Sign(ctx context.Context, keyID KeyID, d // LinkToIdentity links key to identity func (awsKeyProv *awsKmsEthKeyProvider) LinkToIdentity(ctx context.Context, keyID KeyID, identity w3c.DID) (KeyID, error) { - keyIDParts := strings.Split(keyID.ID, awsKmdKeyIDPrefix) - if len(keyIDParts) != awsKmsKeyIDParts { - return KeyID{}, fmt.Errorf("invalid keyID: %v", keyID.ID) + keyIDStr, err := getAwsKmsKeyID(keyID) + if err != nil { + return KeyID{}, err } tagResourceInput := &kms.TagResourceInput{ - KeyId: aws.String(keyIDParts[1]), + KeyId: aws.String(keyIDStr), Tags: []types.Tag{ { TagKey: aws.String("keyType"), @@ -229,25 +229,27 @@ func (awsKeyProv *awsKmsEthKeyProvider) ListByIdentity(ctx context.Context, iden return keysToReturn, nil } +// Delete deletes key by keyID func (awsKeyProv *awsKmsEthKeyProvider) Delete(ctx context.Context, keyID KeyID) error { const pendingWindowInDays = 7 - keyIDParts := strings.Split(keyID.ID, awsKmdKeyIDPrefix) - if len(keyIDParts) != awsKmsKeyIDParts { - return fmt.Errorf("invalid keyID: %v", keyID.ID) + keyIDStr, err := getAwsKmsKeyID(keyID) + if err != nil { + return err } - _, err := awsKeyProv.kmsClient.ScheduleKeyDeletion(ctx, &kms.ScheduleKeyDeletionInput{ - KeyId: aws.String(keyIDParts[1]), + _, err = awsKeyProv.kmsClient.ScheduleKeyDeletion(ctx, &kms.ScheduleKeyDeletionInput{ + KeyId: aws.String(keyIDStr), PendingWindowInDays: aws.Int32(pendingWindowInDays), }) return err } +// Exists checks if key exists func (awsKeyProv *awsKmsEthKeyProvider) Exists(ctx context.Context, keyID KeyID) (bool, error) { - keyIDParts := strings.Split(keyID.ID, awsKmdKeyIDPrefix) - if len(keyIDParts) != awsKmsKeyIDParts { - return false, fmt.Errorf("invalid keyID: %v", keyID.ID) + keyIDStr, err := getAwsKmsKeyID(keyID) + if err != nil { + return false, err } - keyInfo, err := awsKeyProv.getKeyInfoByAlias(ctx, keyIDParts[1]) + keyInfo, err := awsKeyProv.getKeyInfo(ctx, keyIDStr) if err != nil { return false, nil } @@ -258,10 +260,10 @@ func (awsKeyProv *awsKmsEthKeyProvider) Exists(ctx context.Context, keyID KeyID) return true, nil } -// getKeyInfoByAlias returns key metadata by alias -func (awsKeyProv *awsKmsEthKeyProvider) getKeyInfoByAlias(ctx context.Context, aliasName string) (*types.KeyMetadata, error) { +// getKeyInfo returns key metadata by key id +func (awsKeyProv *awsKmsEthKeyProvider) getKeyInfo(ctx context.Context, keyID string) (*types.KeyMetadata, error) { aliasInput := &kms.DescribeKeyInput{ - KeyId: aws.String(aliasName), + KeyId: aws.String(keyID), } aliasOutput, err := awsKeyProv.kmsClient.DescribeKey(ctx, aliasInput) if err != nil { @@ -270,3 +272,11 @@ func (awsKeyProv *awsKmsEthKeyProvider) getKeyInfoByAlias(ctx context.Context, a } return aliasOutput.KeyMetadata, nil } + +func getAwsKmsKeyID(keyID KeyID) (string, error) { + keyIDParts := strings.Split(keyID.ID, awsKmdKeyIDPrefix) + if len(keyIDParts) != awsKmsKeyIDParts { + return "", fmt.Errorf("invalid keyID: %v", keyID.ID) + } + return keyIDParts[1], nil +} From d010ebab1ce5ab7713c99fd6bd2b7b347529506a Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Mon, 9 Dec 2024 06:40:46 -0300 Subject: [PATCH 14/62] chore: change keyID to id --- api/api.yaml | 15 ++++---- internal/api/api.gen.go | 78 +++++++++++++++++++-------------------- internal/api/keys.go | 10 ++--- internal/api/keys_test.go | 14 +++---- 4 files changed, 59 insertions(+), 58 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index 233015dc6..c59ff0525 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -429,7 +429,7 @@ paths: /v2/identities/{identifier}/create-auth-credential: post: - summary: Add a new key to the identity + summary: Create Auth Credential operationId: CreateAuthCredential description: Endpoint to create a new Auth Credential security: @@ -464,6 +464,7 @@ paths: properties: id: type: string + description: The ID of the created Auth Credential x-go-type: uuid.UUID x-go-type-import: name: uuid @@ -1400,7 +1401,7 @@ paths: '500': $ref: '#/components/responses/500' - /v2/identities/{identifier}/keys/{keyID}: + /v2/identities/{identifier}/keys/{id}: get: summary: Get a Key operationId: GetKey @@ -2409,9 +2410,9 @@ components: CreateKeyResponse: type: object required: - - keyID + - id properties: - keyID: + id: type: string x-omitempty: false description: base64 encoded keyID @@ -2420,12 +2421,12 @@ components: Key: type: object required: - - keyID + - id - keyType - publicKey - isAuthCoreClaim properties: - keyID: + id: type: string x-omitempty: false example: ZGlkOnBvbHlnb25pZDpwb2x5Z29uOmFtb3k6MnFRNjhKa1JjZjN5cXBYanRqVVQ3WjdVeW1TV0hzYll @@ -2531,7 +2532,7 @@ components: format: int64 pathKeyID: - name: keyID + name: id in: path required: true description: Key ID in base64 diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index de4caf2c9..a7ebf4412 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -279,8 +279,8 @@ type CreateKeyRequestKeyType string // CreateKeyResponse defines model for CreateKeyResponse. type CreateKeyResponse struct { - // KeyID base64 encoded keyID - KeyID string `json:"keyID"` + // Id base64 encoded keyID + Id string `json:"id"` } // CreateLinkRequest defines model for CreateLinkRequest. @@ -429,12 +429,11 @@ type IssuerDescription struct { // Key defines model for Key. type Key struct { - IsAuthCoreClaim bool `json:"isAuthCoreClaim"` - - // KeyID base64 encoded keyID - KeyID string `json:"keyID"` - KeyType KeyKeyType `json:"keyType"` - PublicKey string `json:"publicKey"` + // Id base64 encoded keyID + Id string `json:"id"` + IsAuthCoreClaim bool `json:"isAuthCoreClaim"` + KeyType KeyKeyType `json:"keyType"` + PublicKey string `json:"publicKey"` } // KeyKeyType defines model for Key.KeyType. @@ -892,7 +891,7 @@ type ServerInterface interface { // Revoke Connection Credentials // (POST /v2/identities/{identifier}/connections/{id}/credentials/revoke) RevokeConnectionCredentials(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, id Id) - // Add a new key to the identity + // Create Auth Credential // (POST /v2/identities/{identifier}/create-auth-credential) CreateAuthCredential(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) // Get Credentials @@ -944,11 +943,11 @@ type ServerInterface interface { // (POST /v2/identities/{identifier}/keys) CreateKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) // Delete Key - // (DELETE /v2/identities/{identifier}/keys/{keyID}) - DeleteKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) + // (DELETE /v2/identities/{identifier}/keys/{id}) + DeleteKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, id PathKeyID) // Get a Key - // (GET /v2/identities/{identifier}/keys/{keyID}) - GetKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) + // (GET /v2/identities/{identifier}/keys/{id}) + GetKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, id PathKeyID) // Get Schemas // (GET /v2/identities/{identifier}/schemas) GetSchemas(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetSchemasParams) @@ -1081,7 +1080,7 @@ func (_ Unimplemented) RevokeConnectionCredentials(w http.ResponseWriter, r *htt w.WriteHeader(http.StatusNotImplemented) } -// Add a new key to the identity +// Create Auth Credential // (POST /v2/identities/{identifier}/create-auth-credential) func (_ Unimplemented) CreateAuthCredential(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { w.WriteHeader(http.StatusNotImplemented) @@ -1184,14 +1183,14 @@ func (_ Unimplemented) CreateKey(w http.ResponseWriter, r *http.Request, identif } // Delete Key -// (DELETE /v2/identities/{identifier}/keys/{keyID}) -func (_ Unimplemented) DeleteKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) { +// (DELETE /v2/identities/{identifier}/keys/{id}) +func (_ Unimplemented) DeleteKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, id PathKeyID) { w.WriteHeader(http.StatusNotImplemented) } // Get a Key -// (GET /v2/identities/{identifier}/keys/{keyID}) -func (_ Unimplemented) GetKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) { +// (GET /v2/identities/{identifier}/keys/{id}) +func (_ Unimplemented) GetKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, id PathKeyID) { w.WriteHeader(http.StatusNotImplemented) } @@ -2494,12 +2493,12 @@ func (siw *ServerInterfaceWrapper) DeleteKey(w http.ResponseWriter, r *http.Requ return } - // ------------- Path parameter "keyID" ------------- - var keyID PathKeyID + // ------------- Path parameter "id" ------------- + var id PathKeyID - err = runtime.BindStyledParameterWithOptions("simple", "keyID", chi.URLParam(r, "keyID"), &keyID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "keyID", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) return } @@ -2510,7 +2509,7 @@ func (siw *ServerInterfaceWrapper) DeleteKey(w http.ResponseWriter, r *http.Requ r = r.WithContext(ctx) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteKey(w, r, identifier, keyID) + siw.Handler.DeleteKey(w, r, identifier, id) })) for _, middleware := range siw.HandlerMiddlewares { @@ -2534,12 +2533,12 @@ func (siw *ServerInterfaceWrapper) GetKey(w http.ResponseWriter, r *http.Request return } - // ------------- Path parameter "keyID" ------------- - var keyID PathKeyID + // ------------- Path parameter "id" ------------- + var id PathKeyID - err = runtime.BindStyledParameterWithOptions("simple", "keyID", chi.URLParam(r, "keyID"), &keyID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "keyID", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) return } @@ -2550,7 +2549,7 @@ func (siw *ServerInterfaceWrapper) GetKey(w http.ResponseWriter, r *http.Request r = r.WithContext(ctx) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetKey(w, r, identifier, keyID) + siw.Handler.GetKey(w, r, identifier, id) })) for _, middleware := range siw.HandlerMiddlewares { @@ -3136,10 +3135,10 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Post(options.BaseURL+"/v2/identities/{identifier}/keys", wrapper.CreateKey) }) r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/v2/identities/{identifier}/keys/{keyID}", wrapper.DeleteKey) + r.Delete(options.BaseURL+"/v2/identities/{identifier}/keys/{id}", wrapper.DeleteKey) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/v2/identities/{identifier}/keys/{keyID}", wrapper.GetKey) + r.Get(options.BaseURL+"/v2/identities/{identifier}/keys/{id}", wrapper.GetKey) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/identities/{identifier}/schemas", wrapper.GetSchemas) @@ -3837,6 +3836,7 @@ type CreateAuthCredentialResponseObject interface { } type CreateAuthCredential201JSONResponse struct { + // Id The ID of the created Auth Credential Id uuid.UUID `json:"id"` } @@ -4580,7 +4580,7 @@ func (response CreateKey500JSONResponse) VisitCreateKeyResponse(w http.ResponseW type DeleteKeyRequestObject struct { Identifier PathIdentifier2 `json:"identifier"` - KeyID PathKeyID `json:"keyID"` + Id PathKeyID `json:"id"` } type DeleteKeyResponseObject interface { @@ -4634,7 +4634,7 @@ func (response DeleteKey500JSONResponse) VisitDeleteKeyResponse(w http.ResponseW type GetKeyRequestObject struct { Identifier PathIdentifier2 `json:"identifier"` - KeyID PathKeyID `json:"keyID"` + Id PathKeyID `json:"id"` } type GetKeyResponseObject interface { @@ -5153,7 +5153,7 @@ type StrictServerInterface interface { // Revoke Connection Credentials // (POST /v2/identities/{identifier}/connections/{id}/credentials/revoke) RevokeConnectionCredentials(ctx context.Context, request RevokeConnectionCredentialsRequestObject) (RevokeConnectionCredentialsResponseObject, error) - // Add a new key to the identity + // Create Auth Credential // (POST /v2/identities/{identifier}/create-auth-credential) CreateAuthCredential(ctx context.Context, request CreateAuthCredentialRequestObject) (CreateAuthCredentialResponseObject, error) // Get Credentials @@ -5205,10 +5205,10 @@ type StrictServerInterface interface { // (POST /v2/identities/{identifier}/keys) CreateKey(ctx context.Context, request CreateKeyRequestObject) (CreateKeyResponseObject, error) // Delete Key - // (DELETE /v2/identities/{identifier}/keys/{keyID}) + // (DELETE /v2/identities/{identifier}/keys/{id}) DeleteKey(ctx context.Context, request DeleteKeyRequestObject) (DeleteKeyResponseObject, error) // Get a Key - // (GET /v2/identities/{identifier}/keys/{keyID}) + // (GET /v2/identities/{identifier}/keys/{id}) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKeyResponseObject, error) // Get Schemas // (GET /v2/identities/{identifier}/schemas) @@ -6228,11 +6228,11 @@ func (sh *strictHandler) CreateKey(w http.ResponseWriter, r *http.Request, ident } // DeleteKey operation middleware -func (sh *strictHandler) DeleteKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) { +func (sh *strictHandler) DeleteKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, id PathKeyID) { var request DeleteKeyRequestObject request.Identifier = identifier - request.KeyID = keyID + request.Id = id handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { return sh.ssi.DeleteKey(ctx, request.(DeleteKeyRequestObject)) @@ -6255,11 +6255,11 @@ func (sh *strictHandler) DeleteKey(w http.ResponseWriter, r *http.Request, ident } // GetKey operation middleware -func (sh *strictHandler) GetKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, keyID PathKeyID) { +func (sh *strictHandler) GetKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, id PathKeyID) { var request GetKeyRequestObject request.Identifier = identifier - request.KeyID = keyID + request.Id = id handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { return sh.ssi.GetKey(ctx, request.(GetKeyRequestObject)) diff --git a/internal/api/keys.go b/internal/api/keys.go index d304b8bcf..d1ff9c3d6 100644 --- a/internal/api/keys.go +++ b/internal/api/keys.go @@ -31,13 +31,13 @@ func (s *Server) CreateKey(ctx context.Context, request CreateKeyRequestObject) }, nil } return CreateKey201JSONResponse{ - KeyID: keyID.ID, + Id: keyID.ID, }, nil } // GetKey is the handler for the GET /keys/{keyID} endpoint. func (s *Server) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKeyResponseObject, error) { - decodedKeyID, err := b64.StdEncoding.DecodeString(request.KeyID) + decodedKeyID, err := b64.StdEncoding.DecodeString(request.Id) if err != nil { log.Error(ctx, "the key id can not be decoded from base64", "err", err) return GetKey400JSONResponse{ @@ -75,7 +75,7 @@ func (s *Server) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKe encodedKeyID := b64.StdEncoding.EncodeToString([]byte(key.KeyID)) return GetKey200JSONResponse{ - KeyID: encodedKeyID, + Id: encodedKeyID, KeyType: KeyKeyType(key.KeyType), PublicKey: key.PublicKey, IsAuthCoreClaim: key.HasAssociatedAuthCoreClaim, @@ -99,7 +99,7 @@ func (s *Server) GetKeys(ctx context.Context, request GetKeysRequestObject) (Get for _, key := range keys { encodedKeyID := b64.StdEncoding.EncodeToString([]byte(key.KeyID)) keysResponse = append(keysResponse, Key{ - KeyID: encodedKeyID, + Id: encodedKeyID, KeyType: KeyKeyType(key.KeyType), PublicKey: key.PublicKey, IsAuthCoreClaim: key.HasAssociatedAuthCoreClaim, @@ -110,7 +110,7 @@ func (s *Server) GetKeys(ctx context.Context, request GetKeysRequestObject) (Get // DeleteKey is the handler for the DELETE /keys/{keyID} endpoint. func (s *Server) DeleteKey(ctx context.Context, request DeleteKeyRequestObject) (DeleteKeyResponseObject, error) { - decodedKeyID, err := b64.StdEncoding.DecodeString(request.KeyID) + decodedKeyID, err := b64.StdEncoding.DecodeString(request.Id) if err != nil { log.Error(ctx, "the key id can not be decoded from base64", "err", err) return DeleteKey400JSONResponse{ diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index 900fab54b..c1a80ea34 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -94,7 +94,7 @@ func TestServer_CreateKey(t *testing.T) { case http.StatusCreated: var response CreateKey201JSONResponse require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) - assert.NotNil(t, response.KeyID) + assert.NotNil(t, response.Id) case http.StatusBadRequest: var response CreateKey400JSONResponse require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) @@ -180,8 +180,8 @@ func TestServer_GetKey(t *testing.T) { case http.StatusCreated: var response GetKey200JSONResponse require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) - assert.NotNil(t, response.KeyID) - assert.Equal(t, keyID, response.KeyID) + assert.NotNil(t, response.Id) + assert.Equal(t, keyID, response.Id) assert.NotNil(t, response.PublicKey) assert.Equal(t, BJJ, response.KeyType) assert.False(t, response.IsAuthCoreClaim) @@ -260,13 +260,13 @@ func TestServer_GetKeys(t *testing.T) { case http.StatusCreated: var response GetKeys200JSONResponse require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) - assert.NotNil(t, response[0].KeyID) - assert.Equal(t, encodedKeyID1, response[0].KeyID) + assert.NotNil(t, response[0].Id) + assert.Equal(t, encodedKeyID1, response[0].Id) assert.NotNil(t, response[0].PublicKey) assert.Equal(t, BJJ, response[0].KeyType) assert.False(t, response[0].IsAuthCoreClaim) - assert.NotNil(t, response[1].KeyID) - assert.Equal(t, encodedKeyID2, response[1].KeyID) + assert.NotNil(t, response[1].Id) + assert.Equal(t, encodedKeyID2, response[1].Id) assert.NotNil(t, response[1].PublicKey) assert.Equal(t, BJJ, response[1].KeyType) assert.False(t, response[1].IsAuthCoreClaim) From fc197d1e3b1ca1944d2e653fbc1c5eb584d4c289 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Mon, 9 Dec 2024 09:24:45 -0300 Subject: [PATCH 15/62] chore: add pagination --- api/api.yaml | 32 +++++++++++++++++++-- internal/api/api.gen.go | 46 ++++++++++++++++++++++++++---- internal/api/keys.go | 38 ++++++++++++++++++++---- internal/api/keys_test.go | 23 ++++++++------- internal/core/ports/key_service.go | 9 ++++-- internal/core/services/key.go | 22 +++++++++++--- 6 files changed, 141 insertions(+), 29 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index c59ff0525..550f81efb 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -1385,15 +1385,29 @@ paths: - Key Management parameters: - $ref: '#/components/parameters/pathIdentifier2' + - in: query + name: max_results + schema: + type: integer + format: uint + example: 50 + default: 50 + description: Number of items to fetch on each page. Minimum is 10. Default is 50. No maximum by the moment. + - in: query + name: page + schema: + type: integer + format: uint + minimum: 1 + example: 1 + description: Page to fetch. First is one. If omitted, page 1 will be returned. responses: '200': description: Keys collection content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Key' + $ref: '#/components/schemas/KeysPaginated' '400': $ref: '#/components/responses/400' '404': @@ -2445,6 +2459,18 @@ components: x-omitempty: false example: true + KeysPaginated: + type: object + required: [ items, meta ] + properties: + items: + type: array + items: + $ref: '#/components/schemas/Key' + meta: + $ref: '#/components/schemas/PaginatedMetadata' + + parameters: credentialStatusType: name: credentialStatusType diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index a7ebf4412..bd8083128 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -439,6 +439,12 @@ type Key struct { // KeyKeyType defines model for Key.KeyType. type KeyKeyType string +// KeysPaginated defines model for KeysPaginated. +type KeysPaginated struct { + Items []Key `json:"items"` + Meta PaginatedMetadata `json:"meta"` +} + // Link defines model for Link. type Link struct { Active bool `json:"active"` @@ -761,6 +767,15 @@ type GetCredentialOfferParams struct { // GetCredentialOfferParamsType defines parameters for GetCredentialOffer. type GetCredentialOfferParamsType string +// GetKeysParams defines parameters for GetKeys. +type GetKeysParams struct { + // MaxResults Number of items to fetch on each page. Minimum is 10. Default is 50. No maximum by the moment. + MaxResults *uint `form:"max_results,omitempty" json:"max_results,omitempty"` + + // Page Page to fetch. First is one. If omitted, all results will be returned. + Page *uint `form:"page,omitempty" json:"page,omitempty"` +} + // GetSchemasParams defines parameters for GetSchemas. type GetSchemasParams struct { // Query Query string to do full text search in schema types and attributes. @@ -938,7 +953,7 @@ type ServerInterface interface { GetCredentialOffer(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, id PathClaim, params GetCredentialOfferParams) // Get Keys // (GET /v2/identities/{identifier}/keys) - GetKeys(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) + GetKeys(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, params GetKeysParams) // Create a Key // (POST /v2/identities/{identifier}/keys) CreateKey(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) @@ -1172,7 +1187,7 @@ func (_ Unimplemented) GetCredentialOffer(w http.ResponseWriter, r *http.Request // Get Keys // (GET /v2/identities/{identifier}/keys) -func (_ Unimplemented) GetKeys(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { +func (_ Unimplemented) GetKeys(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, params GetKeysParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -2437,8 +2452,27 @@ func (siw *ServerInterfaceWrapper) GetKeys(w http.ResponseWriter, r *http.Reques r = r.WithContext(ctx) + // Parameter object where we will unmarshal all parameters from the context + var params GetKeysParams + + // ------------- Optional query parameter "max_results" ------------- + + err = runtime.BindQueryParameter("form", true, false, "max_results", r.URL.Query(), ¶ms.MaxResults) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "max_results", Err: err}) + return + } + + // ------------- Optional query parameter "page" ------------- + + err = runtime.BindQueryParameter("form", true, false, "page", r.URL.Query(), ¶ms.Page) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "page", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetKeys(w, r, identifier) + siw.Handler.GetKeys(w, r, identifier, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4491,13 +4525,14 @@ func (response GetCredentialOffer500JSONResponse) VisitGetCredentialOfferRespons type GetKeysRequestObject struct { Identifier PathIdentifier2 `json:"identifier"` + Params GetKeysParams } type GetKeysResponseObject interface { VisitGetKeysResponse(w http.ResponseWriter) error } -type GetKeys200JSONResponse []Key +type GetKeys200JSONResponse KeysPaginated func (response GetKeys200JSONResponse) VisitGetKeysResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") @@ -6169,10 +6204,11 @@ func (sh *strictHandler) GetCredentialOffer(w http.ResponseWriter, r *http.Reque } // GetKeys operation middleware -func (sh *strictHandler) GetKeys(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2) { +func (sh *strictHandler) GetKeys(w http.ResponseWriter, r *http.Request, identifier PathIdentifier2, params GetKeysParams) { var request GetKeysRequestObject request.Identifier = identifier + request.Params = params handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { return sh.ssi.GetKeys(ctx, request.(GetKeysRequestObject)) diff --git a/internal/api/keys.go b/internal/api/keys.go index d1ff9c3d6..8f23e8ca8 100644 --- a/internal/api/keys.go +++ b/internal/api/keys.go @@ -84,7 +84,29 @@ func (s *Server) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKe // GetKeys is the handler for the GET /keys endpoint. func (s *Server) GetKeys(ctx context.Context, request GetKeysRequestObject) (GetKeysResponseObject, error) { - keys, err := s.keyService.GetAll(ctx, request.Identifier.did()) + const ( + defaultMaxResults = 50 + defaultPage = 1 + minimumMaxResults = 10 + ) + filter := ports.KeyFilter{ + MaxResults: defaultMaxResults, + Page: defaultPage, + } + + if request.Params.MaxResults != nil { + if *request.Params.MaxResults < minimumMaxResults { + filter.MaxResults = minimumMaxResults + } else { + filter.MaxResults = *request.Params.MaxResults + } + } + + if request.Params.Page != nil { + filter.Page = *request.Params.Page + } + + keys, total, err := s.keyService.GetAll(ctx, request.Identifier.did(), filter) if err != nil { log.Error(ctx, "getting keys", "err", err) return GetKeys500JSONResponse{ @@ -94,18 +116,24 @@ func (s *Server) GetKeys(ctx context.Context, request GetKeysRequestObject) (Get }, nil } - keysResponse := make(GetKeys200JSONResponse, 0) - + items := make([]Key, 0) for _, key := range keys { encodedKeyID := b64.StdEncoding.EncodeToString([]byte(key.KeyID)) - keysResponse = append(keysResponse, Key{ + items = append(items, Key{ Id: encodedKeyID, KeyType: KeyKeyType(key.KeyType), PublicKey: key.PublicKey, IsAuthCoreClaim: key.HasAssociatedAuthCoreClaim, }) } - return keysResponse, nil + return GetKeys200JSONResponse{ + Items: items, + Meta: PaginatedMetadata{ + Page: filter.Page, + MaxResults: filter.MaxResults, + Total: total, + }, + }, nil } // DeleteKey is the handler for the DELETE /keys/{keyID} endpoint. diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index c1a80ea34..e60591790 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -260,16 +260,19 @@ func TestServer_GetKeys(t *testing.T) { case http.StatusCreated: var response GetKeys200JSONResponse require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) - assert.NotNil(t, response[0].Id) - assert.Equal(t, encodedKeyID1, response[0].Id) - assert.NotNil(t, response[0].PublicKey) - assert.Equal(t, BJJ, response[0].KeyType) - assert.False(t, response[0].IsAuthCoreClaim) - assert.NotNil(t, response[1].Id) - assert.Equal(t, encodedKeyID2, response[1].Id) - assert.NotNil(t, response[1].PublicKey) - assert.Equal(t, BJJ, response[1].KeyType) - assert.False(t, response[1].IsAuthCoreClaim) + assert.NotNil(t, response.Items[0].Id) + assert.Equal(t, encodedKeyID1, response.Items[0].Id) + assert.NotNil(t, response.Items[0].PublicKey) + assert.Equal(t, BJJ, response.Items[0].KeyType) + assert.False(t, response.Items[0].IsAuthCoreClaim) + assert.NotNil(t, response.Items[1].Id) + assert.Equal(t, encodedKeyID2, response.Items[1].Id) + assert.NotNil(t, response.Items[1].PublicKey) + assert.Equal(t, BJJ, response.Items[1].KeyType) + assert.False(t, response.Items[1].IsAuthCoreClaim) + assert.Equal(t, uint(2), response.Meta.Total) + assert.Equal(t, uint(10), response.Meta.MaxResults) + assert.Equal(t, uint(1), response.Meta.Page) case http.StatusBadRequest: var response GetKeys400JSONResponse require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) diff --git a/internal/core/ports/key_service.go b/internal/core/ports/key_service.go index f3c41eb1f..73ecb5520 100644 --- a/internal/core/ports/key_service.go +++ b/internal/core/ports/key_service.go @@ -9,7 +9,6 @@ import ( "github.com/polygonid/sh-id-platform/internal/kms" ) -// ErrInvalidKeyType is returned when the key type is invalid var ( // ErrInvalidKeyType is returned when the key type is invalid ErrInvalidKeyType = errors.New("invalid key type") @@ -27,10 +26,16 @@ type KMSKey struct { HasAssociatedAuthCoreClaim bool } +// KeyFilter is the filter to use when getting keys +type KeyFilter struct { + MaxResults uint // Max number of results to return on each call. + Page uint // Page number to return. First is 1. +} + // KeyService is the service that manages keys type KeyService interface { CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) Get(ctx context.Context, did *w3c.DID, keyID string) (*KMSKey, error) - GetAll(ctx context.Context, did *w3c.DID) ([]*KMSKey, error) + GetAll(ctx context.Context, did *w3c.DID, filter KeyFilter) ([]*KMSKey, uint, error) Delete(ctx context.Context, did *w3c.DID, keyID string) error } diff --git a/internal/core/services/key.go b/internal/core/services/key.go index 109fb2201..a23e51235 100644 --- a/internal/core/services/key.go +++ b/internal/core/services/key.go @@ -101,23 +101,37 @@ func (ks *Key) Get(ctx context.Context, did *w3c.DID, keyID string) (*ports.KMSK } // GetAll returns all the keys for the given DID -func (ks *Key) GetAll(ctx context.Context, did *w3c.DID) ([]*ports.KMSKey, error) { +func (ks *Key) GetAll(ctx context.Context, did *w3c.DID, filter ports.KeyFilter) ([]*ports.KMSKey, uint, error) { keyIDs, err := ks.kms.KeysByIdentity(ctx, *did) if err != nil { log.Error(ctx, "failed to get keys", "err", err) - return nil, err + return nil, 0, err + } + + total := uint(len(keyIDs)) + + start := (int(filter.Page) - 1) * int(filter.MaxResults) + end := start + int(filter.MaxResults) + + if start >= len(keyIDs) { + return []*ports.KMSKey{}, 0, nil + } + + if end > len(keyIDs) { + end = len(keyIDs) } + keyIDs = keyIDs[start:end] keys := make([]*ports.KMSKey, len(keyIDs)) for i, keyID := range keyIDs { key, err := ks.Get(ctx, did, keyID.ID) if err != nil { log.Error(ctx, "failed to get key", "err", err) - return nil, err + return nil, 0, err } keys[i] = key } - return keys, nil + return keys, total, nil } // Delete deletes the key with the given keyID From 6367e05eff2372f565d1fd3ac1bb38a8803f535c Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Mon, 9 Dec 2024 12:46:46 -0300 Subject: [PATCH 16/62] chore: add validations for eth keys --- api/api.yaml | 4 +- internal/api/api.gen.go | 10 +- internal/api/keys.go | 27 ++-- internal/api/keys_test.go | 183 +++++++++++++++--------- internal/core/ports/key_service.go | 10 +- internal/core/services/identity.go | 1 - internal/core/services/identity_test.go | 11 ++ internal/core/services/key.go | 111 +++++++++++--- 8 files changed, 249 insertions(+), 108 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index 550f81efb..f2e0aa6ec 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -2438,7 +2438,7 @@ components: - id - keyType - publicKey - - isAuthCoreClaim + - isAuthCredential properties: id: type: string @@ -2454,7 +2454,7 @@ components: type: string x-omitempty: false example: "0x04e3e7e" - isAuthCoreClaim: + isAuthCredential: type: boolean x-omitempty: false example: true diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index bd8083128..2414c8e36 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -430,10 +430,10 @@ type IssuerDescription struct { // Key defines model for Key. type Key struct { // Id base64 encoded keyID - Id string `json:"id"` - IsAuthCoreClaim bool `json:"isAuthCoreClaim"` - KeyType KeyKeyType `json:"keyType"` - PublicKey string `json:"publicKey"` + Id string `json:"id"` + IsAuthCredential bool `json:"isAuthCredential"` + KeyType KeyKeyType `json:"keyType"` + PublicKey string `json:"publicKey"` } // KeyKeyType defines model for Key.KeyType. @@ -772,7 +772,7 @@ type GetKeysParams struct { // MaxResults Number of items to fetch on each page. Minimum is 10. Default is 50. No maximum by the moment. MaxResults *uint `form:"max_results,omitempty" json:"max_results,omitempty"` - // Page Page to fetch. First is one. If omitted, all results will be returned. + // Page Page to fetch. First is one. If omitted, page 1 will be returned. Page *uint `form:"page,omitempty" json:"page,omitempty"` } diff --git a/internal/api/keys.go b/internal/api/keys.go index 8f23e8ca8..abb526f01 100644 --- a/internal/api/keys.go +++ b/internal/api/keys.go @@ -75,10 +75,10 @@ func (s *Server) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKe encodedKeyID := b64.StdEncoding.EncodeToString([]byte(key.KeyID)) return GetKey200JSONResponse{ - Id: encodedKeyID, - KeyType: KeyKeyType(key.KeyType), - PublicKey: key.PublicKey, - IsAuthCoreClaim: key.HasAssociatedAuthCoreClaim, + Id: encodedKeyID, + KeyType: KeyKeyType(key.KeyType), + PublicKey: key.PublicKey, + IsAuthCredential: key.HasAssociatedAuthCredential, }, nil } @@ -120,10 +120,10 @@ func (s *Server) GetKeys(ctx context.Context, request GetKeysRequestObject) (Get for _, key := range keys { encodedKeyID := b64.StdEncoding.EncodeToString([]byte(key.KeyID)) items = append(items, Key{ - Id: encodedKeyID, - KeyType: KeyKeyType(key.KeyType), - PublicKey: key.PublicKey, - IsAuthCoreClaim: key.HasAssociatedAuthCoreClaim, + Id: encodedKeyID, + KeyType: KeyKeyType(key.KeyType), + PublicKey: key.PublicKey, + IsAuthCredential: key.HasAssociatedAuthCredential, }) } return GetKeys200JSONResponse{ @@ -151,7 +151,7 @@ func (s *Server) DeleteKey(ctx context.Context, request DeleteKeyRequestObject) err = s.keyService.Delete(ctx, request.Identifier.did(), string(decodedKeyID)) if err != nil { if errors.Is(err, ports.ErrAuthCredentialNotRevoked) { - log.Error(ctx, "delete key. Auth core claim not revoked", "err", err) + log.Error(ctx, "delete key. Auth credential not revoked", "err", err) return DeleteKey400JSONResponse{ N400JSONResponse{ Message: "associated auth credential is not revoked", @@ -159,6 +159,15 @@ func (s *Server) DeleteKey(ctx context.Context, request DeleteKeyRequestObject) }, nil } + if errors.Is(err, ports.ErrKeyAssociatedWithIdentity) { + log.Error(ctx, "delete key. Key associated with identity", "err", err) + return DeleteKey400JSONResponse{ + N400JSONResponse{ + Message: "key is associated with an identity", + }, + }, nil + } + if errors.Is(err, ports.ErrKeyNotFound) { log.Error(ctx, "key not found", "err", err) return DeleteKey404JSONResponse{ diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index e60591790..30a278a62 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -184,7 +184,7 @@ func TestServer_GetKey(t *testing.T) { assert.Equal(t, keyID, response.Id) assert.NotNil(t, response.PublicKey) assert.Equal(t, BJJ, response.KeyType) - assert.False(t, response.IsAuthCoreClaim) + assert.False(t, response.IsAuthCredential) case http.StatusBadRequest: var response GetKey400JSONResponse require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) @@ -200,6 +200,7 @@ func TestServer_GetKeys(t *testing.T) { blockchain = "polygon" network = "amoy" BJJ = "BJJ" + ETH = "ETH" ) ctx := context.Background() server := newTestServer(t, nil) @@ -209,77 +210,60 @@ func TestServer_GetKeys(t *testing.T) { did, err := w3c.ParseDID(iden.Identifier) require.NoError(t, err) - keyID1, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) + _, err = server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) require.NoError(t, err) - keyID2, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) + _, err = server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) require.NoError(t, err) - encodedKeyID1 := b64.StdEncoding.EncodeToString([]byte(keyID1.ID)) - encodedKeyID2 := b64.StdEncoding.EncodeToString([]byte(keyID2.ID)) + idenETH, err := server.Services.identity.Create(ctx, "polygon-test", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: ETH}) + require.NoError(t, err) + didETH, err := w3c.ParseDID(idenETH.Identifier) + require.NoError(t, err) handler := getHandler(ctx, server) - type expected struct { - response GetKeysResponseObject - httpCode int - } - - type testConfig struct { - name string - auth func() (string, string) - expected expected - } - - for _, tc := range []testConfig{ - { - name: "no auth header", - auth: authWrong, - expected: expected{ - httpCode: http.StatusUnauthorized, - }, - }, - { - name: "should get the keys", - auth: authOk, - expected: expected{ - httpCode: http.StatusOK, - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - rr := httptest.NewRecorder() - url := fmt.Sprintf("/v2/identities/%s/keys", did) - req, err := http.NewRequest(http.MethodGet, url, nil) - req.SetBasicAuth(tc.auth()) - require.NoError(t, err) - handler.ServeHTTP(rr, req) - require.Equal(t, tc.expected.httpCode, rr.Code) - - switch tc.expected.httpCode { - case http.StatusCreated: - var response GetKeys200JSONResponse - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) - assert.NotNil(t, response.Items[0].Id) - assert.Equal(t, encodedKeyID1, response.Items[0].Id) - assert.NotNil(t, response.Items[0].PublicKey) - assert.Equal(t, BJJ, response.Items[0].KeyType) - assert.False(t, response.Items[0].IsAuthCoreClaim) - assert.NotNil(t, response.Items[1].Id) - assert.Equal(t, encodedKeyID2, response.Items[1].Id) - assert.NotNil(t, response.Items[1].PublicKey) - assert.Equal(t, BJJ, response.Items[1].KeyType) - assert.False(t, response.Items[1].IsAuthCoreClaim) - assert.Equal(t, uint(2), response.Meta.Total) - assert.Equal(t, uint(10), response.Meta.MaxResults) - assert.Equal(t, uint(1), response.Meta.Page) - case http.StatusBadRequest: - var response GetKeys400JSONResponse - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) - assert.EqualValues(t, tc.expected.response, response) - } - }) - } + t.Run("should get an error", func(t *testing.T) { + rr := httptest.NewRecorder() + url := fmt.Sprintf("/v2/identities/%s/keys", did) + req, err := http.NewRequest(http.MethodGet, url, nil) + req.SetBasicAuth(authWrong()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, http.StatusUnauthorized, rr.Code) + }) + + t.Run("should get the keys for bjj identity", func(t *testing.T) { + rr := httptest.NewRecorder() + url := fmt.Sprintf("/v2/identities/%s/keys", did) + req, err := http.NewRequest(http.MethodGet, url, nil) + req.SetBasicAuth(authOk()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, http.StatusOK, rr.Code) + + var response GetKeys200JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.Equal(t, uint(3), response.Meta.Total) + assert.Equal(t, uint(50), response.Meta.MaxResults) + assert.Equal(t, uint(1), response.Meta.Page) + }) + + t.Run("should get the keys for eth identity", func(t *testing.T) { + rr := httptest.NewRecorder() + url := fmt.Sprintf("/v2/identities/%s/keys", didETH) + req, err := http.NewRequest(http.MethodGet, url, nil) + req.SetBasicAuth(authOk()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, http.StatusOK, rr.Code) + + var response GetKeys200JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.Equal(t, uint(2), response.Meta.Total) + assert.Equal(t, uint(50), response.Meta.MaxResults) + assert.Equal(t, uint(1), response.Meta.Page) + }) } func TestServer_DeleteKey(t *testing.T) { @@ -288,6 +272,7 @@ func TestServer_DeleteKey(t *testing.T) { blockchain = "polygon" network = "amoy" BJJ = "BJJ" + ETH = "ETH" ) ctx := context.Background() server := newTestServer(t, nil) @@ -297,6 +282,28 @@ func TestServer_DeleteKey(t *testing.T) { did, err := w3c.ParseDID(iden.Identifier) require.NoError(t, err) + idenETH, err := server.Services.identity.Create(ctx, "polygon-test", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: ETH}) + require.NoError(t, err) + didETH, err := w3c.ParseDID(idenETH.Identifier) + require.NoError(t, err) + + idenETHKeys, _, err := server.keyService.GetAll(ctx, didETH, ports.KeyFilter{MaxResults: 10, Page: 1}) + require.NoError(t, err) + assert.Equal(t, len(idenETHKeys), 2) + + idenETHBJJKey := "" + idenETHETHKey := "" + if idenETHKeys[0].KeyType == kms.KeyTypeBabyJubJub { + idenETHBJJKey = idenETHKeys[0].KeyID + idenETHETHKey = idenETHKeys[1].KeyID + } else { + idenETHBJJKey = idenETHKeys[1].KeyID + idenETHETHKey = idenETHKeys[0].KeyID + } + + keyETHIDToDelete, err := server.keyService.CreateKey(ctx, didETH, kms.KeyTypeEthereum) + require.NoError(t, err) + keyID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) require.NoError(t, err) @@ -318,6 +325,7 @@ func TestServer_DeleteKey(t *testing.T) { type testConfig struct { name string + did string auth func() (string, string) KeyID string expected expected @@ -327,22 +335,34 @@ func TestServer_DeleteKey(t *testing.T) { { name: "no auth header", auth: authWrong, + did: did.String(), KeyID: keyID.ID, expected: expected{ httpCode: http.StatusUnauthorized, }, }, { - name: "should delete a key", + name: "should delete the bjj key", auth: authOk, + did: did.String(), KeyID: keyID.ID, expected: expected{ httpCode: http.StatusOK, }, }, + { + name: "should delete the eth key", + auth: authOk, + did: didETH.String(), + KeyID: keyETHIDToDelete.ID, + expected: expected{ + httpCode: http.StatusOK, + }, + }, { name: "should get an error - wrong keyID", auth: authOk, + did: did.String(), KeyID: "123", expected: expected{ httpCode: http.StatusBadRequest, @@ -356,6 +376,7 @@ func TestServer_DeleteKey(t *testing.T) { { name: "should get an error - associated auth credential is not revoked", auth: authOk, + did: did.String(), KeyID: keyIDForAuthCoreClaimID.ID, expected: expected{ httpCode: http.StatusBadRequest, @@ -366,10 +387,38 @@ func TestServer_DeleteKey(t *testing.T) { }, }, }, + { + name: "should get an error key is associated with an identity", + auth: authOk, + did: didETH.String(), + KeyID: b64.StdEncoding.EncodeToString([]byte(idenETHETHKey)), + expected: expected{ + httpCode: http.StatusBadRequest, + response: DeleteKey400JSONResponse{ + N400JSONResponse: N400JSONResponse{ + Message: "key is associated with an identity", + }, + }, + }, + }, + { + name: "should get an error associated auth credential is not revoked ", + auth: authOk, + did: didETH.String(), + KeyID: b64.StdEncoding.EncodeToString([]byte(idenETHBJJKey)), + expected: expected{ + httpCode: http.StatusBadRequest, + response: DeleteKey400JSONResponse{ + N400JSONResponse: N400JSONResponse{ + Message: "associated auth credential is not revoked", + }, + }, + }, + }, } { t.Run(tc.name, func(t *testing.T) { rr := httptest.NewRecorder() - url := fmt.Sprintf("/v2/identities/%s/keys/%s", did, tc.KeyID) + url := fmt.Sprintf("/v2/identities/%s/keys/%s", tc.did, tc.KeyID) req, err := http.NewRequest(http.MethodDelete, url, nil) req.SetBasicAuth(tc.auth()) require.NoError(t, err) diff --git a/internal/core/ports/key_service.go b/internal/core/ports/key_service.go index 73ecb5520..eef260aeb 100644 --- a/internal/core/ports/key_service.go +++ b/internal/core/ports/key_service.go @@ -16,14 +16,16 @@ var ( ErrAuthCredentialNotRevoked = errors.New("associated auth core claim not revoked") // ErrKeyNotFound is returned when the key is not found ErrKeyNotFound = errors.New("key not found") + // ErrKeyAssociatedWithIdentity is returned when the key is associated with an identity + ErrKeyAssociatedWithIdentity = errors.New("key is associated with an identity") ) // KMSKey is the struct that represents a key type KMSKey struct { - KeyID string - KeyType kms.KeyType - PublicKey string - HasAssociatedAuthCoreClaim bool + KeyID string + KeyType kms.KeyType + PublicKey string + HasAssociatedAuthCredential bool } // KeyFilter is the filter to use when getting keys diff --git a/internal/core/services/identity.go b/internal/core/services/identity.go index ac0e0403d..ddb51a92e 100644 --- a/internal/core/services/identity.go +++ b/internal/core/services/identity.go @@ -51,7 +51,6 @@ const ( authReason = "authentication" ) -// ErrWrongDIDMetada - represents an error in the identity metadata var ( // 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") diff --git a/internal/core/services/identity_test.go b/internal/core/services/identity_test.go index 5e18d722d..d1004c9fc 100644 --- a/internal/core/services/identity_test.go +++ b/internal/core/services/identity_test.go @@ -100,6 +100,17 @@ func Test_identity_CreateIdentity(t *testing.T) { } else { t.Errorf("invalid key type") } + + DID, err := w3c.ParseDID(identity.Identifier) + require.NoError(t, err) + isEthID, address, err := common.CheckEthIdentityByDID(DID) + require.NoError(t, err) + if tc.options.KeyType == ETH { + assert.True(t, isEthID) + assert.Equal(t, address, *identity.Address) + } else { + assert.False(t, isEthID) + } } }) } diff --git a/internal/core/services/key.go b/internal/core/services/key.go index a23e51235..946793486 100644 --- a/internal/core/services/key.go +++ b/internal/core/services/key.go @@ -6,8 +6,11 @@ import ( "strings" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" "github.com/iden3/go-iden3-core/v2/w3c" + "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/kms" "github.com/polygonid/sh-id-platform/internal/log" @@ -87,16 +90,29 @@ func (ks *Key) Get(ctx context.Context, did *w3c.DID, keyID string) (*ports.KMSK return nil, ports.ErrKeyNotFound } - authCoreClaim, err := ks.claimService.GetAuthCredentialWithPublicKey(ctx, did, publicKey) - if err != nil { - log.Error(ctx, "failed to check if key has associated auth credential", "err", err) - return nil, err + hasAssociatedAuthCredential := false + switch keyType { + case kms.KeyTypeBabyJubJub: + hasAssociatedAuthCredential, _, err = ks.hasAssociatedAuthCredential(ctx, did, publicKey) + if err != nil { + log.Error(ctx, "failed to check if key has associated auth credential", "err", err) + return nil, err + } + case kms.KeyTypeEthereum: + hasAssociatedAuthCredential, err = ks.isAssociatedWithIdentity(ctx, did, publicKey) + if err != nil { + log.Error(ctx, "failed to check if key has associated auth credential", "err", err) + return nil, err + } + default: + return nil, ports.ErrInvalidKeyType } + return &ports.KMSKey{ - KeyID: keyID, - KeyType: keyType, - PublicKey: hexutil.Encode(publicKey), - HasAssociatedAuthCoreClaim: authCoreClaim != nil, + KeyID: keyID, + KeyType: keyType, + PublicKey: hexutil.Encode(publicKey), + HasAssociatedAuthCredential: hasAssociatedAuthCredential, }, nil } @@ -162,25 +178,44 @@ func (ks *Key) Delete(ctx context.Context, did *w3c.DID, keyID string) error { log.Error(ctx, "failed to get public key", "err", err) return err } - authCredential, err := ks.claimService.GetAuthCredentialWithPublicKey(ctx, did, publicKey) - if err != nil { - log.Error(ctx, "failed to check if key has associated auth credential", "err", err) - return err - } - if authCredential != nil { - log.Info(ctx, "can not be deleted because it has an associated auth credential. Have to check revocation status") - revStatus, err := ks.claimService.GetRevocationStatus(ctx, *did, uint64(authCredential.RevNonce)) + hasAssociatedAuthCoreCredential := false + var authCredential *domain.Claim + switch keyType { + case kms.KeyTypeBabyJubJub: + hasAssociatedAuthCoreCredential, authCredential, err = ks.hasAssociatedAuthCredential(ctx, did, publicKey) if err != nil { - log.Error(ctx, "failed to get revocation status", "err", err) + log.Error(ctx, "failed to check if key has associated auth credential", "err", err) return err } - if revStatus != nil && !revStatus.MTP.Existence { - log.Info(ctx, "auth credential is non revoked. Can not be deleted") - return ports.ErrAuthCredentialNotRevoked + if hasAssociatedAuthCoreCredential { + log.Info(ctx, "can not be deleted because it has an associated auth credential. Have to check revocation status") + revStatus, err := ks.claimService.GetRevocationStatus(ctx, *did, uint64(authCredential.RevNonce)) + if err != nil { + log.Error(ctx, "failed to get revocation status", "err", err) + return err + } + + if revStatus != nil && !revStatus.MTP.Existence { + log.Info(ctx, "auth credential is non revoked. Can not be deleted") + return ports.ErrAuthCredentialNotRevoked + } + } + case kms.KeyTypeEthereum: + hasAssociatedAuthCoreCredential, err = ks.isAssociatedWithIdentity(ctx, did, publicKey) + if err != nil { + log.Error(ctx, "failed to check if key has associated auth credential", "err", err) + return err + } + if hasAssociatedAuthCoreCredential { + log.Info(ctx, "can not be deleted because it is associated with the identity") + return ports.ErrKeyAssociatedWithIdentity } + default: + return ports.ErrInvalidKeyType } + return ks.kms.Delete(ctx, kmsKeyID) } @@ -212,3 +247,39 @@ func getKeyType(keyID string) (kms.KeyType, error) { return keyType, nil } + +func (ks *Key) hasAssociatedAuthCredential(ctx context.Context, did *w3c.DID, publicKey []byte) (bool, *domain.Claim, error) { + hasAssociatedAuthCoreClaim := false + authCoreClaim, err := ks.claimService.GetAuthCredentialWithPublicKey(ctx, did, publicKey) + if err != nil { + log.Error(ctx, "failed to check if key has associated auth credential", "err", err) + return false, nil, err + } + hasAssociatedAuthCoreClaim = authCoreClaim != nil + return hasAssociatedAuthCoreClaim, authCoreClaim, nil +} + +func (ks *Key) isAssociatedWithIdentity(ctx context.Context, did *w3c.DID, publicKey []byte) (bool, error) { + hasAssociatedAuthCoreClaim := false + pubKey, err := crypto.DecompressPubkey(publicKey) + if err != nil { + log.Error(ctx, "failed to decompress public key", "err", err) + return false, err + } + + keyETHAddress := crypto.PubkeyToAddress(*pubKey) + isEthAddress, identityAddress, err := common.CheckEthIdentityByDID(did) + if err != nil { + log.Error(ctx, "failed to check if DID is ETH identity", "err", err) + return false, err + } + + identityAddressToBeChecked := strings.ToUpper("0x" + identityAddress) + if isEthAddress { + hasAssociatedAuthCoreClaim = identityAddressToBeChecked == strings.ToUpper(keyETHAddress.Hex()) + } else { + hasAssociatedAuthCoreClaim = false + } + + return hasAssociatedAuthCoreClaim, nil +} From 3134d120d8a2e514c55722c0a7c9b0a032701e7d Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Mon, 9 Dec 2024 14:55:09 -0300 Subject: [PATCH 17/62] chore: add test and fix key sort --- internal/api/keys_test.go | 98 ++++++++++++++++++++++++++++++++--- internal/core/services/key.go | 6 ++- 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index 30a278a62..ab8738f70 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -210,12 +210,6 @@ func TestServer_GetKeys(t *testing.T) { did, err := w3c.ParseDID(iden.Identifier) require.NoError(t, err) - _, err = server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) - require.NoError(t, err) - - _, err = server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) - require.NoError(t, err) - idenETH, err := server.Services.identity.Create(ctx, "polygon-test", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: ETH}) require.NoError(t, err) didETH, err := w3c.ParseDID(idenETH.Identifier) @@ -244,9 +238,48 @@ func TestServer_GetKeys(t *testing.T) { var response GetKeys200JSONResponse require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) - assert.Equal(t, uint(3), response.Meta.Total) + assert.Equal(t, uint(1), response.Meta.Total) assert.Equal(t, uint(50), response.Meta.MaxResults) assert.Equal(t, uint(1), response.Meta.Page) + assert.Equal(t, 1, countAuthCredentials(t, response.Items)) + }) + + t.Run("should get the keys for bjj identity with pagination", func(t *testing.T) { + for i := 0; i < 20; i++ { + _, err = server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) + require.NoError(t, err) + } + + rr := httptest.NewRecorder() + url := fmt.Sprintf("/v2/identities/%s/keys?max_results=11&page=1", did) + req, err := http.NewRequest(http.MethodGet, url, nil) + req.SetBasicAuth(authOk()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, http.StatusOK, rr.Code) + + var response GetKeys200JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.Equal(t, uint(21), response.Meta.Total) + assert.Equal(t, uint(11), response.Meta.MaxResults) + assert.Equal(t, uint(1), response.Meta.Page) + + rr = httptest.NewRecorder() + url = fmt.Sprintf("/v2/identities/%s/keys?max_results=11&page=2", did) + req, err = http.NewRequest(http.MethodGet, url, nil) + req.SetBasicAuth(authOk()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, http.StatusOK, rr.Code) + + var response1 GetKeys200JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response1)) + assert.Equal(t, uint(21), response1.Meta.Total) + assert.Equal(t, uint(11), response1.Meta.MaxResults) + assert.Equal(t, uint(2), response1.Meta.Page) + + all := append(response.Items, response1.Items...) + assert.Equal(t, 1, countAuthCredentials(t, all)) }) t.Run("should get the keys for eth identity", func(t *testing.T) { @@ -263,7 +296,58 @@ func TestServer_GetKeys(t *testing.T) { assert.Equal(t, uint(2), response.Meta.Total) assert.Equal(t, uint(50), response.Meta.MaxResults) assert.Equal(t, uint(1), response.Meta.Page) + + assert.Equal(t, 2, countAuthCredentials(t, response.Items)) }) + + t.Run("should get the keys for eth identity with pagination", func(t *testing.T) { + for i := 0; i < 20; i++ { + _, err = server.keyService.CreateKey(ctx, didETH, kms.KeyTypeBabyJubJub) + require.NoError(t, err) + } + + rr := httptest.NewRecorder() + url := fmt.Sprintf("/v2/identities/%s/keys?max_results=10&page=1", didETH) + req, err := http.NewRequest(http.MethodGet, url, nil) + req.SetBasicAuth(authOk()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, http.StatusOK, rr.Code) + + var response GetKeys200JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.Equal(t, uint(22), response.Meta.Total) + assert.Equal(t, uint(10), response.Meta.MaxResults) + assert.Equal(t, uint(1), response.Meta.Page) + + rr = httptest.NewRecorder() + url = fmt.Sprintf("/v2/identities/%s/keys?max_results=15&page=2", didETH) + req, err = http.NewRequest(http.MethodGet, url, nil) + req.SetBasicAuth(authOk()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, http.StatusOK, rr.Code) + + var response1 GetKeys200JSONResponse + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response1)) + assert.Equal(t, uint(22), response1.Meta.Total) + assert.Equal(t, uint(15), response1.Meta.MaxResults) + assert.Equal(t, uint(2), response1.Meta.Page) + + all := append(response.Items, response1.Items...) + assert.Equal(t, 2, countAuthCredentials(t, all)) + }) +} + +func countAuthCredentials(t *testing.T, keys []Key) int { + t.Helper() + count := 0 + for _, key := range keys { + if key.IsAuthCredential { + count++ + } + } + return count } func TestServer_DeleteKey(t *testing.T) { diff --git a/internal/core/services/key.go b/internal/core/services/key.go index 946793486..286ee2a12 100644 --- a/internal/core/services/key.go +++ b/internal/core/services/key.go @@ -3,6 +3,7 @@ package services import ( "context" b64 "encoding/base64" + "sort" "strings" "github.com/ethereum/go-ethereum/common/hexutil" @@ -125,7 +126,6 @@ func (ks *Key) GetAll(ctx context.Context, did *w3c.DID, filter ports.KeyFilter) } total := uint(len(keyIDs)) - start := (int(filter.Page) - 1) * int(filter.MaxResults) end := start + int(filter.MaxResults) @@ -137,6 +137,10 @@ func (ks *Key) GetAll(ctx context.Context, did *w3c.DID, filter ports.KeyFilter) end = len(keyIDs) } + sort.Slice(keyIDs, func(i, j int) bool { + return keyIDs[i].ID < keyIDs[j].ID + }) + keyIDs = keyIDs[start:end] keys := make([]*ports.KMSKey, len(keyIDs)) for i, keyID := range keyIDs { From 0c984e4372d7fbbe86c9af3ad0afa7a602e30918 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Mon, 9 Dec 2024 15:06:32 -0300 Subject: [PATCH 18/62] fix: test --- internal/api/keys_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index ab8738f70..b2b97a9ad 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -307,7 +307,7 @@ func TestServer_GetKeys(t *testing.T) { } rr := httptest.NewRecorder() - url := fmt.Sprintf("/v2/identities/%s/keys?max_results=10&page=1", didETH) + url := fmt.Sprintf("/v2/identities/%s/keys?max_results=15&page=1", didETH) req, err := http.NewRequest(http.MethodGet, url, nil) req.SetBasicAuth(authOk()) require.NoError(t, err) @@ -317,7 +317,7 @@ func TestServer_GetKeys(t *testing.T) { var response GetKeys200JSONResponse require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) assert.Equal(t, uint(22), response.Meta.Total) - assert.Equal(t, uint(10), response.Meta.MaxResults) + assert.Equal(t, uint(15), response.Meta.MaxResults) assert.Equal(t, uint(1), response.Meta.Page) rr = httptest.NewRecorder() From fb713dec0fbfd70b28c82c329339c2e2639c0bd7 Mon Sep 17 00:00:00 2001 From: Oleksandr Raspopov Date: Tue, 10 Dec 2024 09:26:15 +0100 Subject: [PATCH 19/62] chore: ui for keys handling --- ui/src/adapters/api/keys.ts | 108 ++++++++++ ui/src/components/keys/CreateKey.tsx | 77 +++++++ ui/src/components/keys/Key.tsx | 112 ++++++++++ ui/src/components/keys/Keys.tsx | 32 +++ ui/src/components/keys/KeysTable.tsx | 274 +++++++++++++++++++++++++ ui/src/components/shared/Detail.tsx | 4 +- ui/src/components/shared/Router.tsx | 6 + ui/src/components/shared/SiderMenu.tsx | 16 ++ ui/src/domain/index.ts | 3 + ui/src/domain/key.ts | 11 + ui/src/routes.ts | 17 +- ui/src/styles/index.scss | 10 - ui/src/utils/constants.ts | 4 + 13 files changed, 662 insertions(+), 12 deletions(-) create mode 100644 ui/src/adapters/api/keys.ts create mode 100644 ui/src/components/keys/CreateKey.tsx create mode 100644 ui/src/components/keys/Key.tsx create mode 100644 ui/src/components/keys/Keys.tsx create mode 100644 ui/src/components/keys/KeysTable.tsx create mode 100644 ui/src/domain/key.ts diff --git a/ui/src/adapters/api/keys.ts b/ui/src/adapters/api/keys.ts new file mode 100644 index 000000000..20bc18c65 --- /dev/null +++ b/ui/src/adapters/api/keys.ts @@ -0,0 +1,108 @@ +import axios from "axios"; +import { z } from "zod"; + +import { Response, buildErrorResponse, buildSuccessResponse } from "src/adapters"; +import { ID, IDParser, buildAuthorizationHeader } from "src/adapters/api"; +import { getResourceParser, getStrictParser } from "src/adapters/parsers"; +import { Env, Key, KeyType } from "src/domain"; +import { API_VERSION } from "src/utils/constants"; +import { Resource } from "src/utils/types"; + +const keyParser = getStrictParser()( + z.object({ + id: z.string(), + isAuthCoreClaim: z.boolean(), + keyType: z.nativeEnum(KeyType), + publicKey: z.string(), + }) +); + +export async function getKeys({ + env, + identifier, + params: { maxResults, page }, + signal, +}: { + env: Env; + identifier: string; + params: { + maxResults?: number; + page?: number; + }; + signal?: AbortSignal; +}): Promise>> { + try { + const response = await axios({ + baseURL: env.api.url, + headers: { + Authorization: buildAuthorizationHeader(env), + }, + method: "GET", + params: new URLSearchParams({ + ...(maxResults !== undefined ? { max_results: maxResults.toString() } : {}), + ...(page !== undefined ? { page: page.toString() } : {}), + }), + signal, + url: `${API_VERSION}/identities/${identifier}/keys`, + }); + return buildSuccessResponse(getResourceParser(keyParser).parse(response.data)); + } catch (error) { + return buildErrorResponse(error); + } +} + +export async function getKey({ + env, + identifier, + keyID, + signal, +}: { + env: Env; + identifier: string; + keyID: string; + signal: AbortSignal; +}): Promise> { + try { + const response = await axios({ + baseURL: env.api.url, + headers: { + Authorization: buildAuthorizationHeader(env), + }, + method: "GET", + signal, + url: `${API_VERSION}/identities/${identifier}/keys/${keyID}`, + }); + return buildSuccessResponse(keyParser.parse(response.data)); + } catch (error) { + return buildErrorResponse(error); + } +} + +export type CreateKey = { + keyType: KeyType; +}; + +export async function createKey({ + env, + identifier, + payload, +}: { + env: Env; + identifier: string; + payload: CreateKey; +}): Promise> { + try { + const response = await axios({ + baseURL: env.api.url, + data: payload, + headers: { + Authorization: buildAuthorizationHeader(env), + }, + method: "POST", + url: `${API_VERSION}/identities/${identifier}/keys`, + }); + return buildSuccessResponse(IDParser.parse(response.data)); + } catch (error) { + return buildErrorResponse(error); + } +} diff --git a/ui/src/components/keys/CreateKey.tsx b/ui/src/components/keys/CreateKey.tsx new file mode 100644 index 000000000..9947a3da9 --- /dev/null +++ b/ui/src/components/keys/CreateKey.tsx @@ -0,0 +1,77 @@ +import { App, Button, Card, Divider, Flex, Form, Select, Space } from "antd"; +import { useNavigate } from "react-router-dom"; + +import { CreateKey as CreateKeyType, createKey } from "src/adapters/api/keys"; +import { SiderLayoutContent } from "src/components/shared/SiderLayoutContent"; +import { useEnvContext } from "src/contexts/Env"; +import { useIdentityContext } from "src/contexts/Identity"; +import { KeyType } from "src/domain"; +import { ROUTES } from "src/routes"; +import { KEY_ADD_NEW, VALUE_REQUIRED } from "src/utils/constants"; + +export function CreateKey() { + const env = useEnvContext(); + const { identifier } = useIdentityContext(); + const [form] = Form.useForm(); + const navigate = useNavigate(); + const { message } = App.useApp(); + + const handleSubmit = (formValues: CreateKeyType) => { + return void createKey({ + env, + identifier, + payload: formValues, + }).then((response) => { + if (response.success) { + void message.success("Key added successfully"); + navigate(ROUTES.keys.path); + } else { + void message.error(response.error.message); + } + }); + }; + + return ( + + + +
+ + + + + + + + + + +
+
+
+ ); +} diff --git a/ui/src/components/keys/Key.tsx b/ui/src/components/keys/Key.tsx new file mode 100644 index 000000000..f1eb8a16e --- /dev/null +++ b/ui/src/components/keys/Key.tsx @@ -0,0 +1,112 @@ +import { Card, Space, Typography } from "antd"; +import { useCallback, useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; + +import { useIdentityContext } from "../../contexts/Identity"; +import { getKey } from "src/adapters/api/keys"; +import {} from "src/adapters/parsers/view"; +import { Detail } from "src/components/shared/Detail"; +import { ErrorResult } from "src/components/shared/ErrorResult"; +import { LoadingResult } from "src/components/shared/LoadingResult"; +import { SiderLayoutContent } from "src/components/shared/SiderLayoutContent"; +import { useEnvContext } from "src/contexts/Env"; +import { AppError, Key as KeyType } from "src/domain"; +import { AsyncTask, hasAsyncTaskFailed, isAsyncTaskStarting } from "src/utils/async"; +import { isAbortedError, makeRequestAbortable } from "src/utils/browser"; +import { KEY_DETAILS } from "src/utils/constants"; + +export function Key() { + const env = useEnvContext(); + const { identifier } = useIdentityContext(); + + const [key, setKey] = useState>({ + status: "pending", + }); + + const { keyID } = useParams(); + + const fetchKey = useCallback( + async (signal: AbortSignal) => { + if (keyID) { + setKey({ status: "loading" }); + + const response = await getKey({ + env, + identifier, + keyID, + signal, + }); + + if (response.success) { + setKey({ data: response.data, status: "successful" }); + } else { + if (!isAbortedError(response.error)) { + setKey({ error: response.error, status: "failed" }); + } + } + } + }, + [env, keyID, identifier] + ); + + useEffect(() => { + const { aborter } = makeRequestAbortable(fetchKey); + + return aborter; + }, [fetchKey]); + + if (!identifier) { + return ; + } + + return ( + + {(() => { + if (hasAsyncTaskFailed(key)) { + return ( + + + + ); + } else if (isAsyncTaskStarting(key)) { + return ( + + + + ); + } else { + return ( + + + + KEY DETAILS + + + + + + + + + ); + } + })()} + + ); +} diff --git a/ui/src/components/keys/Keys.tsx b/ui/src/components/keys/Keys.tsx new file mode 100644 index 000000000..722fac2a5 --- /dev/null +++ b/ui/src/components/keys/Keys.tsx @@ -0,0 +1,32 @@ +import { Button, Space } from "antd"; +import { generatePath, useNavigate } from "react-router-dom"; + +import IconPlus from "src/assets/icons/plus.svg?react"; +import { KeysTable } from "src/components/keys/KeysTable"; +import { SiderLayoutContent } from "src/components/shared/SiderLayoutContent"; +import { ROUTES } from "src/routes"; +import { KEYS, KEY_ADD } from "src/utils/constants"; + +export function Keys() { + const navigate = useNavigate(); + + return ( + } + onClick={() => navigate(generatePath(ROUTES.createKey.path))} + type="primary" + > + {KEY_ADD} + + } + title={KEYS} + > + + + + + ); +} diff --git a/ui/src/components/keys/KeysTable.tsx b/ui/src/components/keys/KeysTable.tsx new file mode 100644 index 000000000..f5195ca9c --- /dev/null +++ b/ui/src/components/keys/KeysTable.tsx @@ -0,0 +1,274 @@ +import { + Avatar, + Button, + Card, + Dropdown, + Row, + Space, + Table, + TableColumnsType, + Tag, + Tooltip, + Typography, +} from "antd"; +import { useCallback, useEffect, useState } from "react"; +import { Link, generatePath, useNavigate, useSearchParams } from "react-router-dom"; + +import { getKeys } from "src/adapters/api/keys"; +import { positiveIntegerFromStringParser } from "src/adapters/parsers"; +import IconIssuers from "src/assets/icons/building-08.svg?react"; +import IconCheckMark from "src/assets/icons/check.svg?react"; +import IconCopy from "src/assets/icons/copy-01.svg?react"; +import IconDots from "src/assets/icons/dots-vertical.svg?react"; +import IconInfoCircle from "src/assets/icons/info-circle.svg?react"; +import IconPlus from "src/assets/icons/plus.svg?react"; +import { ErrorResult } from "src/components/shared/ErrorResult"; +import { NoResults } from "src/components/shared/NoResults"; +import { TableCard } from "src/components/shared/TableCard"; +import { useEnvContext } from "src/contexts/Env"; +import { useIdentityContext } from "src/contexts/Identity"; +import { AppError, Key } from "src/domain"; +import { ROUTES } from "src/routes"; +import { AsyncTask, isAsyncTaskDataAvailable, isAsyncTaskStarting } from "src/utils/async"; +import { isAbortedError, makeRequestAbortable } from "src/utils/browser"; +import { + DEFAULT_PAGINATION_MAX_RESULTS, + DEFAULT_PAGINATION_PAGE, + DEFAULT_PAGINATION_TOTAL, + DETAILS, + DOTS_DROPDOWN_WIDTH, + KEY_ADD_NEW, + PAGINATION_MAX_RESULTS_PARAM, + PAGINATION_PAGE_PARAM, + QUERY_SEARCH_PARAM, +} from "src/utils/constants"; +import { notifyParseErrors } from "src/utils/error"; + +export function KeysTable() { + const env = useEnvContext(); + const { identifier } = useIdentityContext(); + + const navigate = useNavigate(); + + const [keys, setKeys] = useState>({ + status: "pending", + }); + + const [searchParams, setSearchParams] = useSearchParams(); + const queryParam = searchParams.get(QUERY_SEARCH_PARAM); + const paginationPageParam = searchParams.get(PAGINATION_PAGE_PARAM); + const paginationMaxResultsParam = searchParams.get(PAGINATION_MAX_RESULTS_PARAM); + + const paginationPageParsed = positiveIntegerFromStringParser.safeParse(paginationPageParam); + const paginationMaxResultsParsed = + positiveIntegerFromStringParser.safeParse(paginationMaxResultsParam); + + const [paginationTotal, setPaginationTotal] = useState(DEFAULT_PAGINATION_TOTAL); + const paginationPage = paginationPageParsed.success + ? paginationPageParsed.data + : DEFAULT_PAGINATION_PAGE; + const paginationMaxResults = paginationMaxResultsParsed.success + ? paginationMaxResultsParsed.data + : DEFAULT_PAGINATION_MAX_RESULTS; + + const keysList = isAsyncTaskDataAvailable(keys) ? keys.data : []; + const showDefaultContent = keys.status === "successful" && keysList.length === 0; + + const tableColumns: TableColumnsType = [ + { + dataIndex: "publicKey", + key: "publicKey", + render: (publicKey: Key["publicKey"]) => ( + + , ], + }} + ellipsis={{ + suffix: publicKey.slice(-5), + }} + strong + > + {publicKey} + + + ), + title: "Public key", + }, + { + dataIndex: "keyType", + key: "keyType", + render: (keyType: Key["keyType"]) => {keyType}, + title: "Type", + }, + { + dataIndex: "isAuthCoreClaim", + key: "isAuthCoreClaim", + render: (isAuthCoreClaim: Key["isAuthCoreClaim"]) => ( + {`${isAuthCoreClaim}`} + ), + title: "Auth core claim", + }, + { + dataIndex: "id", + key: "id", + render: (id: Key["id"]) => ( + , + key: "details", + label: DETAILS, + onClick: () => + navigate( + generatePath(ROUTES.keyDetails.path, { + keyID: id, + }) + ), + }, + ], + }} + > + + + + + ), + width: DOTS_DROPDOWN_WIDTH, + }, + ]; + + const updateUrlParams = useCallback( + ({ maxResults, page }: { maxResults?: number; page?: number }) => { + setSearchParams((previousParams) => { + const params = new URLSearchParams(previousParams); + params.set( + PAGINATION_PAGE_PARAM, + page !== undefined ? page.toString() : DEFAULT_PAGINATION_PAGE.toString() + ); + params.set( + PAGINATION_MAX_RESULTS_PARAM, + maxResults !== undefined + ? maxResults.toString() + : DEFAULT_PAGINATION_MAX_RESULTS.toString() + ); + + return params; + }); + }, + [setSearchParams] + ); + + const fetchKeys = useCallback( + async (signal?: AbortSignal) => { + setKeys((previousKeys) => + isAsyncTaskDataAvailable(previousKeys) + ? { data: previousKeys.data, status: "reloading" } + : { status: "loading" } + ); + + const response = await getKeys({ + env, + identifier, + params: { + maxResults: paginationMaxResults, + page: paginationPage, + }, + signal, + }); + if (response.success) { + setKeys({ + data: response.data.items.successful, + status: "successful", + }); + setPaginationTotal(response.data.meta.total); + updateUrlParams({ + maxResults: response.data.meta.max_results, + page: response.data.meta.page, + }); + notifyParseErrors(response.data.items.failed); + } else { + if (!isAbortedError(response.error)) { + setKeys({ error: response.error, status: "failed" }); + } + } + }, + [env, paginationMaxResults, paginationPage, identifier, updateUrlParams] + ); + + useEffect(() => { + const { aborter } = makeRequestAbortable(fetchKeys); + + return aborter; + }, [fetchKeys]); + + return ( + + } size={48} /> + + No keys + + Your keys will be listed here. + + + + + + } + isLoading={isAsyncTaskStarting(keys)} + query={queryParam} + showDefaultContents={showDefaultContent} + table={ + ({ + title: ( + + <>{title} + + ), + ...column, + }))} + dataSource={keysList} + locale={{ + emptyText: + keys.status === "failed" ? ( + + ) : ( + + ), + }} + onChange={({ current, pageSize, total }) => { + setPaginationTotal(total || DEFAULT_PAGINATION_TOTAL); + updateUrlParams({ + maxResults: pageSize, + page: current, + }); + }} + pagination={{ + current: paginationPage, + hideOnSinglePage: true, + pageSize: paginationMaxResults, + position: ["bottomRight"], + total: paginationTotal, + }} + rowKey="id" + showSorterTooltip + sortDirections={["ascend", "descend"]} + /> + } + title={ + + + + {paginationTotal} + + + } + /> + ); +} diff --git a/ui/src/components/shared/Detail.tsx b/ui/src/components/shared/Detail.tsx index 37d91ad28..eae3faa61 100644 --- a/ui/src/components/shared/Detail.tsx +++ b/ui/src/components/shared/Detail.tsx @@ -1,7 +1,7 @@ import { App, Button, Col, Flex, Grid, Row, Tag, TagProps, Typography } from "antd"; - import copy from "copy-to-clipboard"; import { useRef } from "react"; + import IconCheckMark from "src/assets/icons/check.svg?react"; import IconCopy from "src/assets/icons/copy-01.svg?react"; import IconDownload from "src/assets/icons/download-01.svg?react"; @@ -47,11 +47,13 @@ export function Detail({ const componentProps = Component === Typography.Link ? { + className: "detail", ellipsis: true, href, target: "_blank", } : { + className: "detail", ellipsis: ellipsisPosition ? { suffix: text.slice(-ellipsisPosition) } : true, }; diff --git a/ui/src/components/shared/Router.tsx b/ui/src/components/shared/Router.tsx index 2a039700d..815b08447 100644 --- a/ui/src/components/shared/Router.tsx +++ b/ui/src/components/shared/Router.tsx @@ -12,6 +12,9 @@ import { Identities } from "src/components/identities/Identities"; import { Identity } from "src/components/identities/Identity"; import { Onboarding } from "src/components/identities/Onboarding"; import { IssuerState } from "src/components/issuer-state/IssuerState"; +import { CreateKey } from "src/components/keys/CreateKey"; +import { Key } from "src/components/keys/Key"; +import { Keys } from "src/components/keys/Keys"; import { FullWidthLayout } from "src/components/layouts/FullWidthLayout"; import { SiderLayout } from "src/components/layouts/SiderLayout"; import { ImportSchema } from "src/components/schemas/ImportSchema"; @@ -26,6 +29,7 @@ const COMPONENTS: Record = { connectionDetails: ConnectionDetails, connections: ConnectionsTable, createIdentity: CreateIdentity, + createKey: CreateKey, credentialDetails: CredentialDetails, credentials: Credentials, identities: Identities, @@ -33,6 +37,8 @@ const COMPONENTS: Record = { importSchema: ImportSchema, issueCredential: IssueCredential, issuerState: IssuerState, + keyDetails: Key, + keys: Keys, linkDetails: LinkDetails, notFound: NotFound, onboarding: Onboarding, diff --git a/ui/src/components/shared/SiderMenu.tsx b/ui/src/components/shared/SiderMenu.tsx index f2be095e2..abe00e9dd 100644 --- a/ui/src/components/shared/SiderMenu.tsx +++ b/ui/src/components/shared/SiderMenu.tsx @@ -24,6 +24,7 @@ import { DOCS_URL, IDENTITIES, ISSUER_STATE, + KEYS, SCHEMAS, } from "src/utils/constants"; @@ -48,6 +49,7 @@ export function SiderMenu({ const issuerStatePath = ROUTES.issuerState.path; const schemasPath = ROUTES.schemas.path; const identitiesPath = ROUTES.identities.path; + const keysPath = ROUTES.keys.path; const getSelectedKey = (): string[] => { if ( @@ -90,6 +92,13 @@ export function SiderMenu({ ) ) { return [identitiesPath]; + } else if ( + matchRoutes( + [{ path: keysPath }, { path: ROUTES.keyDetails.path }, { path: ROUTES.createKey.path }], + pathname + ) + ) { + return [keysPath]; } return []; @@ -162,6 +171,13 @@ export function SiderMenu({ onClick: () => onMenuClick(identitiesPath), title: "", }, + { + icon: , + key: keysPath, + label: KEYS, + onClick: () => onMenuClick(keysPath), + title: "", + }, ]} selectedKeys={getSelectedKey()} style={{ marginTop: 16 }} diff --git a/ui/src/domain/index.ts b/ui/src/domain/index.ts index 95c2a39db..e60db262b 100644 --- a/ui/src/domain/index.ts +++ b/ui/src/domain/index.ts @@ -62,3 +62,6 @@ export type { Schema as ApiSchema } from "src/domain/schema"; export type { Identity, IdentityDetails, Blockchain, Network } from "src/domain/identity"; export { IdentityType, Method, CredentialStatusType } from "src/domain/identity"; + +export type { Key } from "src/domain/key"; +export { KeyType } from "src/domain/key"; diff --git a/ui/src/domain/key.ts b/ui/src/domain/key.ts new file mode 100644 index 000000000..6911a24a9 --- /dev/null +++ b/ui/src/domain/key.ts @@ -0,0 +1,11 @@ +export enum KeyType { + BJJ = "BJJ", + ETH = "ETH", +} + +export type Key = { + id: string; + isAuthCoreClaim: boolean; + keyType: KeyType; + publicKey: string; +}; diff --git a/ui/src/routes.ts b/ui/src/routes.ts index 675689da1..a7e781ef3 100644 --- a/ui/src/routes.ts +++ b/ui/src/routes.ts @@ -13,7 +13,10 @@ export type RouteID = | "identities" | "createIdentity" | "identityDetails" - | "onboarding"; + | "onboarding" + | "keys" + | "keyDetails" + | "createKey"; export type Layout = "fullWidth" | "fullWidthGrey" | "sider"; @@ -38,6 +41,10 @@ export const ROUTES: Routes = { layout: "sider", path: "/identities/create", }, + createKey: { + layout: "sider", + path: "/keys/create", + }, credentialDetails: { layout: "sider", path: "/credentials/issued/:credentialID", @@ -66,6 +73,14 @@ export const ROUTES: Routes = { layout: "sider", path: "/issuer-state", }, + keyDetails: { + layout: "sider", + path: "/keys/:keyID", + }, + keys: { + layout: "sider", + path: "/keys", + }, linkDetails: { layout: "sider", path: "/credentials/links/:linkID", diff --git a/ui/src/styles/index.scss b/ui/src/styles/index.scss index 8e5f261d2..c108e3da9 100644 --- a/ui/src/styles/index.scss +++ b/ui/src/styles/index.scss @@ -313,16 +313,6 @@ } .ant-typography { - &:has(.ant-typography-copy) { - display: flex; - justify-content: flex-end; - word-break: keep-all; - - @media screen and (width <= 576px) { - justify-content: flex-start; - } - } - .ant-typography-copy { margin-inline-start: 8px; } diff --git a/ui/src/utils/constants.ts b/ui/src/utils/constants.ts index 3688d1eef..f08965960 100644 --- a/ui/src/utils/constants.ts +++ b/ui/src/utils/constants.ts @@ -23,6 +23,10 @@ export const IDENTITY_ADD_NEW = "Add new identity"; export const IDENTITY_ADD = "Add identity"; export const IDENTITY_DETAILS = "Identity details"; export const IDENTITIES = "Identities"; +export const KEYS = "Keys"; +export const KEY_ADD = "Add key"; +export const KEY_ADD_NEW = "Add new key"; +export const KEY_DETAILS = "Key details"; export const LINKS = "Links"; export const REVOCATION = "Revocation"; export const REVOKE = "Revoke"; From 1f5d74500b5eb08d42c6f05348f05eccbb6f0c48 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Tue, 10 Dec 2024 07:13:11 -0300 Subject: [PATCH 20/62] chore: add tests --- internal/api/keys_test.go | 55 ++++++++++++++++++++++++----------- internal/api/networks_test.go | 4 +-- internal/common/read_file.go | 22 ++++++++++++++ internal/core/services/key.go | 18 ++++++------ 4 files changed, 71 insertions(+), 28 deletions(-) diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index b2b97a9ad..33eda0c19 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -20,19 +20,25 @@ import ( func TestServer_CreateKey(t *testing.T) { const ( - method = "polygonid" - blockchain = "polygon" - network = "amoy" + method = "iden3" + blockchain = "privado" + network = "main" BJJ = "BJJ" + ETH = "ETH" ) ctx := context.Background() server := newTestServer(t, nil) - iden, err := server.Services.identity.Create(ctx, "polygon-test", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) + iden, err := server.Services.identity.Create(ctx, "http://issuer-node", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) require.NoError(t, err) did, err := w3c.ParseDID(iden.Identifier) require.NoError(t, err) + idenETH, err := server.Services.identity.Create(ctx, "http://issuer-node", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: ETH}) + require.NoError(t, err) + didETH, err := w3c.ParseDID(idenETH.Identifier) + require.NoError(t, err) + handler := getHandler(ctx, server) type expected struct { @@ -43,6 +49,7 @@ func TestServer_CreateKey(t *testing.T) { type testConfig struct { name string auth func() (string, string) + did string body CreateKeyRequest expected expected } @@ -50,14 +57,16 @@ func TestServer_CreateKey(t *testing.T) { for _, tc := range []testConfig{ { name: "no auth header", + did: did.String(), auth: authWrong, expected: expected{ httpCode: http.StatusUnauthorized, }, }, { - name: "should create a key", + name: "should create a bjj key", auth: authOk, + did: did.String(), body: CreateKeyRequest{ KeyType: BJJ, }, @@ -68,6 +77,7 @@ func TestServer_CreateKey(t *testing.T) { { name: "should get an error", auth: authOk, + did: did.String(), body: CreateKeyRequest{ KeyType: "wrong type", }, @@ -80,10 +90,21 @@ func TestServer_CreateKey(t *testing.T) { }, }, }, + { + name: "should create a eth key", + auth: authOk, + did: didETH.String(), + body: CreateKeyRequest{ + KeyType: BJJ, + }, + expected: expected{ + httpCode: http.StatusCreated, + }, + }, } { t.Run(tc.name, func(t *testing.T) { rr := httptest.NewRecorder() - url := fmt.Sprintf("/v2/identities/%s/keys", did) + url := fmt.Sprintf("/v2/identities/%s/keys", tc.did) req, err := http.NewRequest(http.MethodPost, url, tests.JSONBody(t, tc.body)) req.SetBasicAuth(tc.auth()) require.NoError(t, err) @@ -106,9 +127,9 @@ func TestServer_CreateKey(t *testing.T) { func TestServer_GetKey(t *testing.T) { const ( - method = "polygonid" - blockchain = "polygon" - network = "amoy" + method = "iden3" + blockchain = "privado" + network = "main" BJJ = "BJJ" ) ctx := context.Background() @@ -196,21 +217,21 @@ func TestServer_GetKey(t *testing.T) { func TestServer_GetKeys(t *testing.T) { const ( - method = "polygonid" - blockchain = "polygon" - network = "amoy" + method = "iden3" + blockchain = "privado" + network = "main" BJJ = "BJJ" ETH = "ETH" ) ctx := context.Background() server := newTestServer(t, nil) - iden, err := server.Services.identity.Create(ctx, "polygon-test", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) + iden, err := server.Services.identity.Create(ctx, "http://issuer-node", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: BJJ}) require.NoError(t, err) did, err := w3c.ParseDID(iden.Identifier) require.NoError(t, err) - idenETH, err := server.Services.identity.Create(ctx, "polygon-test", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: ETH}) + idenETH, err := server.Services.identity.Create(ctx, "http://issuer-node", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: network, KeyType: ETH}) require.NoError(t, err) didETH, err := w3c.ParseDID(idenETH.Identifier) require.NoError(t, err) @@ -352,9 +373,9 @@ func countAuthCredentials(t *testing.T, keys []Key) int { func TestServer_DeleteKey(t *testing.T) { const ( - method = "polygonid" - blockchain = "polygon" - network = "amoy" + method = "iden3" + blockchain = "privado" + network = "main" BJJ = "BJJ" ETH = "ETH" ) diff --git a/internal/api/networks_test.go b/internal/api/networks_test.go index ccd1e12a9..527a72b4d 100644 --- a/internal/api/networks_test.go +++ b/internal/api/networks_test.go @@ -43,7 +43,7 @@ func TestServer_GetSupportedNetworks(t *testing.T) { t.Run(tc.name, func(t *testing.T) { rr := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/v2/supported-networks", nil) + req, err := http.NewRequest(http.MethodGet, "/v2/supported-networks", nil) req.SetBasicAuth(tc.auth()) require.NoError(t, err) handler.ServeHTTP(rr, req) @@ -52,7 +52,7 @@ func TestServer_GetSupportedNetworks(t *testing.T) { if tc.expected.httpCode == http.StatusOK { var response GetSupportedNetworks200JSONResponse assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) - assert.Equal(t, 1, len(response)) + assert.Equal(t, 2, len(response)) assert.Equal(t, "polygon", response[0].Blockchain) assert.Equal(t, []NetworkData{ { diff --git a/internal/common/read_file.go b/internal/common/read_file.go index c5d00344f..bc880f5b3 100644 --- a/internal/common/read_file.go +++ b/internal/common/read_file.go @@ -51,6 +51,28 @@ func CreateFile(t *testing.T) *MyYAMLReader { contractAddress: 0x3d3763eC0a50CE1AdF83d0b5D99FBE0e3fEB43fb chainID: 80002 publishingKey: pbkey + +privado: + main: + contractAddress: 0x3C9acB2205Aa72A05F6D77d708b5Cf85FCa3a896 + networkURL: https://rpc-mainnet.privado.id + defaultGasLimit: 600000 + confirmationTimeout: 10s + confirmationBlockCount: 5 + receiptTimeout: 600s + minGasPrice: 0 + maxGasPrice: 1000000 + rpcResponseTimeout: 5s + waitReceiptCycleTime: 30s + waitBlockCycleTime: 30s + gasLess: false + rhsSettings: + mode: None + contractAddress: 0x7dF78ED37d0B39Ffb6d4D527Bb1865Bf85B60f81 + rhsUrl: https://rhs-staging.polygonid.me + chainID: 21000 + publishingKey: pbkey + `) reader := NewMyYAMLReader(yamlData) diff --git a/internal/core/services/key.go b/internal/core/services/key.go index 286ee2a12..6fd47a8da 100644 --- a/internal/core/services/key.go +++ b/internal/core/services/key.go @@ -252,19 +252,21 @@ func getKeyType(keyID string) (kms.KeyType, error) { return keyType, nil } +// hasAssociatedAuthCredential checks if the bbj key has an associated auth credential func (ks *Key) hasAssociatedAuthCredential(ctx context.Context, did *w3c.DID, publicKey []byte) (bool, *domain.Claim, error) { - hasAssociatedAuthCoreClaim := false - authCoreClaim, err := ks.claimService.GetAuthCredentialWithPublicKey(ctx, did, publicKey) + hasAssociatedAuthCredential := false + authCredential, err := ks.claimService.GetAuthCredentialWithPublicKey(ctx, did, publicKey) if err != nil { log.Error(ctx, "failed to check if key has associated auth credential", "err", err) return false, nil, err } - hasAssociatedAuthCoreClaim = authCoreClaim != nil - return hasAssociatedAuthCoreClaim, authCoreClaim, nil + hasAssociatedAuthCredential = authCredential != nil + return hasAssociatedAuthCredential, authCredential, nil } +// isAssociatedWithIdentity checks if the eth key is associated with the identity func (ks *Key) isAssociatedWithIdentity(ctx context.Context, did *w3c.DID, publicKey []byte) (bool, error) { - hasAssociatedAuthCoreClaim := false + hasAssociatedAuthCredential := false pubKey, err := crypto.DecompressPubkey(publicKey) if err != nil { log.Error(ctx, "failed to decompress public key", "err", err) @@ -280,10 +282,8 @@ func (ks *Key) isAssociatedWithIdentity(ctx context.Context, did *w3c.DID, publi identityAddressToBeChecked := strings.ToUpper("0x" + identityAddress) if isEthAddress { - hasAssociatedAuthCoreClaim = identityAddressToBeChecked == strings.ToUpper(keyETHAddress.Hex()) - } else { - hasAssociatedAuthCoreClaim = false + hasAssociatedAuthCredential = identityAddressToBeChecked == strings.ToUpper(keyETHAddress.Hex()) } - return hasAssociatedAuthCoreClaim, nil + return hasAssociatedAuthCredential, nil } From cc12337a9584ee9f7bfe8eaf7ec57e6e5fdf85be Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Tue, 10 Dec 2024 20:43:02 -0300 Subject: [PATCH 21/62] chore: add name field to keys --- api/api.yaml | 9 ++ cmd/notifications/main.go | 3 +- cmd/pending_publisher/main.go | 3 +- cmd/platform/main.go | 5 +- internal/api/api.gen.go | 3 + internal/api/keys.go | 13 ++- internal/api/keys_test.go | 18 ++-- internal/api/main_test.go | 6 +- internal/core/domain/key.go | 53 +++++++++++ internal/core/domain/publicKey.go | 9 ++ internal/core/ports/key_repository.go | 18 ++++ internal/core/ports/key_service.go | 3 +- internal/core/services/identity.go | 38 +++++++- internal/core/services/identity_test.go | 37 +++++--- internal/core/services/key.go | 48 ++++++++-- internal/core/services/link_test.go | 3 +- internal/core/services/notification_test.go | 3 +- .../202412101722170_add_keys_table.sql | 18 ++++ internal/repositories/key.go | 69 ++++++++++++++ internal/repositories/key_test.go | 95 +++++++++++++++++++ internal/repositories/main_test.go | 18 ++++ 21 files changed, 430 insertions(+), 42 deletions(-) create mode 100644 internal/core/domain/key.go create mode 100644 internal/core/ports/key_repository.go create mode 100644 internal/db/schema/migrations/202412101722170_add_keys_table.sql create mode 100644 internal/repositories/key.go create mode 100644 internal/repositories/key_test.go diff --git a/api/api.yaml b/api/api.yaml index f2e0aa6ec..3daae12d9 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -2414,12 +2414,16 @@ components: type: object required: - keyType + - name properties: keyType: type: string x-omitempty: false example: "BJJ" enum: [ BJJ, ETH ] + name: + type: string + example: "my key" CreateKeyResponse: type: object @@ -2439,6 +2443,7 @@ components: - keyType - publicKey - isAuthCredential + - Name properties: id: type: string @@ -2458,6 +2463,10 @@ components: type: boolean x-omitempty: false example: true + Name: + type: string + x-omitempty: false + example: "my key" KeysPaginated: type: object diff --git a/cmd/notifications/main.go b/cmd/notifications/main.go index eb71663b2..490014e05 100644 --- a/cmd/notifications/main.go +++ b/cmd/notifications/main.go @@ -130,6 +130,7 @@ func newCredentialsService(ctx context.Context, cfg *config.Configuration, stora mtRepository := repositories.NewIdentityMerkleTreeRepository() identityStateRepository := repositories.NewIdentityState() revocationRepository := repositories.NewRevocation() + keyRepository := repositories.NewKey(*storage) reader, err := network.GetReaderFromConfig(cfg, ctx) if err != nil { @@ -157,7 +158,7 @@ func newCredentialsService(ctx context.Context, cfg *config.Configuration, stora *cfg.MediaTypeManager.Enabled, ) - identityService := services.NewIdentity(keyStore, identityRepository, mtRepository, identityStateRepository, mtService, qrService, claimsRepository, revocationRepository, nil, storage, nil, nil, ps, *networkResolver, rhsFactory, revocationStatusResolver) + identityService := services.NewIdentity(keyStore, identityRepository, mtRepository, identityStateRepository, mtService, qrService, claimsRepository, revocationRepository, nil, storage, nil, nil, ps, *networkResolver, rhsFactory, revocationStatusResolver, keyRepository) claimsService := services.NewClaim(claimsRepository, identityService, qrService, mtService, identityStateRepository, schemaLoader, storage, cfg.ServerUrl, ps, cfg.IPFS.GatewayURL, revocationStatusResolver, mediaTypeManager, cfg.UniversalLinks) return claimsService, nil diff --git a/cmd/pending_publisher/main.go b/cmd/pending_publisher/main.go index 5ae3e5c55..667da2034 100644 --- a/cmd/pending_publisher/main.go +++ b/cmd/pending_publisher/main.go @@ -104,6 +104,7 @@ func main() { mtRepo := repositories.NewIdentityMerkleTreeRepository() identityStateRepo := repositories.NewIdentityState() revocationRepository := repositories.NewRevocation() + keyRepository := repositories.NewKey(*storage) mtService := services.NewIdentityMerkleTrees(mtRepo) qrService := services.NewQrStoreService(cachex) @@ -120,7 +121,7 @@ func main() { *cfg.MediaTypeManager.Enabled, ) - identityService := services.NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, qrService, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) + identityService := services.NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, qrService, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver, keyRepository) claimsService := services.NewClaim(claimsRepo, identityService, qrService, mtService, identityStateRepo, schemaLoader, storage, cfg.ServerUrl, ps, cfg.IPFS.GatewayURL, revocationStatusResolver, mediaTypeManager, cfg.UniversalLinks) circuitsLoaderService := circuitLoaders.NewCircuits(cfg.Circuit.Path) diff --git a/cmd/platform/main.go b/cmd/platform/main.go index 303e93b05..fa33cb857 100644 --- a/cmd/platform/main.go +++ b/cmd/platform/main.go @@ -112,6 +112,7 @@ func main() { schemaRepository := repositories.NewSchema(*storage) linkRepository := repositories.NewLink(*storage) sessionRepository := repositories.NewSessionCached(cachex) + keyRepository := repositories.NewKey(*storage) // services initialization mtService := services.NewIdentityMerkleTrees(mtRepository) @@ -146,12 +147,12 @@ func main() { } revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := services.NewIdentity(keyStore, identityRepository, mtRepository, identityStateRepository, mtService, qrService, claimsRepository, revocationRepository, connectionsRepository, storage, verifier, sessionRepository, ps, *networkResolver, rhsFactory, revocationStatusResolver) + identityService := services.NewIdentity(keyStore, identityRepository, mtRepository, identityStateRepository, mtService, qrService, claimsRepository, revocationRepository, connectionsRepository, storage, verifier, sessionRepository, ps, *networkResolver, rhsFactory, revocationStatusResolver, keyRepository) claimsService := services.NewClaim(claimsRepository, identityService, qrService, mtService, identityStateRepository, schemaLoader, storage, cfg.ServerUrl, ps, cfg.IPFS.GatewayURL, revocationStatusResolver, mediaTypeManager, cfg.UniversalLinks) proofService := services.NewProver(circuitsLoaderService) schemaService := services.NewSchema(schemaRepository, schemaLoader) linkService := services.NewLinkService(storage, claimsService, qrService, claimsRepository, linkRepository, schemaRepository, schemaLoader, sessionRepository, ps, identityService, *networkResolver, cfg.UniversalLinks) - keyService := services.NewKey(keyStore, claimsService) + keyService := services.NewKey(keyStore, claimsService, keyRepository) transactionService, err := gateways.NewTransaction(*networkResolver) if err != nil { log.Error(ctx, "error creating transaction service", "err", err) diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 2414c8e36..31a9cad5a 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -272,6 +272,7 @@ type CreateIdentityResponseCredentialStatusType string // CreateKeyRequest defines model for CreateKeyRequest. type CreateKeyRequest struct { KeyType CreateKeyRequestKeyType `json:"keyType"` + Name string `json:"name"` } // CreateKeyRequestKeyType defines model for CreateKeyRequest.KeyType. @@ -429,6 +430,8 @@ type IssuerDescription struct { // Key defines model for Key. type Key struct { + Name string `json:"Name"` + // Id base64 encoded keyID Id string `json:"id"` IsAuthCredential bool `json:"isAuthCredential"` diff --git a/internal/api/keys.go b/internal/api/keys.go index abb526f01..b017a824f 100644 --- a/internal/api/keys.go +++ b/internal/api/keys.go @@ -21,7 +21,16 @@ func (s *Server) CreateKey(ctx context.Context, request CreateKeyRequestObject) }, nil } - keyID, err := s.keyService.CreateKey(ctx, request.Identifier.did(), kms.KeyType(request.Body.KeyType)) + if request.Body.Name == "" { + log.Error(ctx, "name is required") + return CreateKey400JSONResponse{ + N400JSONResponse{ + Message: "name is required", + }, + }, nil + } + + keyID, err := s.keyService.CreateKey(ctx, request.Identifier.did(), kms.KeyType(request.Body.KeyType), request.Body.Name) if err != nil { log.Error(ctx, "creating key", "err", err) return CreateKey500JSONResponse{ @@ -79,6 +88,7 @@ func (s *Server) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKe KeyType: KeyKeyType(key.KeyType), PublicKey: key.PublicKey, IsAuthCredential: key.HasAssociatedAuthCredential, + Name: key.Name, }, nil } @@ -124,6 +134,7 @@ func (s *Server) GetKeys(ctx context.Context, request GetKeysRequestObject) (Get KeyType: KeyKeyType(key.KeyType), PublicKey: key.PublicKey, IsAuthCredential: key.HasAssociatedAuthCredential, + Name: key.Name, }) } return GetKeys200JSONResponse{ diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index 33eda0c19..f6d5daa59 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -69,6 +69,7 @@ func TestServer_CreateKey(t *testing.T) { did: did.String(), body: CreateKeyRequest{ KeyType: BJJ, + Name: "my-bjj-key", }, expected: expected{ httpCode: http.StatusCreated, @@ -95,7 +96,8 @@ func TestServer_CreateKey(t *testing.T) { auth: authOk, did: didETH.String(), body: CreateKeyRequest{ - KeyType: BJJ, + KeyType: ETH, + Name: "my-eth-key", }, expected: expected{ httpCode: http.StatusCreated, @@ -140,7 +142,7 @@ func TestServer_GetKey(t *testing.T) { did, err := w3c.ParseDID(iden.Identifier) require.NoError(t, err) - keyID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) + keyID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub, "my-key") require.NoError(t, err) handler := getHandler(ctx, server) @@ -206,6 +208,7 @@ func TestServer_GetKey(t *testing.T) { assert.NotNil(t, response.PublicKey) assert.Equal(t, BJJ, response.KeyType) assert.False(t, response.IsAuthCredential) + assert.True(t, "my-key" == response.Name) case http.StatusBadRequest: var response GetKey400JSONResponse require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) @@ -267,7 +270,8 @@ func TestServer_GetKeys(t *testing.T) { t.Run("should get the keys for bjj identity with pagination", func(t *testing.T) { for i := 0; i < 20; i++ { - _, err = server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) + name := fmt.Sprintf("my-key-%d", i) + _, err = server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub, name) require.NoError(t, err) } @@ -323,7 +327,7 @@ func TestServer_GetKeys(t *testing.T) { t.Run("should get the keys for eth identity with pagination", func(t *testing.T) { for i := 0; i < 20; i++ { - _, err = server.keyService.CreateKey(ctx, didETH, kms.KeyTypeBabyJubJub) + _, err = server.keyService.CreateKey(ctx, didETH, kms.KeyTypeBabyJubJub, fmt.Sprintf("my-key-%d", i)) require.NoError(t, err) } @@ -406,13 +410,13 @@ func TestServer_DeleteKey(t *testing.T) { idenETHETHKey = idenETHKeys[0].KeyID } - keyETHIDToDelete, err := server.keyService.CreateKey(ctx, didETH, kms.KeyTypeEthereum) + keyETHIDToDelete, err := server.keyService.CreateKey(ctx, didETH, kms.KeyTypeEthereum, "key-eth-to-delete") require.NoError(t, err) - keyID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) + keyID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub, "key-bjj-to-delete") require.NoError(t, err) - keyIDForAuthCoreClaimID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub) + keyIDForAuthCoreClaimID, err := server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub, "key-bjj-for-auth-core-claim-id") require.NoError(t, err) keyIDForAuthCoreClaimIDASByteArr, err := b64.StdEncoding.DecodeString(keyIDForAuthCoreClaimID.ID) diff --git a/internal/api/main_test.go b/internal/api/main_test.go index fb632867b..2d12fd9d9 100644 --- a/internal/api/main_test.go +++ b/internal/api/main_test.go @@ -235,6 +235,7 @@ type repos struct { schemas ports.SchemaRepository sessions ports.SessionRepository revocation ports.RevocationRepository + keyRepository ports.KeyRepository } type servicex struct { @@ -273,6 +274,7 @@ func newTestServer(t *testing.T, st *db.Storage) *testServer { sessions: repositories.NewSessionCached(cachex), schemas: repositories.NewSchema(*st), revocation: repositories.NewRevocation(), + keyRepository: repositories.NewKey(*st), } pubSub := pubsub.NewMock() @@ -284,7 +286,7 @@ func newTestServer(t *testing.T, st *db.Storage) *testServer { mtService := services.NewIdentityMerkleTrees(repos.idenMerkleTree) qrService := services.NewQrStoreService(cachex) rhsFactory := reversehash.NewFactory(*networkResolver, reversehash.DefaultRHSTimeOut) - identityService := services.NewIdentity(keyStore, repos.identity, repos.idenMerkleTree, repos.identityState, mtService, qrService, repos.claims, repos.revocation, repos.connection, st, nil, repos.sessions, pubSub, *networkResolver, rhsFactory, revocationStatusResolver) + identityService := services.NewIdentity(keyStore, repos.identity, repos.idenMerkleTree, repos.identityState, mtService, qrService, repos.claims, repos.revocation, repos.connection, st, nil, repos.sessions, pubSub, *networkResolver, rhsFactory, revocationStatusResolver, repos.keyRepository) connectionService := services.NewConnection(repos.connection, repos.claims, st) schemaService := services.NewSchema(repos.schemas, schemaLoader) @@ -299,7 +301,7 @@ func newTestServer(t *testing.T, st *db.Storage) *testServer { claimsService := services.NewClaim(repos.claims, identityService, qrService, mtService, repos.identityState, schemaLoader, st, cfg.ServerUrl, pubSub, ipfsGatewayURL, revocationStatusResolver, mediaTypeManager, cfg.UniversalLinks) accountService := services.NewAccountService(*networkResolver) linkService := services.NewLinkService(storage, claimsService, qrService, repos.claims, repos.links, repos.schemas, schemaLoader, repos.sessions, pubSub, identityService, *networkResolver, cfg.UniversalLinks) - keyService := services.NewKey(keyStore, claimsService) + keyService := services.NewKey(keyStore, claimsService, repos.keyRepository) server := NewServer(&cfg, identityService, accountService, connectionService, claimsService, qrService, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil, schemaService, linkService, keyService) return &testServer{ diff --git a/internal/core/domain/key.go b/internal/core/domain/key.go new file mode 100644 index 000000000..ca23add9b --- /dev/null +++ b/internal/core/domain/key.go @@ -0,0 +1,53 @@ +package domain + +import ( + "fmt" + "time" + + "github.com/google/uuid" + "github.com/iden3/go-iden3-core/v2/w3c" + + "github.com/polygonid/sh-id-platform/internal/common" +) + +// KeyCoreDID is a DID type for keys +type KeyCoreDID w3c.DID + +// Key is a key domain model +type Key struct { + ID uuid.UUID `json:"id"` + IssuerDID KeyCoreDID `json:"issuer_did"` + PublicKey string `json:"public_key"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` +} + +// NewKey creates a new Key +func NewKey(issuerDID w3c.DID, publicKey, name string) *Key { + return &Key{ + ID: uuid.New(), + IssuerDID: KeyCoreDID(issuerDID), + PublicKey: publicKey, + Name: name, + CreatedAt: time.Now(), + } +} + +// IssuerCoreDID returns the issuer DID as a w3c.DID pointer +func (key *Key) IssuerCoreDID() *w3c.DID { + return common.ToPointer(w3c.DID(key.IssuerDID)) +} + +// Scan implements the sql.Scanner interface +func (keydid *KeyCoreDID) Scan(value interface{}) error { + didStr, ok := value.(string) + if !ok { + return fmt.Errorf("invalid value type, expected string") + } + did, err := w3c.ParseDID(didStr) + if err != nil { + return err + } + *keydid = KeyCoreDID(*did) + return nil +} diff --git a/internal/core/domain/publicKey.go b/internal/core/domain/publicKey.go index 7cf2232c4..0eab3de78 100644 --- a/internal/core/domain/publicKey.go +++ b/internal/core/domain/publicKey.go @@ -13,6 +13,7 @@ const ( // PublicKey - defines the interface for public keys type PublicKey interface { Equal([]byte) bool + String() string } type bjjPublicKey struct { @@ -34,8 +35,16 @@ func (b *bjjPublicKey) Equal(pubKey []byte) bool { return bytes.Equal(pubKey, compPubKey[:]) } +func (b *bjjPublicKey) String() string { + return "0x" + b.publicKey.String() +} + type unSupportedPublicKeyType struct{} func (u *unSupportedPublicKeyType) Equal([]byte) bool { return false } + +func (u *unSupportedPublicKeyType) String() string { + return "" +} diff --git a/internal/core/ports/key_repository.go b/internal/core/ports/key_repository.go new file mode 100644 index 000000000..44ece35c8 --- /dev/null +++ b/internal/core/ports/key_repository.go @@ -0,0 +1,18 @@ +package ports + +import ( + "context" + + "github.com/google/uuid" + "github.com/iden3/go-iden3-core/v2/w3c" + + "github.com/polygonid/sh-id-platform/internal/core/domain" + "github.com/polygonid/sh-id-platform/internal/db" +) + +// KeyRepository key repository interface +type KeyRepository interface { + Save(ctx context.Context, conn db.Querier, key *domain.Key) (uuid.UUID, error) + GetByPublicKey(ctx context.Context, issuerDID w3c.DID, publicKey string) (*domain.Key, error) + Delete(ctx context.Context, issuerDID w3c.DID, publicKey string) error +} diff --git a/internal/core/ports/key_service.go b/internal/core/ports/key_service.go index eef260aeb..9d82e7929 100644 --- a/internal/core/ports/key_service.go +++ b/internal/core/ports/key_service.go @@ -26,6 +26,7 @@ type KMSKey struct { KeyType kms.KeyType PublicKey string HasAssociatedAuthCredential bool + Name string } // KeyFilter is the filter to use when getting keys @@ -36,7 +37,7 @@ type KeyFilter struct { // KeyService is the service that manages keys type KeyService interface { - CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) + CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType, name string) (kms.KeyID, error) Get(ctx context.Context, did *w3c.DID, keyID string) (*KMSKey, error) GetAll(ctx context.Context, did *w3c.DID, filter KeyFilter) ([]*KMSKey, uint, error) Delete(ctx context.Context, did *w3c.DID, keyID string) error diff --git a/internal/core/services/identity.go b/internal/core/services/identity.go index ddb51a92e..7ac4a5355 100644 --- a/internal/core/services/identity.go +++ b/internal/core/services/identity.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/google/uuid" auth "github.com/iden3/go-iden3-auth/v2" @@ -46,9 +47,11 @@ import ( ) const ( - transitionDelay = time.Minute * 5 - serviceContext = "https://www.w3.org/ns/did/v1" - authReason = "authentication" + transitionDelay = time.Minute * 5 + serviceContext = "https://www.w3.org/ns/did/v1" + authReason = "authentication" + defaultBJJKeyName = "default-bjj" + defaultETHKeyName = "default-eth" ) var ( @@ -93,11 +96,12 @@ type identity struct { revocationStatusResolver *revocationstatus.Resolver networkResolver network.Resolver rhsFactory reversehash.Factory + keyRepository ports.KeyRepository } // NewIdentity creates a new identity // nolint -func NewIdentity(kms kms.KMSType, identityRepository ports.IndentityRepository, imtRepository ports.IdentityMerkleTreeRepository, identityStateRepository ports.IdentityStateRepository, mtservice ports.MtService, qrService ports.QrStoreService, claimsRepository ports.ClaimRepository, revocationRepository ports.RevocationRepository, connectionsRepository ports.ConnectionRepository, storage *db.Storage, verifier *auth.Verifier, sessionRepository ports.SessionRepository, ps pubsub.Client, networkResolver network.Resolver, rhsFactory reversehash.Factory, revocationStatusResolver *revocationstatus.Resolver) ports.IdentityService { +func NewIdentity(kms kms.KMSType, identityRepository ports.IndentityRepository, imtRepository ports.IdentityMerkleTreeRepository, identityStateRepository ports.IdentityStateRepository, mtservice ports.MtService, qrService ports.QrStoreService, claimsRepository ports.ClaimRepository, revocationRepository ports.RevocationRepository, connectionsRepository ports.ConnectionRepository, storage *db.Storage, verifier *auth.Verifier, sessionRepository ports.SessionRepository, ps pubsub.Client, networkResolver network.Resolver, rhsFactory reversehash.Factory, revocationStatusResolver *revocationstatus.Resolver, keyRepository ports.KeyRepository) ports.IdentityService { return &identity{ identityRepository: identityRepository, imtRepository: imtRepository, @@ -116,6 +120,7 @@ func NewIdentity(kms kms.KMSType, identityRepository ports.IndentityRepository, networkResolver: networkResolver, rhsFactory: rhsFactory, revocationStatusResolver: revocationStatusResolver, + keyRepository: keyRepository, } } @@ -630,6 +635,12 @@ func (i *identity) createEthIdentity(ctx context.Context, tx db.Querier, hostURL return nil, nil, err } + ethPublicKey, err := i.kms.PublicKey(key) + if err != nil { + log.Error(ctx, "getting eth public key", "err", err) + return nil, nil, err + } + identity, did, err := i.createEthIdentityFromKeyID(ctx, mts, &key, didOptions, tx) if err != nil { return nil, nil, err @@ -686,6 +697,19 @@ func (i *identity) createEthIdentity(ctx context.Context, tx db.Querier, hostURL return nil, nil, errors.Join(err, errors.New("can't save auth claim")) } + defaultBJJKey := domain.NewKey(*did, authClaimModel.GetPublicKey().String(), defaultBJJKeyName) + _, err = i.keyRepository.Save(ctx, tx, defaultBJJKey) + if err != nil { + return nil, nil, fmt.Errorf("can't save default key: %w", err) + } + + defaultETHKey := domain.NewKey(*did, hexutil.Encode(ethPublicKey), defaultETHKeyName) + _, err = i.keyRepository.Save(ctx, tx, defaultETHKey) + if err != nil { + log.Error(ctx, "saving default eth key", "err", err) + return nil, nil, fmt.Errorf("can't save default eth key: %w", err) + } + return did, identity.State.TreeState().State.BigInt(), nil } @@ -755,6 +779,12 @@ func (i *identity) createIdentity(ctx context.Context, tx db.Querier, hostURL st return nil, nil, fmt.Errorf("can't save identity: %w", err) } + defaultKey := domain.NewKey(*did, authClaimModel.GetPublicKey().String(), defaultBJJKeyName) + _, err = i.keyRepository.Save(ctx, tx, defaultKey) + if err != nil { + return nil, nil, fmt.Errorf("can't save default key: %w", err) + } + resolverPrefix, err := common.ResolverPrefix(did) if err != nil { log.Error(ctx, "getting resolver prefix", "err", err) diff --git a/internal/core/services/identity_test.go b/internal/core/services/identity_test.go index d1004c9fc..999fcb4eb 100644 --- a/internal/core/services/identity_test.go +++ b/internal/core/services/identity_test.go @@ -45,6 +45,7 @@ func Test_identity_CreateIdentity(t *testing.T) { revocationRepository := repositories.NewRevocation() mtService := NewIdentityMerkleTrees(mtRepo) connectionsRepository := repositories.NewConnection() + keyRepository := repositories.NewKey(*storage) reader := common.CreateFile(t) networkResolver, err := network.NewResolver(ctx, cfg, keyStore, reader) @@ -52,7 +53,7 @@ func Test_identity_CreateIdentity(t *testing.T) { rhsFactory := reversehash.NewFactory(*networkResolver, reversehash.DefaultRHSTimeOut) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver, keyRepository) type testConfig struct { name string @@ -125,6 +126,7 @@ func Test_identity_CreateIdentityWithRHSNone(t *testing.T) { revocationRepository := repositories.NewRevocation() mtService := NewIdentityMerkleTrees(mtRepo) connectionsRepository := repositories.NewConnection() + keyRepository := repositories.NewKey(*storage) reader := common.CreateFile(t) networkResolver, err := network.NewResolver(ctx, cfg, keyStore, reader) @@ -143,7 +145,7 @@ func Test_identity_CreateIdentityWithRHSNone(t *testing.T) { }).Return(rhsPublishers, nil) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver, keyRepository) identity, err := identityService.Create(ctx, cfg.ServerUrl, &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: BJJ}) assert.NoError(t, err) assert.NotNil(t, identity.Identifier) @@ -170,7 +172,7 @@ func Test_identity_CreateIdentityWithRHSNone(t *testing.T) { }).Return(rhsPublishers, nil) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver, keyRepository) _, err := identityService.Create(ctx, cfg.ServerUrl, &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: BJJ}) assert.Error(t, err) rhsPublisherReverseHashServiceMock.AssertNumberOfCalls(t, "PublishNodesToRHS", 1) @@ -179,7 +181,7 @@ func Test_identity_CreateIdentityWithRHSNone(t *testing.T) { t.Run("should create ETH identity with RHS", func(t *testing.T) { rhsFactoryMock := reversehash.NewMockFactory(t) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver, keyRepository) identity, err := identityService.Create(ctx, cfg.ServerUrl, &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: ETH}) assert.NoError(t, err) assert.NotNil(t, identity.Identifier) @@ -207,7 +209,7 @@ func Test_identity_CreateIdentityWithRHSNone(t *testing.T) { }).Return(rhsPublishers, nil) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver, keyRepository) identity, err := identityService.Create(ctx, cfg.ServerUrl, &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: BJJ}) assert.NoError(t, err) assert.NotNil(t, identity.Identifier) @@ -235,7 +237,7 @@ func Test_identity_CreateIdentityWithRHSNone(t *testing.T) { }).Return(rhsPublishers, nil) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver, keyRepository) identity, err := identityService.Create(ctx, cfg.ServerUrl, &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: BJJ, AuthCredentialStatus: verifiable.Iden3commRevocationStatusV1}) assert.NoError(t, err) assert.NotNil(t, identity.Identifier) @@ -259,6 +261,7 @@ func Test_identity_CreateIdentityWithRHSOffChain(t *testing.T) { revocationRepository := repositories.NewRevocation() mtService := NewIdentityMerkleTrees(mtRepo) connectionsRepository := repositories.NewConnection() + keyRepository := repositories.NewKey(*storage) reader := common.CreateFileWithRHSOffChain(t) networkResolver, err := network.NewResolver(ctx, cfg, keyStore, reader) @@ -277,7 +280,7 @@ func Test_identity_CreateIdentityWithRHSOffChain(t *testing.T) { }).Return(rhsPublishers, nil) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver, keyRepository) identity, err := identityService.Create(ctx, cfg.ServerUrl, &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: BJJ}) assert.NoError(t, err) assert.NotNil(t, identity.Identifier) @@ -304,7 +307,7 @@ func Test_identity_CreateIdentityWithRHSOffChain(t *testing.T) { }).Return(rhsPublishers, nil) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver, keyRepository) _, err := identityService.Create(ctx, cfg.ServerUrl, &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: BJJ}) assert.Error(t, err) rhsPublisherReverseHashServiceMock.AssertNumberOfCalls(t, "PublishNodesToRHS", 1) @@ -313,7 +316,7 @@ func Test_identity_CreateIdentityWithRHSOffChain(t *testing.T) { t.Run("should create ETH identity with RHS", func(t *testing.T) { rhsFactoryMock := reversehash.NewMockFactory(t) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver, keyRepository) identity, err := identityService.Create(ctx, cfg.ServerUrl, &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: ETH}) assert.NoError(t, err) assert.NotNil(t, identity.Identifier) @@ -341,7 +344,7 @@ func Test_identity_CreateIdentityWithRHSOffChain(t *testing.T) { }).Return(rhsPublishers, nil) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver, keyRepository) identity, err := identityService.Create(ctx, cfg.ServerUrl, &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: BJJ}) assert.NoError(t, err) assert.NotNil(t, identity.Identifier) @@ -369,7 +372,7 @@ func Test_identity_CreateIdentityWithRHSOffChain(t *testing.T) { }).Return(rhsPublishers, nil) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactoryMock, revocationStatusResolver, keyRepository) identity, err := identityService.Create(ctx, cfg.ServerUrl, &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: BJJ, AuthCredentialStatus: verifiable.Iden3ReverseSparseMerkleTreeProof}) assert.NoError(t, err) assert.NotNil(t, identity.Identifier) @@ -393,6 +396,7 @@ func Test_identity_UpdateState(t *testing.T) { revocationRepository := repositories.NewRevocation() mtService := NewIdentityMerkleTrees(mtRepo) connectionsRepository := repositories.NewConnection() + keyRepository := repositories.NewKey(*storage) reader := common.CreateFile(t) networkResolver, err := network.NewResolver(ctx, cfg, keyStore, reader) @@ -400,7 +404,7 @@ func Test_identity_UpdateState(t *testing.T) { rhsFactory := reversehash.NewFactory(*networkResolver, reversehash.DefaultRHSTimeOut) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver, keyRepository) mediaTypeManager := NewMediaTypeManager( map[iden3comm.ProtocolMessage][]string{ @@ -586,6 +590,7 @@ func Test_identity_GetByDID(t *testing.T) { revocationRepository := repositories.NewRevocation() mtService := NewIdentityMerkleTrees(mtRepo) connectionsRepository := repositories.NewConnection() + keyRepository := repositories.NewKey(*storage) reader := common.CreateFile(t) networkResolver, err := network.NewResolver(ctx, cfg, keyStore, reader) @@ -593,7 +598,7 @@ func Test_identity_GetByDID(t *testing.T) { rhsFactory := reversehash.NewFactory(*networkResolver, reversehash.DefaultRHSTimeOut) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver, keyRepository) identity, err := identityService.Create(ctx, "polygon-test", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: BJJ}) assert.NoError(t, err) @@ -643,6 +648,7 @@ func Test_identity_GetLatestStateByID(t *testing.T) { revocationRepository := repositories.NewRevocation() mtService := NewIdentityMerkleTrees(mtRepo) connectionsRepository := repositories.NewConnection() + keyRepository := repositories.NewKey(*storage) reader := common.CreateFile(t) networkResolver, err := network.NewResolver(ctx, cfg, keyStore, reader) @@ -650,7 +656,7 @@ func Test_identity_GetLatestStateByID(t *testing.T) { rhsFactory := reversehash.NewFactory(*networkResolver, reversehash.DefaultRHSTimeOut) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver, keyRepository) identity, err := identityService.Create(ctx, "polygon-test", &ports.DIDCreationOptions{Method: method, Blockchain: blockchain, Network: net, KeyType: BJJ}) assert.NoError(t, err) @@ -710,6 +716,7 @@ func Test_identity_RotateKey(t *testing.T) { revocationRepository := repositories.NewRevocation() mtService := NewIdentityMerkleTrees(mtRepo) connectionsRepository := repositories.NewConnection() + keyRepository := repositories.NewKey(*storage) reader := common.CreateFile(t) networkResolver, err := network.NewResolver(ctx, cfg, keyStore, reader) @@ -717,7 +724,7 @@ func Test_identity_RotateKey(t *testing.T) { rhsFactory := reversehash.NewFactory(*networkResolver, reversehash.DefaultRHSTimeOut) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver, keyRepository) type testConfig struct { name string diff --git a/internal/core/services/key.go b/internal/core/services/key.go index 6fd47a8da..c0b936dcf 100644 --- a/internal/core/services/key.go +++ b/internal/core/services/key.go @@ -3,6 +3,7 @@ package services import ( "context" b64 "encoding/base64" + "errors" "sort" "strings" @@ -15,24 +16,27 @@ import ( "github.com/polygonid/sh-id-platform/internal/core/ports" "github.com/polygonid/sh-id-platform/internal/kms" "github.com/polygonid/sh-id-platform/internal/log" + "github.com/polygonid/sh-id-platform/internal/repositories" ) // Key is the service that manages keys type Key struct { - kms *kms.KMS - claimService ports.ClaimService + kms *kms.KMS + claimService ports.ClaimService + keyRepository ports.KeyRepository } // NewKey creates a new Key -func NewKey(kms *kms.KMS, claimService ports.ClaimService) ports.KeyService { +func NewKey(kms *kms.KMS, claimService ports.ClaimService, keyRepository ports.KeyRepository) ports.KeyService { return &Key{ - kms: kms, - claimService: claimService, + kms: kms, + claimService: claimService, + keyRepository: keyRepository, } } // CreateKey creates a new key for the given DID -func (ks *Key) CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType) (kms.KeyID, error) { +func (ks *Key) CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType, name string) (kms.KeyID, error) { var keyID kms.KeyID var err error if keyType == kms.KeyTypeBabyJubJub { @@ -56,6 +60,20 @@ func (ks *Key) CreateKey(ctx context.Context, did *w3c.DID, keyType kms.KeyType) } } + publicKeyAsBytes, err := ks.kms.PublicKey(keyID) + if err != nil { + log.Error(ctx, "failed to get public key", "err", err) + return kms.KeyID{}, err + } + + publicKey := hexutil.Encode(publicKeyAsBytes) + keyToSave := domain.NewKey(*did, publicKey, name) + _, err = ks.keyRepository.Save(ctx, nil, keyToSave) + if err != nil { + log.Error(ctx, "failed to save key", "err", err) + return kms.KeyID{}, err + } + encodedKeyID := b64.StdEncoding.EncodeToString([]byte(keyID.ID)) log.Info(ctx, "key created successfully", "keyID", encodedKeyID) keyID.ID = encodedKeyID @@ -92,6 +110,7 @@ func (ks *Key) Get(ctx context.Context, did *w3c.DID, keyID string) (*ports.KMSK } hasAssociatedAuthCredential := false + defaultKeyName := "" switch keyType { case kms.KeyTypeBabyJubJub: hasAssociatedAuthCredential, _, err = ks.hasAssociatedAuthCredential(ctx, did, publicKey) @@ -99,21 +118,34 @@ func (ks *Key) Get(ctx context.Context, did *w3c.DID, keyID string) (*ports.KMSK log.Error(ctx, "failed to check if key has associated auth credential", "err", err) return nil, err } + defaultKeyName = defaultBJJKeyName case kms.KeyTypeEthereum: hasAssociatedAuthCredential, err = ks.isAssociatedWithIdentity(ctx, did, publicKey) if err != nil { log.Error(ctx, "failed to check if key has associated auth credential", "err", err) return nil, err } + defaultKeyName = defaultETHKeyName default: return nil, ports.ErrInvalidKeyType } + keyInfo, err := ks.keyRepository.GetByPublicKey(ctx, *did, hexutil.Encode(publicKey)) + if err != nil { + if !errors.Is(err, repositories.ErrKeyNotFound) { + return nil, err + } + keyInfo = &domain.Key{ + Name: defaultKeyName, + } + } + return &ports.KMSKey{ KeyID: keyID, KeyType: keyType, PublicKey: hexutil.Encode(publicKey), HasAssociatedAuthCredential: hasAssociatedAuthCredential, + Name: keyInfo.Name, }, nil } @@ -220,6 +252,10 @@ func (ks *Key) Delete(ctx context.Context, did *w3c.DID, keyID string) error { return ports.ErrInvalidKeyType } + if err := ks.keyRepository.Delete(ctx, *did, hexutil.Encode(publicKey)); err != nil { + log.Error(ctx, "failed to delete key", "err", err) + return err + } return ks.kms.Delete(ctx, kmsKeyID) } diff --git a/internal/core/services/link_test.go b/internal/core/services/link_test.go index 4730975d2..1550e61fd 100644 --- a/internal/core/services/link_test.go +++ b/internal/core/services/link_test.go @@ -35,6 +35,7 @@ func Test_link_issueClaim(t *testing.T) { schemaRepository := repositories.NewSchema(*storage) mtService := NewIdentityMerkleTrees(mtRepo) connectionsRepository := repositories.NewConnection() + keyRepository := repositories.NewKey(*storage) reader := common.CreateFile(t) networkResolver, err := networkPkg.NewResolver(ctx, cfg, keyStore, reader) @@ -42,7 +43,7 @@ func Test_link_issueClaim(t *testing.T) { rhsFactory := reversehash.NewFactory(*networkResolver, reversehash.DefaultRHSTimeOut) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver, keyRepository) sessionRepository := repositories.NewSessionCached(cachex) schemaService := NewSchema(schemaRepository, docLoader) diff --git a/internal/core/services/notification_test.go b/internal/core/services/notification_test.go index cc9c99a06..77abc02cc 100644 --- a/internal/core/services/notification_test.go +++ b/internal/core/services/notification_test.go @@ -39,6 +39,7 @@ func TestNotification_SendNotification(t *testing.T) { mtService := NewIdentityMerkleTrees(mtRepo) revocationRepository := repositories.NewRevocation() connectionsRepository := repositories.NewConnection() + keyRepository := repositories.NewKey(*storage) reader := common.CreateFile(t) networkResolver, err := networkPkg.NewResolver(ctx, cfg, keyStore, reader) @@ -46,7 +47,7 @@ func TestNotification_SendNotification(t *testing.T) { rhsFactory := reversehash.NewFactory(*networkResolver, reversehash.DefaultRHSTimeOut) revocationStatusResolver := revocationstatus.NewRevocationStatusResolver(*networkResolver) - identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver) + identityService := NewIdentity(keyStore, identityRepo, mtRepo, identityStateRepo, mtService, nil, claimsRepo, revocationRepository, connectionsRepository, storage, nil, nil, pubsub.NewMock(), *networkResolver, rhsFactory, revocationStatusResolver, keyRepository) mediaTypeManager := NewMediaTypeManager( map[iden3comm.ProtocolMessage][]string{ diff --git a/internal/db/schema/migrations/202412101722170_add_keys_table.sql b/internal/db/schema/migrations/202412101722170_add_keys_table.sql new file mode 100644 index 000000000..faf15b958 --- /dev/null +++ b/internal/db/schema/migrations/202412101722170_add_keys_table.sql @@ -0,0 +1,18 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE keys( + id UUID PRIMARY KEY NOT NULL, + issuer_did text NOT NULL, + name text NOT NULL, + public_key text NOT NULL, + created_at timestamptz NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamptz NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT keys_unique_name UNIQUE (issuer_did, name), + CONSTRAINT keys_identities_id_key foreign key (issuer_did) references identities (identifier) +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE IF EXISTS keys; +-- +goose StatementEnd \ No newline at end of file diff --git a/internal/repositories/key.go b/internal/repositories/key.go new file mode 100644 index 000000000..a9515fdcd --- /dev/null +++ b/internal/repositories/key.go @@ -0,0 +1,69 @@ +package repositories + +import ( + "context" + "errors" + "strings" + + "github.com/google/uuid" + "github.com/iden3/go-iden3-core/v2/w3c" + + "github.com/polygonid/sh-id-platform/internal/core/domain" + "github.com/polygonid/sh-id-platform/internal/db" + "github.com/polygonid/sh-id-platform/internal/log" +) + +// ErrKeyNotFound key not found error +var ErrKeyNotFound = errors.New("key not found") + +type key struct { + conn db.Storage +} + +// NewKey returns a new key repository +func NewKey(conn db.Storage) *key { + return &key{ + conn, + } +} + +// Save saves a key +func (k *key) Save(ctx context.Context, conn db.Querier, key *domain.Key) (uuid.UUID, error) { + if conn == nil { + conn = k.conn.Pgx + } + + sql := `INSERT INTO keys (id, issuer_did, public_key, name) + VALUES($1, $2, $3, $4) ON CONFLICT (id) DO + UPDATE SET public_key=$3, name=$4` + _, err := conn.Exec(ctx, sql, key.ID, key.IssuerCoreDID().String(), key.PublicKey, key.Name) + if err != nil { + return uuid.Nil, err + } + return key.ID, err +} + +// GetByPublicKey returns a key by its public key +func (k *key) GetByPublicKey(ctx context.Context, issuerDID w3c.DID, publicKey string) (*domain.Key, error) { + sql := `SELECT id, issuer_did, public_key, name + FROM keys WHERE issuer_did=$1 and public_key=$2` + row := k.conn.Pgx.QueryRow(ctx, sql, issuerDID.String(), publicKey) + + key := domain.Key{} + err := row.Scan(&key.ID, &key.IssuerDID, &key.PublicKey, &key.Name) + if err != nil { + log.Error(ctx, "error getting key by public key", "err", err) + if strings.Contains(err.Error(), "no rows in result set") { + return nil, ErrKeyNotFound + } + return nil, err + } + return &key, nil +} + +// Delete deletes a key by its public key +func (k *key) Delete(ctx context.Context, issuerDID w3c.DID, publicKey string) error { + sql := `DELETE FROM keys WHERE issuer_did=$1 AND public_key=$2` + _, err := k.conn.Pgx.Exec(ctx, sql, issuerDID.String(), publicKey) + return err +} diff --git a/internal/repositories/key_test.go b/internal/repositories/key_test.go new file mode 100644 index 000000000..54810a829 --- /dev/null +++ b/internal/repositories/key_test.go @@ -0,0 +1,95 @@ +package repositories + +import ( + "context" + "errors" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/polygonid/sh-id-platform/internal/core/domain" +) + +func TestKey_Save(t *testing.T) { + keyRepository := NewKey(*storage) + + ctx := context.Background() + did := randomDID(t) + _, err := storage.Pgx.Exec(ctx, "INSERT INTO identities (identifier, keytype) VALUES ($1, $2)", did.String(), "BJJ") + assert.NoError(t, err) + + t.Run("should save a new key", func(t *testing.T) { + key := domain.Key{ + ID: uuid.New(), + IssuerDID: domain.KeyCoreDID(did), + PublicKey: "publicKey", + Name: "name", + } + id, err := keyRepository.Save(context.Background(), storage.Pgx, &key) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, id) + assert.Equal(t, key.ID, id) + }) + + t.Run("should get an error", func(t *testing.T) { + key := domain.Key{ + ID: uuid.New(), + IssuerDID: domain.KeyCoreDID(did), + PublicKey: "publicKey", + Name: "name_1", + } + + id, err := keyRepository.Save(context.Background(), storage.Pgx, &key) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, id) + assert.Equal(t, key.ID, id) + + key2 := domain.Key{ + ID: uuid.New(), + IssuerDID: domain.KeyCoreDID(did), + PublicKey: "publicKey2", + Name: "name_1", + } + + id, err = keyRepository.Save(context.Background(), storage.Pgx, &key2) + require.Error(t, err) + require.Equal(t, uuid.Nil, id) + }) +} + +func TestKey_GetByPublicKey(t *testing.T) { + keyRepository := NewKey(*storage) + ctx := context.Background() + did := randomDID(t) + _, err := storage.Pgx.Exec(ctx, "INSERT INTO identities (identifier, keytype) VALUES ($1, $2)", did.String(), "BJJ") + assert.NoError(t, err) + + key := domain.Key{ + ID: uuid.New(), + IssuerDID: domain.KeyCoreDID(did), + PublicKey: "publicKey", + Name: "name" + uuid.New().String(), + } + id, err := keyRepository.Save(context.Background(), storage.Pgx, &key) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, id) + assert.Equal(t, key.ID, id) + + t.Run("should get the key by public key", func(t *testing.T) { + keyFromDatabase, err := keyRepository.GetByPublicKey(ctx, did, key.PublicKey) + require.NoError(t, err) + assert.Equal(t, key.ID, keyFromDatabase.ID) + assert.Equal(t, key.IssuerDID, keyFromDatabase.IssuerDID) + assert.Equal(t, key.PublicKey, keyFromDatabase.PublicKey) + assert.Equal(t, key.Name, keyFromDatabase.Name) + }) + + t.Run("should get an error - ErrKeyNotFound", func(t *testing.T) { + keyFromDatabase, err := keyRepository.GetByPublicKey(ctx, did, "wrong public key") + assert.Error(t, err) + assert.Nil(t, keyFromDatabase) + assert.True(t, errors.Is(err, ErrKeyNotFound)) + }) +} diff --git a/internal/repositories/main_test.go b/internal/repositories/main_test.go index d58adcca7..d208d681e 100644 --- a/internal/repositories/main_test.go +++ b/internal/repositories/main_test.go @@ -2,9 +2,14 @@ package repositories import ( "context" + "crypto/rand" "os" "testing" + core "github.com/iden3/go-iden3-core/v2" + "github.com/iden3/go-iden3-core/v2/w3c" + "github.com/stretchr/testify/require" + "github.com/polygonid/sh-id-platform/internal/config" "github.com/polygonid/sh-id-platform/internal/db" "github.com/polygonid/sh-id-platform/internal/db/tests" @@ -47,3 +52,16 @@ func lookupPostgresURL() string { } return con } + +func randomDID(t *testing.T) w3c.DID { + t.Helper() + typ, err := core.BuildDIDType(core.DIDMethodIden3, core.Privado, core.Main) + var genesis [27]byte + require.NoError(t, err) + _, err = rand.Read(genesis[:]) + require.NoError(t, err) + id := core.NewID(typ, genesis) + did, err := core.ParseDIDFromID(id) + require.NoError(t, err) + return *did +} From 227778d951430810658f035408e22175dc34f8e8 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Tue, 10 Dec 2024 20:47:53 -0300 Subject: [PATCH 22/62] fix: test --- internal/api/networks_test.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/internal/api/networks_test.go b/internal/api/networks_test.go index 527a72b4d..889f68e20 100644 --- a/internal/api/networks_test.go +++ b/internal/api/networks_test.go @@ -53,13 +53,6 @@ func TestServer_GetSupportedNetworks(t *testing.T) { var response GetSupportedNetworks200JSONResponse assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) assert.Equal(t, 2, len(response)) - assert.Equal(t, "polygon", response[0].Blockchain) - assert.Equal(t, []NetworkData{ - { - Name: "amoy", - CredentialStatus: []string{"Iden3commRevocationStatusV1.0"}, - }, - }, response[0].Networks) } }) } From 34a92c41812d002fd38e1672d49b5a64f7852084 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 11 Dec 2024 07:21:26 -0300 Subject: [PATCH 23/62] chore: add sort by name --- internal/api/keys_test.go | 31 +++++++++++++++++++++++++++++-- internal/core/services/key.go | 11 ++++++----- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index f6d5daa59..1c5605bff 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -92,7 +92,24 @@ func TestServer_CreateKey(t *testing.T) { }, }, { - name: "should create a eth key", + name: "should get an error - empty name", + auth: authOk, + did: did.String(), + body: CreateKeyRequest{ + KeyType: "BJJ", + Name: "", + }, + expected: expected{ + httpCode: http.StatusBadRequest, + response: CreateKey400JSONResponse{ + N400JSONResponse: N400JSONResponse{ + Message: "name is required", + }, + }, + }, + }, + { + name: "should create an eth key", auth: authOk, did: didETH.String(), body: CreateKeyRequest{ @@ -270,7 +287,7 @@ func TestServer_GetKeys(t *testing.T) { t.Run("should get the keys for bjj identity with pagination", func(t *testing.T) { for i := 0; i < 20; i++ { - name := fmt.Sprintf("my-key-%d", i) + name := fmt.Sprintf("my-key-%s", string('A'+rune(i+1))) _, err = server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub, name) require.NoError(t, err) } @@ -305,6 +322,16 @@ func TestServer_GetKeys(t *testing.T) { all := append(response.Items, response1.Items...) assert.Equal(t, 1, countAuthCredentials(t, all)) + + // Check that the keys are sorted by name + for i, key := range all { + assert.Equal(t, BJJ, string(key.KeyType)) + if i == 0 { + assert.Equal(t, "default-bjj", key.Name) + } else { + assert.Equal(t, fmt.Sprintf("my-key-%s", string('A'+rune(i))), key.Name) + } + } }) t.Run("should get the keys for eth identity", func(t *testing.T) { diff --git a/internal/core/services/key.go b/internal/core/services/key.go index c0b936dcf..62d78fc18 100644 --- a/internal/core/services/key.go +++ b/internal/core/services/key.go @@ -169,11 +169,6 @@ func (ks *Key) GetAll(ctx context.Context, did *w3c.DID, filter ports.KeyFilter) end = len(keyIDs) } - sort.Slice(keyIDs, func(i, j int) bool { - return keyIDs[i].ID < keyIDs[j].ID - }) - - keyIDs = keyIDs[start:end] keys := make([]*ports.KMSKey, len(keyIDs)) for i, keyID := range keyIDs { key, err := ks.Get(ctx, did, keyID.ID) @@ -183,6 +178,12 @@ func (ks *Key) GetAll(ctx context.Context, did *w3c.DID, filter ports.KeyFilter) } keys[i] = key } + + sort.Slice(keys, func(i, j int) bool { + return keys[i].Name < keys[j].Name + }) + + keys = keys[start:end] return keys, total, nil } From 6ca09bc31c9f378fb4e9b266f01415d28214e401 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 11 Dec 2024 07:41:48 -0300 Subject: [PATCH 24/62] chore: add test for identity creation --- internal/core/services/identity_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/internal/core/services/identity_test.go b/internal/core/services/identity_test.go index 999fcb4eb..30d529edb 100644 --- a/internal/core/services/identity_test.go +++ b/internal/core/services/identity_test.go @@ -47,6 +47,9 @@ func Test_identity_CreateIdentity(t *testing.T) { connectionsRepository := repositories.NewConnection() keyRepository := repositories.NewKey(*storage) + claimService := NewClaim(claimsRepo, nil, nil, mtService, identityStateRepo, docLoader, storage, cfg.ServerUrl, pubsub.NewMock(), ipfsGateway, nil, nil, cfg.UniversalLinks) + keyService := NewKey(keyStore, claimService, keyRepository) + reader := common.CreateFile(t) networkResolver, err := network.NewResolver(ctx, cfg, keyStore, reader) require.NoError(t, err) @@ -112,6 +115,16 @@ func Test_identity_CreateIdentity(t *testing.T) { } else { assert.False(t, isEthID) } + + allKeys, total, err := keyService.GetAll(ctx, DID, ports.KeyFilter{MaxResults: 10, Page: 1}) + require.NoError(t, err) + if tc.options.KeyType == ETH { + assert.Equal(t, uint(2), total) + assert.Equal(t, 2, len(allKeys)) + } else { + assert.Equal(t, uint(1), total) + assert.Equal(t, 1, len(allKeys)) + } } }) } From 138bbea5264025b075a307317234c4f6181eabdc Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 11 Dec 2024 09:01:39 -0300 Subject: [PATCH 25/62] fix: misspelling --- api/api.yaml | 4 ++-- internal/api/api.gen.go | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index 3daae12d9..d48d8fc94 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -2443,7 +2443,7 @@ components: - keyType - publicKey - isAuthCredential - - Name + - name properties: id: type: string @@ -2463,7 +2463,7 @@ components: type: boolean x-omitempty: false example: true - Name: + name: type: string x-omitempty: false example: "my key" diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 31a9cad5a..66e43276e 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -430,12 +430,11 @@ type IssuerDescription struct { // Key defines model for Key. type Key struct { - Name string `json:"Name"` - // Id base64 encoded keyID Id string `json:"id"` IsAuthCredential bool `json:"isAuthCredential"` KeyType KeyKeyType `json:"keyType"` + Name string `json:"name"` PublicKey string `json:"publicKey"` } From 7cb38e98bc1cfcdcaaab0f7111a512b13fe1f584 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 11 Dec 2024 12:11:08 -0300 Subject: [PATCH 26/62] chore: change key types names --- api/api.yaml | 8 ++++---- internal/api/api.gen.go | 12 ++++++------ internal/api/keys.go | 27 ++++++++++++++++++++------- internal/api/keys_test.go | 18 +++++++++--------- internal/core/services/identity.go | 4 ++-- 5 files changed, 41 insertions(+), 28 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index d48d8fc94..593b4f9c9 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -2419,8 +2419,8 @@ components: keyType: type: string x-omitempty: false - example: "BJJ" - enum: [ BJJ, ETH ] + example: "babyjujJub" + enum: [ babyjujJub, secp256k1 ] name: type: string example: "my key" @@ -2453,8 +2453,8 @@ components: keyType: type: string x-omitempty: false - example: "BJJ" - enum: [ BJJ, ETH ] + example: "babyjujJub" + enum: [ babyjujJub, secp256k1 ] publicKey: type: string x-omitempty: false diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 66e43276e..786bdd697 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -46,8 +46,8 @@ const ( // Defines values for CreateIdentityRequestDidMetadataType. const ( - CreateIdentityRequestDidMetadataTypeBJJ CreateIdentityRequestDidMetadataType = "BJJ" - CreateIdentityRequestDidMetadataTypeETH CreateIdentityRequestDidMetadataType = "ETH" + BJJ CreateIdentityRequestDidMetadataType = "BJJ" + ETH CreateIdentityRequestDidMetadataType = "ETH" ) // Defines values for CreateIdentityResponseCredentialStatusType. @@ -59,8 +59,8 @@ const ( // Defines values for CreateKeyRequestKeyType. const ( - CreateKeyRequestKeyTypeBJJ CreateKeyRequestKeyType = "BJJ" - CreateKeyRequestKeyTypeETH CreateKeyRequestKeyType = "ETH" + CreateKeyRequestKeyTypeBabyjujJub CreateKeyRequestKeyType = "babyjujJub" + CreateKeyRequestKeyTypeSecp256k1 CreateKeyRequestKeyType = "secp256k1" ) // Defines values for DisplayMethodType. @@ -84,8 +84,8 @@ const ( // Defines values for KeyKeyType. const ( - BJJ KeyKeyType = "BJJ" - ETH KeyKeyType = "ETH" + KeyKeyTypeBabyjujJub KeyKeyType = "babyjujJub" + KeyKeyTypeSecp256k1 KeyKeyType = "secp256k1" ) // Defines values for LinkStatus. diff --git a/internal/api/keys.go b/internal/api/keys.go index b017a824f..1b01fcb58 100644 --- a/internal/api/keys.go +++ b/internal/api/keys.go @@ -12,11 +12,11 @@ import ( // CreateKey is the handler for the POST /keys endpoint. func (s *Server) CreateKey(ctx context.Context, request CreateKeyRequestObject) (CreateKeyResponseObject, error) { - if string(request.Body.KeyType) != string(BJJ) && string(request.Body.KeyType) != string(ETH) { - log.Error(ctx, "invalid key type. BJJ and ETH Keys are supported") + if string(request.Body.KeyType) != string(KeyKeyTypeBabyjujJub) && string(request.Body.KeyType) != string(KeyKeyTypeSecp256k1) { + log.Error(ctx, "invalid key type. babyjujJub and secp256k1 keys are supported") return CreateKey400JSONResponse{ N400JSONResponse{ - Message: "invalid key type. BJJ and ETH Keys are supported", + Message: "invalid key type. babyjujJub and secp256k1 keys are supported are supported", }, }, nil } @@ -30,7 +30,7 @@ func (s *Server) CreateKey(ctx context.Context, request CreateKeyRequestObject) }, nil } - keyID, err := s.keyService.CreateKey(ctx, request.Identifier.did(), kms.KeyType(request.Body.KeyType), request.Body.Name) + keyID, err := s.keyService.CreateKey(ctx, request.Identifier.did(), convertKeyTypeFromRequest(request.Body.KeyType), request.Body.Name) if err != nil { log.Error(ctx, "creating key", "err", err) return CreateKey500JSONResponse{ @@ -81,11 +81,10 @@ func (s *Server) GetKey(ctx context.Context, request GetKeyRequestObject) (GetKe }, }, nil } - encodedKeyID := b64.StdEncoding.EncodeToString([]byte(key.KeyID)) return GetKey200JSONResponse{ Id: encodedKeyID, - KeyType: KeyKeyType(key.KeyType), + KeyType: convertKeyTypeToResponse(key.KeyType), PublicKey: key.PublicKey, IsAuthCredential: key.HasAssociatedAuthCredential, Name: key.Name, @@ -131,7 +130,7 @@ func (s *Server) GetKeys(ctx context.Context, request GetKeysRequestObject) (Get encodedKeyID := b64.StdEncoding.EncodeToString([]byte(key.KeyID)) items = append(items, Key{ Id: encodedKeyID, - KeyType: KeyKeyType(key.KeyType), + KeyType: convertKeyTypeToResponse(key.KeyType), PublicKey: key.PublicKey, IsAuthCredential: key.HasAssociatedAuthCredential, Name: key.Name, @@ -200,3 +199,17 @@ func (s *Server) DeleteKey(ctx context.Context, request DeleteKeyRequestObject) Message: "key deleted", }, nil } + +func convertKeyTypeToResponse(keyType kms.KeyType) KeyKeyType { + if keyType == "BJJ" { + return KeyKeyTypeBabyjujJub + } + return KeyKeyTypeSecp256k1 +} + +func convertKeyTypeFromRequest(keyType CreateKeyRequestKeyType) kms.KeyType { + if string(keyType) == string(KeyKeyTypeBabyjujJub) { + return "BJJ" + } + return "ETH" +} diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index 1c5605bff..5027aea4f 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -68,7 +68,7 @@ func TestServer_CreateKey(t *testing.T) { auth: authOk, did: did.String(), body: CreateKeyRequest{ - KeyType: BJJ, + KeyType: CreateKeyRequestKeyType(KeyKeyTypeBabyjujJub), Name: "my-bjj-key", }, expected: expected{ @@ -86,7 +86,7 @@ func TestServer_CreateKey(t *testing.T) { httpCode: http.StatusBadRequest, response: CreateKey400JSONResponse{ N400JSONResponse: N400JSONResponse{ - Message: "invalid key type. BJJ and ETH Keys are supported", + Message: "invalid key type. babyjujJub and secp256k1 keys are supported are supported", }, }, }, @@ -96,7 +96,7 @@ func TestServer_CreateKey(t *testing.T) { auth: authOk, did: did.String(), body: CreateKeyRequest{ - KeyType: "BJJ", + KeyType: CreateKeyRequestKeyType(KeyKeyTypeBabyjujJub), Name: "", }, expected: expected{ @@ -113,7 +113,7 @@ func TestServer_CreateKey(t *testing.T) { auth: authOk, did: didETH.String(), body: CreateKeyRequest{ - KeyType: ETH, + KeyType: CreateKeyRequestKeyType(KeyKeyTypeSecp256k1), Name: "my-eth-key", }, expected: expected{ @@ -223,7 +223,7 @@ func TestServer_GetKey(t *testing.T) { assert.NotNil(t, response.Id) assert.Equal(t, keyID, response.Id) assert.NotNil(t, response.PublicKey) - assert.Equal(t, BJJ, response.KeyType) + assert.Equal(t, KeyKeyTypeBabyjujJub, response.KeyType) assert.False(t, response.IsAuthCredential) assert.True(t, "my-key" == response.Name) case http.StatusBadRequest: @@ -287,7 +287,7 @@ func TestServer_GetKeys(t *testing.T) { t.Run("should get the keys for bjj identity with pagination", func(t *testing.T) { for i := 0; i < 20; i++ { - name := fmt.Sprintf("my-key-%s", string('A'+rune(i+1))) + name := fmt.Sprintf("z-key-%s", string('A'+rune(i+1))) _, err = server.keyService.CreateKey(ctx, did, kms.KeyTypeBabyJubJub, name) require.NoError(t, err) } @@ -325,11 +325,11 @@ func TestServer_GetKeys(t *testing.T) { // Check that the keys are sorted by name for i, key := range all { - assert.Equal(t, BJJ, string(key.KeyType)) + assert.Equal(t, string(KeyKeyTypeBabyjujJub), string(key.KeyType)) if i == 0 { - assert.Equal(t, "default-bjj", key.Name) + assert.Equal(t, "pubkey-bjj", key.Name) } else { - assert.Equal(t, fmt.Sprintf("my-key-%s", string('A'+rune(i))), key.Name) + assert.Equal(t, fmt.Sprintf("z-key-%s", string('A'+rune(i))), key.Name) } } }) diff --git a/internal/core/services/identity.go b/internal/core/services/identity.go index 7ac4a5355..b0826959b 100644 --- a/internal/core/services/identity.go +++ b/internal/core/services/identity.go @@ -50,8 +50,8 @@ const ( transitionDelay = time.Minute * 5 serviceContext = "https://www.w3.org/ns/did/v1" authReason = "authentication" - defaultBJJKeyName = "default-bjj" - defaultETHKeyName = "default-eth" + defaultBJJKeyName = "pubkey-bjj" + defaultETHKeyName = "pubkey-eth" ) var ( From df73f0f329681f0cd9bffa4e662ecc48ec9b07a9 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 11 Dec 2024 12:59:06 -0300 Subject: [PATCH 27/62] fix: name of babyjubjub key --- api/api.yaml | 8 ++++---- internal/api/api.gen.go | 4 ++-- internal/api/keys.go | 6 +++--- internal/api/keys_test.go | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index 593b4f9c9..902ea0694 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -2419,8 +2419,8 @@ components: keyType: type: string x-omitempty: false - example: "babyjujJub" - enum: [ babyjujJub, secp256k1 ] + example: "babyjubJub" + enum: [ babyjubJub, secp256k1 ] name: type: string example: "my key" @@ -2453,8 +2453,8 @@ components: keyType: type: string x-omitempty: false - example: "babyjujJub" - enum: [ babyjujJub, secp256k1 ] + example: "babyjubJub" + enum: [ babyjubJub, secp256k1 ] publicKey: type: string x-omitempty: false diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 786bdd697..c3822f1b4 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -59,7 +59,7 @@ const ( // Defines values for CreateKeyRequestKeyType. const ( - CreateKeyRequestKeyTypeBabyjujJub CreateKeyRequestKeyType = "babyjujJub" + CreateKeyRequestKeyTypeBabyjubJub CreateKeyRequestKeyType = "babyjubJub" CreateKeyRequestKeyTypeSecp256k1 CreateKeyRequestKeyType = "secp256k1" ) @@ -84,7 +84,7 @@ const ( // Defines values for KeyKeyType. const ( - KeyKeyTypeBabyjujJub KeyKeyType = "babyjujJub" + KeyKeyTypeBabyjubJub KeyKeyType = "babyjubJub" KeyKeyTypeSecp256k1 KeyKeyType = "secp256k1" ) diff --git a/internal/api/keys.go b/internal/api/keys.go index 1b01fcb58..604822109 100644 --- a/internal/api/keys.go +++ b/internal/api/keys.go @@ -12,7 +12,7 @@ import ( // CreateKey is the handler for the POST /keys endpoint. func (s *Server) CreateKey(ctx context.Context, request CreateKeyRequestObject) (CreateKeyResponseObject, error) { - if string(request.Body.KeyType) != string(KeyKeyTypeBabyjujJub) && string(request.Body.KeyType) != string(KeyKeyTypeSecp256k1) { + if string(request.Body.KeyType) != string(KeyKeyTypeBabyjubJub) && string(request.Body.KeyType) != string(KeyKeyTypeSecp256k1) { log.Error(ctx, "invalid key type. babyjujJub and secp256k1 keys are supported") return CreateKey400JSONResponse{ N400JSONResponse{ @@ -202,13 +202,13 @@ func (s *Server) DeleteKey(ctx context.Context, request DeleteKeyRequestObject) func convertKeyTypeToResponse(keyType kms.KeyType) KeyKeyType { if keyType == "BJJ" { - return KeyKeyTypeBabyjujJub + return KeyKeyTypeBabyjubJub } return KeyKeyTypeSecp256k1 } func convertKeyTypeFromRequest(keyType CreateKeyRequestKeyType) kms.KeyType { - if string(keyType) == string(KeyKeyTypeBabyjujJub) { + if string(keyType) == string(KeyKeyTypeBabyjubJub) { return "BJJ" } return "ETH" diff --git a/internal/api/keys_test.go b/internal/api/keys_test.go index 5027aea4f..edd5e12ed 100644 --- a/internal/api/keys_test.go +++ b/internal/api/keys_test.go @@ -68,7 +68,7 @@ func TestServer_CreateKey(t *testing.T) { auth: authOk, did: did.String(), body: CreateKeyRequest{ - KeyType: CreateKeyRequestKeyType(KeyKeyTypeBabyjujJub), + KeyType: CreateKeyRequestKeyType(KeyKeyTypeBabyjubJub), Name: "my-bjj-key", }, expected: expected{ @@ -96,7 +96,7 @@ func TestServer_CreateKey(t *testing.T) { auth: authOk, did: did.String(), body: CreateKeyRequest{ - KeyType: CreateKeyRequestKeyType(KeyKeyTypeBabyjujJub), + KeyType: CreateKeyRequestKeyType(KeyKeyTypeBabyjubJub), Name: "", }, expected: expected{ @@ -223,7 +223,7 @@ func TestServer_GetKey(t *testing.T) { assert.NotNil(t, response.Id) assert.Equal(t, keyID, response.Id) assert.NotNil(t, response.PublicKey) - assert.Equal(t, KeyKeyTypeBabyjujJub, response.KeyType) + assert.Equal(t, KeyKeyTypeBabyjubJub, response.KeyType) assert.False(t, response.IsAuthCredential) assert.True(t, "my-key" == response.Name) case http.StatusBadRequest: @@ -325,7 +325,7 @@ func TestServer_GetKeys(t *testing.T) { // Check that the keys are sorted by name for i, key := range all { - assert.Equal(t, string(KeyKeyTypeBabyjujJub), string(key.KeyType)) + assert.Equal(t, string(KeyKeyTypeBabyjubJub), string(key.KeyType)) if i == 0 { assert.Equal(t, "pubkey-bjj", key.Name) } else { From bee8d0682a5e28a376a01851041a8f2402e30869 Mon Sep 17 00:00:00 2001 From: Oleksandr Raspopov Date: Thu, 12 Dec 2024 11:41:20 +0100 Subject: [PATCH 28/62] chore: update keys table, detail pages --- ui/src/adapters/api/keys.ts | 38 +++++++++++- ui/src/components/keys/CreateKey.tsx | 13 +++- ui/src/components/keys/Key.tsx | 90 +++++++++++++++++++++++----- ui/src/components/keys/KeysTable.tsx | 30 ++++------ ui/src/domain/key.ts | 7 ++- 5 files changed, 140 insertions(+), 38 deletions(-) diff --git a/ui/src/adapters/api/keys.ts b/ui/src/adapters/api/keys.ts index 20bc18c65..eed6a088a 100644 --- a/ui/src/adapters/api/keys.ts +++ b/ui/src/adapters/api/keys.ts @@ -11,8 +11,9 @@ import { Resource } from "src/utils/types"; const keyParser = getStrictParser()( z.object({ id: z.string(), - isAuthCoreClaim: z.boolean(), + isAuthCredential: z.boolean(), keyType: z.nativeEnum(KeyType), + name: z.string(), publicKey: z.string(), }) ); @@ -60,7 +61,7 @@ export async function getKey({ env: Env; identifier: string; keyID: string; - signal: AbortSignal; + signal?: AbortSignal; }): Promise> { try { const response = await axios({ @@ -80,6 +81,7 @@ export async function getKey({ export type CreateKey = { keyType: KeyType; + name: string; }; export async function createKey({ @@ -106,3 +108,35 @@ export async function createKey({ return buildErrorResponse(error); } } + +export type UpdateKey = { + name: string; +}; + +export async function updateKeyName({ + env, + identifier, + keyID, + payload, +}: { + env: Env; + identifier: string; + keyID: string; + payload: UpdateKey; +}) { + try { + await axios({ + baseURL: env.api.url, + data: payload, + headers: { + Authorization: buildAuthorizationHeader(env), + }, + method: "PATCH", + url: `${API_VERSION}/identities/${identifier}/keys/${keyID}`, + }); + + return buildSuccessResponse(undefined); + } catch (error) { + return buildErrorResponse(error); + } +} diff --git a/ui/src/components/keys/CreateKey.tsx b/ui/src/components/keys/CreateKey.tsx index 9947a3da9..ff3947b27 100644 --- a/ui/src/components/keys/CreateKey.tsx +++ b/ui/src/components/keys/CreateKey.tsx @@ -1,4 +1,4 @@ -import { App, Button, Card, Divider, Flex, Form, Select, Space } from "antd"; +import { App, Button, Card, Divider, Flex, Form, Input, Select, Space } from "antd"; import { useNavigate } from "react-router-dom"; import { CreateKey as CreateKeyType, createKey } from "src/adapters/api/keys"; @@ -43,11 +43,20 @@ export function CreateKey() {
+ + + + (); + const [key, setKey] = useState>({ status: "pending", }); @@ -26,7 +32,7 @@ export function Key() { const { keyID } = useParams(); const fetchKey = useCallback( - async (signal: AbortSignal) => { + async (signal?: AbortSignal) => { if (keyID) { setKey({ status: "loading" }); @@ -55,13 +61,31 @@ export function Key() { return aborter; }, [fetchKey]); - if (!identifier) { - return ; + if (!keyID) { + return ; } + const handleEditName = (formValues: UpdateKey) => { + return void updateKeyName({ + env, + identifier, + keyID, + payload: { name: formValues.name.trim() }, + }).then((response) => { + if (response.success) { + void fetchKey().then(() => { + setNameEditable(false); + void message.success("Key edited successfully"); + }); + } else { + void message.error(response.error.message); + } + }); + }; + return ( @@ -86,10 +110,49 @@ export function Key() { ); } else { return ( - + + {nameEditable ? ( + + + + + + +