Repository: TBD54566975/ssi-service
Pull request #559 (2023-06-22 22:23:23)

Fixing duplicate did:webs bug
# Overview
You can no longer create 2 did webs with the same didWebId

# Description
Fixes https://github.com/TBD54566975/ssi-service/issues/554 

# How Has This Been Tested?
Added to unit test
Manual postman test

<img width="1014" alt="image" src="https://github.com/TBD54566975/ssi-service/assets/5314059/5abc0e41-d5c7-49e8-8173-82fd05967beb">


codecov-commenter: ## [Codecov](https://app.codecov.io/gh/TBD54566975/ssi-service/pull/559?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) Report
> Merging [#559](https://app.codecov.io/gh/TBD54566975/ssi-service/pull/559?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) (9bf33fe) into [main](https://app.codecov.io/gh/TBD54566975/ssi-service/commit/f9a5907ec583992981c95f965593aa7e5257392e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) (f9a5907) will **decrease** coverage by `0.07%`.
> The diff coverage is `0.00%`.

> :exclamation: Current head 9bf33fe differs from pull request most recent head a08243c. Consider uploading reports for the commit a08243c to get more accurate results

```diff
@@            Coverage Diff             @@
##             main     #559      +/-   ##
==========================================
- Coverage   24.05%   23.99%   -0.07%     
==========================================
  Files          46       46              
  Lines        5105     5118      +13     
==========================================
  Hits         1228     1228              
- Misses       3649     3662      +13     
  Partials      228      228              
```


| [Impacted Files](https://app.codecov.io/gh/TBD54566975/ssi-service/pull/559?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) | Coverage Δ | |
|---|---|---|
| [pkg/service/did/storage.go](https://app.codecov.io/gh/TBD54566975/ssi-service/pull/559?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975#diff-cGtnL3NlcnZpY2UvZGlkL3N0b3JhZ2UuZ28=) | `55.47% <0.00%> (-2.38%)` | :arrow_down: |
| [pkg/service/did/web.go](https://app.codecov.io/gh/TBD54566975/ssi-service/pull/559?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975#diff-cGtnL3NlcnZpY2UvZGlkL3dlYi5nbw==) | `0.00% <0.00%> (ø)` | |



Patch:
diff --git a/pkg/service/did/storage.go b/pkg/service/did/storage.go
index cf4d463..cb2e0ec 100644
--- a/pkg/service/did/storage.go
+++ b/pkg/service/did/storage.go
@@ -116,6 +116,16 @@ func (ds *Storage) GetDIDDefault(ctx context.Context, id string) (*DefaultStored
 	return outType, nil
 }
 
+// DIDExists returns true if DID exists, false if not
+func (ds *Storage) DIDExists(ctx context.Context, id string) (bool, error) {
+	ns, err := getNamespaceForDID(id)
+	if err != nil {
+		return false, sdkutil.LoggingErrorMsg(err, fmt.Sprintf("could not get namespace for did: %s", id))
+	}
+
+	return ds.db.Exists(ctx, ns, id)
+}
+
 // ListDIDs attempts to get all DIDs for a given method. It will return those it can even if it has trouble with some.
 // The out parameter must be a pointer to a struct for a type that implement the StoredDID interface.
 // The result is a slice of the type of the out parameter (an array of pointers to the type of the out parameter).)
diff --git a/pkg/service/did/web.go b/pkg/service/did/web.go
index 3dd4b1f..b3d1d25 100644
--- a/pkg/service/did/web.go
+++ b/pkg/service/did/web.go
@@ -69,6 +69,15 @@ func (h *webHandler) CreateDID(ctx context.Context, request CreateDIDRequest) (*
 		return nil, errors.Wrap(err, "could not validate did:web")
 	}
 
+	exists, err := h.storage.DIDExists(ctx, opts.DIDWebID)
+	if err != nil {
+		return nil, errors.Wrapf(err, "error getting DID: %s", opts.DIDWebID)
+	}
+
+	if exists {
+		return nil, fmt.Errorf("did with id<%s> already exists", opts.DIDWebID)
+	}
+
 	pubKey, privKey, err := crypto.GenerateKeyByKeyType(request.KeyType)
 	if err != nil {
 		return nil, errors.Wrap(err, "could not generate key for did:web")


Test patch:
diff --git a/integration/didweb_resolver_integration_test.go b/integration/didweb_resolver_integration_test.go
index 224cec9..e5b8fed 100644
--- a/integration/didweb_resolver_integration_test.go
+++ b/integration/didweb_resolver_integration_test.go
@@ -27,4 +27,5 @@ func TestResolveDIDWebIntegration(t *testing.T) {
 	didDocumentID, err := getJSONElement(resolvedOutput, "$.didDocument.id")
 	assert.NoError(t, err)
 	assert.Equal(t, "did:web:tbd.website", didDocumentID)
+
 }
diff --git a/integration/testdata/did-web-input.json b/integration/testdata/did-web-input.json
index 824b099..71515b8 100644
--- a/integration/testdata/did-web-input.json
+++ b/integration/testdata/did-web-input.json
@@ -1,6 +1,6 @@
 {
   "keyType":"Ed25519",
   "options": {
-    "didWebId": "did:web:tbd.website"
+    "didWebId": "did:web:example.com"
   }
 }
\ No newline at end of file
diff --git a/pkg/server/server_did_test.go b/pkg/server/server_did_test.go
index 6e52522..b942088 100644
--- a/pkg/server/server_did_test.go
+++ b/pkg/server/server_did_test.go
@@ -245,6 +245,66 @@ func TestDIDAPI(t *testing.T) {
 				assert.Contains(tt, resp.DID.ID, didsdk.IONMethod)
 			})
 
+			t.Run("Test Create Duplicate DID:Webs", func(tt *testing.T) {
+				db := test.ServiceStorage(t)
+				require.NotEmpty(tt, db)
+
+				_, keyStoreService := testKeyStore(tt, db)
+				didService := testDIDRouter(tt, db, keyStoreService, []string{"web"})
+
+				// reset recorder between calls
+				w := httptest.NewRecorder()
+
+				// good options
+				options := did.CreateWebDIDOptions{DIDWebID: "did:web:example.com"}
+
+				params := map[string]string{
+					"method": "web",
+				}
+
+				// with body, good key type with options
+				createDIDRequest := router.CreateDIDByMethodRequest{
+					KeyType: crypto.Ed25519,
+					Options: options,
+				}
+
+				requestReader := newRequestValue(tt, createDIDRequest)
+				req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/dids/web", requestReader)
+
+				gock.New("https://example.com").
+					Get("/.well-known/did.json").
+					Reply(200).
+					BodyString(`{"didDocument": {"id": "did:web:example.com"}}`)
+				defer gock.Off()
+
+				c := newRequestContextWithParams(w, req, params)
+				didService.CreateDIDByMethod(c)
+				assert.True(tt, util.Is2xxResponse(w.Code))
+
+				var resp router.CreateDIDByMethodResponse
+				err := json.NewDecoder(w.Body).Decode(&resp)
+				assert.NoError(tt, err)
+				assert.Contains(tt, resp.DID.ID, didsdk.WebMethod)
+
+				// reset recorder between calls
+				w = httptest.NewRecorder()
+
+				requestReader2 := newRequestValue(tt, createDIDRequest)
+				req2 := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/dids/web", requestReader2)
+				gock.New("https://example.com").
+					Get("/.well-known/did.json").
+					Reply(200).
+					BodyString(`{"didDocument": {"id": "did:web:example.com"}}`)
+				defer gock.Off()
+
+				// Make sure it can't make another did:web of the same DIDWebID
+				c = newRequestContextWithParams(w, req2, params)
+				didService.CreateDIDByMethod(c)
+				assert.Equal(tt, w.Code, 500)
+
+				assert.Contains(tt, w.Body.String(), "already exists")
+			})
+
 			t.Run("Test Get DID By Method", func(tt *testing.T) {
 				db := test.ServiceStorage(t)
 				require.NotEmpty(tt, db)