Repository: ory/kratos
Pull request #990 (2021-01-15 09:48:04)

fix: linking a connection may result in system error
## Related issue

#694 

## Proposed changes

<!--
Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request.
-->

## Checklist

<!--
Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of
them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.
-->

- [x] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md).
- [x] I have read the [security policy](../security/policy).
- [x] I confirm that this pull request does not address a security
      vulnerability. If this pull request addresses a security. vulnerability, I
      confirm that I got green light (please contact
      [security@ory.sh](mailto:security@ory.sh)) from the maintainers to push
      the changes.
- [x] I have added tests that prove my fix is effective or that my feature
      works.
- [x] I have added or changed [the documentation](docs/docs).

## Further comments

<!--
If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution
you did and what alternatives you considered, etc...
-->



Patch:
diff --git a/docs/docs/guides/login-session.mdx b/docs/docs/guides/login-session.mdx
index 87cd80bf8..1c3cfc268 100644
--- a/docs/docs/guides/login-session.mdx
+++ b/docs/docs/guides/login-session.mdx
@@ -110,7 +110,7 @@ A typical session payload will look like this:
 <TabItem value="nodejs">
 
 ```js
-import { PublicApi } from '@oryd/kratos-client'
+import { PublicApi } from '@ory/kratos-client'
 
 const publicEndpoint = new PublicApi(config.kratos.public)
 const protect = (req: Request, res: Response, next: NextFunction) => {
diff --git a/docs/docs/milestones.md b/docs/docs/milestones.md
index f1d570c7d..990b513b6 100644
--- a/docs/docs/milestones.md
+++ b/docs/docs/milestones.md
@@ -181,6 +181,8 @@ New feature or request.
       [@hackerman](https://github.com/aeneasr)
 - [ ] Password policies: make api.pwnedpasswords.com configurable
       ([kratos#969](https://github.com/ory/kratos/issues/969))
+- [ ] Access identity schema information (e.g. usernames) within the jsonnet
+      mapper ([kratos#987](https://github.com/ory/kratos/issues/987))
 - [x] Support remote argon2 execution
       ([kratos#357](https://github.com/ory/kratos/issues/357)) -
       [@hackerman](https://github.com/aeneasr)
diff --git a/docs/docs/sdk.md b/docs/docs/sdk.md
index c32d74a44..5a62fcf10 100644
--- a/docs/docs/sdk.md
+++ b/docs/docs/sdk.md
@@ -21,7 +21,7 @@ repositories:
 - [Python](https://pypi.org/project/ory-kratos-client/)
 - [PHP](https://packagist.org/packages/ory/kratos-client)
 - [Go](https://github.com/ory/kratos-client-go)
-- [NodeJS](https://www.npmjs.com/package/@oryd/kratos-client) (with TypeScript)
+- [NodeJS](https://www.npmjs.com/package/@ory/kratos-client) (with TypeScript)
 - [Java](https://search.maven.org/artifact/sh.ory.kratos/kratos-client)
 - [Ruby](https://rubygems.org/gems/ory-kratos-client)
 
diff --git a/docs/docs/self-service.mdx b/docs/docs/self-service.mdx
index ae9d9169f..b51ae9766 100644
--- a/docs/docs/self-service.mdx
+++ b/docs/docs/self-service.mdx
@@ -343,7 +343,7 @@ snippet:
 
 ```jsx
 // import React from ...
-import { PublicApi } from '@oryd/kratos-client'
+import { PublicApi } from '@ory/kratos-client'
 
 const kratos = new PublicApi('http://127.0.0.1:4455/.ory/kratos/public')
 
diff --git a/docs/docs/self-service/flows/code/login/samples/api/init.js.txt b/docs/docs/self-service/flows/code/login/samples/api/init.js.txt
index f9f702619..c42d07917 100644
--- a/docs/docs/self-service/flows/code/login/samples/api/init.js.txt
+++ b/docs/docs/self-service/flows/code/login/samples/api/init.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 kratos.initializeSelfServiceLoginViaAPIFlow()
diff --git a/docs/docs/self-service/flows/code/login/samples/get.js.txt b/docs/docs/self-service/flows/code/login/samples/get.js.txt
index b4587ceee..3d13dd3c6 100644
--- a/docs/docs/self-service/flows/code/login/samples/get.js.txt
+++ b/docs/docs/self-service/flows/code/login/samples/get.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 const flowId = '' // usually something like: req.query.flow
diff --git a/docs/docs/self-service/flows/code/recovery/samples/api/init.js.txt b/docs/docs/self-service/flows/code/recovery/samples/api/init.js.txt
index 5057896ca..6057b3d51 100644
--- a/docs/docs/self-service/flows/code/recovery/samples/api/init.js.txt
+++ b/docs/docs/self-service/flows/code/recovery/samples/api/init.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 kratos.initializeSelfServiceRecoveryViaAPIFlow()
diff --git a/docs/docs/self-service/flows/code/recovery/samples/get.js.txt b/docs/docs/self-service/flows/code/recovery/samples/get.js.txt
index 9f4da1ea7..eae853aaf 100644
--- a/docs/docs/self-service/flows/code/recovery/samples/get.js.txt
+++ b/docs/docs/self-service/flows/code/recovery/samples/get.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 const flowId = '' // usually something like: req.query.flow
diff --git a/docs/docs/self-service/flows/code/registration/samples/api/init.js.txt b/docs/docs/self-service/flows/code/registration/samples/api/init.js.txt
index f3b086bb3..e677e63f8 100644
--- a/docs/docs/self-service/flows/code/registration/samples/api/init.js.txt
+++ b/docs/docs/self-service/flows/code/registration/samples/api/init.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 kratos.initializeSelfServiceRegistrationViaAPIFlow()
diff --git a/docs/docs/self-service/flows/code/registration/samples/get.js.txt b/docs/docs/self-service/flows/code/registration/samples/get.js.txt
index c08f058af..67fec53c2 100644
--- a/docs/docs/self-service/flows/code/registration/samples/get.js.txt
+++ b/docs/docs/self-service/flows/code/registration/samples/get.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 const flowId = '' // usually something like: req.query.flow
diff --git a/docs/docs/self-service/flows/code/settings/samples/api/init.js.txt b/docs/docs/self-service/flows/code/settings/samples/api/init.js.txt
index 4e7f2fc21..9b1e6ae3f 100644
--- a/docs/docs/self-service/flows/code/settings/samples/api/init.js.txt
+++ b/docs/docs/self-service/flows/code/settings/samples/api/init.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 kratos.initializeSelfServiceSettingsViaAPIFlow()
diff --git a/docs/docs/self-service/flows/code/settings/samples/get.js.txt b/docs/docs/self-service/flows/code/settings/samples/get.js.txt
index 9418a70d8..42360a723 100644
--- a/docs/docs/self-service/flows/code/settings/samples/get.js.txt
+++ b/docs/docs/self-service/flows/code/settings/samples/get.js.txt
@@ -1,4 +1,4 @@
-import { AdminApi, Configuration } from '@oryd/kratos-client';
+import { AdminApi, Configuration } from '@ory/kratos-client';
 
 // We are using the ORY Kratos Admin API (:4434) to avoid having to set the session cookie / session token:
 const kratos = new AdminApi(new Configuration({ basePath: 'http://127.0.0.1:4434/' }));
diff --git a/docs/docs/self-service/flows/code/verification/samples/api/init.js.txt b/docs/docs/self-service/flows/code/verification/samples/api/init.js.txt
index 0cca754cc..360e120f4 100644
--- a/docs/docs/self-service/flows/code/verification/samples/api/init.js.txt
+++ b/docs/docs/self-service/flows/code/verification/samples/api/init.js.txt
@@ -1,4 +1,4 @@
-import {PublicApi} from '@oryd/kratos-client'
+import {PublicApi} from '@ory/kratos-client'
 
 const kratos = new PublicApi('http://127.0.0.1:4433/')
 kratos.initializeSelfServiceVerificationViaAPIFlow()
diff --git a/docs/docs/self-service/flows/code/verification/samples/get.js.txt b/docs/docs/self-service/flows/code/verification/samples/get.js.txt
index 493d39e4b..fe8b5400d 100644
--- a/docs/docs/self-service/flows/code/verification/samples/get.js.txt
+++ b/docs/docs/self-service/flows/code/verification/samples/get.js.txt
@@ -1,4 +1,4 @@
-import {CommonApi} from '@oryd/kratos-client'
+import {CommonApi} from '@ory/kratos-client'
 
 const kratos = new CommonApi('http://127.0.0.1:4433/')
 
diff --git a/docs/sidebar.json b/docs/sidebar.json
index 25d1b4cf7..9c86cbd5d 100644
--- a/docs/sidebar.json
+++ b/docs/sidebar.json
@@ -84,6 +84,31 @@
         "cli/kratos-serve", 
         "cli/kratos-version"
       ]
+    }, 
+    {
+      "items": [
+        "cli/kratos", 
+        "cli/kratos-hashers", 
+        "cli/kratos-hashers-argon2", 
+        "cli/kratos-hashers-argon2-calibrate", 
+        "cli/kratos-identities", 
+        "cli/kratos-identities-delete", 
+        "cli/kratos-identities-get", 
+        "cli/kratos-identities-import", 
+        "cli/kratos-identities-list", 
+        "cli/kratos-identities-patch", 
+        "cli/kratos-identities-validate", 
+        "cli/kratos-jsonnet", 
+        "cli/kratos-jsonnet-format", 
+        "cli/kratos-jsonnet-lint", 
+        "cli/kratos-migrate", 
+        "cli/kratos-migrate-sql", 
+        "cli/kratos-remote", 
+        "cli/kratos-remote-status", 
+        "cli/kratos-remote-version", 
+        "cli/kratos-serve", 
+        "cli/kratos-version"
+      ]
     }
   ],
   "Debug & Help": [
diff --git a/docs/versioned_docs/version-v0.5/guides/login-session.mdx b/docs/versioned_docs/version-v0.5/guides/login-session.mdx
index 87cd80bf8..1c3cfc268 100644
--- a/docs/versioned_docs/version-v0.5/guides/login-session.mdx
+++ b/docs/versioned_docs/version-v0.5/guides/login-session.mdx
@@ -110,7 +110,7 @@ A typical session payload will look like this:
 <TabItem value="nodejs">
 
 ```js
-import { PublicApi } from '@oryd/kratos-client'
+import { PublicApi } from '@ory/kratos-client'
 
 const publicEndpoint = new PublicApi(config.kratos.public)
 const protect = (req: Request, res: Response, next: NextFunction) => {
diff --git a/docs/versioned_docs/version-v0.5/sdk.md b/docs/versioned_docs/version-v0.5/sdk.md
index c32d74a44..5a62fcf10 100644
--- a/docs/versioned_docs/version-v0.5/sdk.md
+++ b/docs/versioned_docs/version-v0.5/sdk.md
@@ -21,7 +21,7 @@ repositories:
 - [Python](https://pypi.org/project/ory-kratos-client/)
 - [PHP](https://packagist.org/packages/ory/kratos-client)
 - [Go](https://github.com/ory/kratos-client-go)
-- [NodeJS](https://www.npmjs.com/package/@oryd/kratos-client) (with TypeScript)
+- [NodeJS](https://www.npmjs.com/package/@ory/kratos-client) (with TypeScript)
 - [Java](https://search.maven.org/artifact/sh.ory.kratos/kratos-client)
 - [Ruby](https://rubygems.org/gems/ory-kratos-client)
 
diff --git a/docs/versioned_docs/version-v0.5/self-service.mdx b/docs/versioned_docs/version-v0.5/self-service.mdx
index ae9d9169f..b51ae9766 100644
--- a/docs/versioned_docs/version-v0.5/self-service.mdx
+++ b/docs/versioned_docs/version-v0.5/self-service.mdx
@@ -343,7 +343,7 @@ snippet:
 
 ```jsx
 // import React from ...
-import { PublicApi } from '@oryd/kratos-client'
+import { PublicApi } from '@ory/kratos-client'
 
 const kratos = new PublicApi('http://127.0.0.1:4455/.ory/kratos/public')
 
diff --git a/docs/versioned_docs/version-v0.5/self-service/flows/code/login/samples/api/init.js.txt b/docs/versioned_docs/version-v0.5/self-service/flows/code/login/samples/api/init.js.txt
index f9f702619..c42d07917 100644
--- a/docs/versioned_docs/version-v0.5/self-service/flows/code/login/samples/api/init.js.txt
+++ b/docs/versioned_docs/version-v0.5/self-service/flows/code/login/samples/api/init.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 kratos.initializeSelfServiceLoginViaAPIFlow()
diff --git a/docs/versioned_docs/version-v0.5/self-service/flows/code/login/samples/get.js.txt b/docs/versioned_docs/version-v0.5/self-service/flows/code/login/samples/get.js.txt
index b4587ceee..3d13dd3c6 100644
--- a/docs/versioned_docs/version-v0.5/self-service/flows/code/login/samples/get.js.txt
+++ b/docs/versioned_docs/version-v0.5/self-service/flows/code/login/samples/get.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 const flowId = '' // usually something like: req.query.flow
diff --git a/docs/versioned_docs/version-v0.5/self-service/flows/code/recovery/samples/api/init.js.txt b/docs/versioned_docs/version-v0.5/self-service/flows/code/recovery/samples/api/init.js.txt
index 5057896ca..6057b3d51 100644
--- a/docs/versioned_docs/version-v0.5/self-service/flows/code/recovery/samples/api/init.js.txt
+++ b/docs/versioned_docs/version-v0.5/self-service/flows/code/recovery/samples/api/init.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 kratos.initializeSelfServiceRecoveryViaAPIFlow()
diff --git a/docs/versioned_docs/version-v0.5/self-service/flows/code/recovery/samples/get.js.txt b/docs/versioned_docs/version-v0.5/self-service/flows/code/recovery/samples/get.js.txt
index 9f4da1ea7..eae853aaf 100644
--- a/docs/versioned_docs/version-v0.5/self-service/flows/code/recovery/samples/get.js.txt
+++ b/docs/versioned_docs/version-v0.5/self-service/flows/code/recovery/samples/get.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 const flowId = '' // usually something like: req.query.flow
diff --git a/docs/versioned_docs/version-v0.5/self-service/flows/code/registration/samples/api/init.js.txt b/docs/versioned_docs/version-v0.5/self-service/flows/code/registration/samples/api/init.js.txt
index f3b086bb3..e677e63f8 100644
--- a/docs/versioned_docs/version-v0.5/self-service/flows/code/registration/samples/api/init.js.txt
+++ b/docs/versioned_docs/version-v0.5/self-service/flows/code/registration/samples/api/init.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 kratos.initializeSelfServiceRegistrationViaAPIFlow()
diff --git a/docs/versioned_docs/version-v0.5/self-service/flows/code/registration/samples/get.js.txt b/docs/versioned_docs/version-v0.5/self-service/flows/code/registration/samples/get.js.txt
index c08f058af..67fec53c2 100644
--- a/docs/versioned_docs/version-v0.5/self-service/flows/code/registration/samples/get.js.txt
+++ b/docs/versioned_docs/version-v0.5/self-service/flows/code/registration/samples/get.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 const flowId = '' // usually something like: req.query.flow
diff --git a/docs/versioned_docs/version-v0.5/self-service/flows/code/settings/samples/api/init.js.txt b/docs/versioned_docs/version-v0.5/self-service/flows/code/settings/samples/api/init.js.txt
index 4e7f2fc21..9b1e6ae3f 100644
--- a/docs/versioned_docs/version-v0.5/self-service/flows/code/settings/samples/api/init.js.txt
+++ b/docs/versioned_docs/version-v0.5/self-service/flows/code/settings/samples/api/init.js.txt
@@ -1,4 +1,4 @@
-import { Configuration, PublicApi } from '@oryd/kratos-client';
+import { Configuration, PublicApi } from '@ory/kratos-client';
 const kratos = new PublicApi(new Configuration({ basePath: 'http://127.0.0.1:4433/' }));
 
 kratos.initializeSelfServiceSettingsViaAPIFlow()
diff --git a/docs/versioned_docs/version-v0.5/self-service/flows/code/settings/samples/get.js.txt b/docs/versioned_docs/version-v0.5/self-service/flows/code/settings/samples/get.js.txt
index 9418a70d8..42360a723 100644
--- a/docs/versioned_docs/version-v0.5/self-service/flows/code/settings/samples/get.js.txt
+++ b/docs/versioned_docs/version-v0.5/self-service/flows/code/settings/samples/get.js.txt
@@ -1,4 +1,4 @@
-import { AdminApi, Configuration } from '@oryd/kratos-client';
+import { AdminApi, Configuration } from '@ory/kratos-client';
 
 // We are using the ORY Kratos Admin API (:4434) to avoid having to set the session cookie / session token:
 const kratos = new AdminApi(new Configuration({ basePath: 'http://127.0.0.1:4434/' }));
diff --git a/docs/versioned_docs/version-v0.5/self-service/flows/code/verification/samples/api/init.js.txt b/docs/versioned_docs/version-v0.5/self-service/flows/code/verification/samples/api/init.js.txt
index 0cca754cc..360e120f4 100644
--- a/docs/versioned_docs/version-v0.5/self-service/flows/code/verification/samples/api/init.js.txt
+++ b/docs/versioned_docs/version-v0.5/self-service/flows/code/verification/samples/api/init.js.txt
@@ -1,4 +1,4 @@
-import {PublicApi} from '@oryd/kratos-client'
+import {PublicApi} from '@ory/kratos-client'
 
 const kratos = new PublicApi('http://127.0.0.1:4433/')
 kratos.initializeSelfServiceVerificationViaAPIFlow()
diff --git a/docs/versioned_docs/version-v0.5/self-service/flows/code/verification/samples/get.js.txt b/docs/versioned_docs/version-v0.5/self-service/flows/code/verification/samples/get.js.txt
index 493d39e4b..fe8b5400d 100644
--- a/docs/versioned_docs/version-v0.5/self-service/flows/code/verification/samples/get.js.txt
+++ b/docs/versioned_docs/version-v0.5/self-service/flows/code/verification/samples/get.js.txt
@@ -1,4 +1,4 @@
-import {CommonApi} from '@oryd/kratos-client'
+import {CommonApi} from '@ory/kratos-client'
 
 const kratos = new CommonApi('http://127.0.0.1:4433/')
 
diff --git a/selfservice/flow/settings/hook.go b/selfservice/flow/settings/hook.go
index 21ae7bd3c..a1b37970f 100644
--- a/selfservice/flow/settings/hook.go
+++ b/selfservice/flow/settings/hook.go
@@ -2,6 +2,8 @@ package settings
 
 import (
 	"fmt"
+	"github.com/ory/kratos/schema"
+	"github.com/ory/x/sqlcon"
 	"net/http"
 	"time"
 
@@ -130,6 +132,9 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request,
 			e.d.Logger().WithError(err).Debug("Modifying protected field requires re-authentication.")
 			return errors.WithStack(NewFlowNeedsReAuth())
 		}
+		if errors.Is(err, sqlcon.ErrUniqueViolation) {
+			return schema.NewDuplicateCredentialsError()
+		}
 		return err
 	}
 	e.d.Audit().


Test patch:
diff --git a/selfservice/strategy/oidc/strategy_settings_test.go b/selfservice/strategy/oidc/strategy_settings_test.go
index 0a21ef77c..55a637f2e 100644
--- a/selfservice/strategy/oidc/strategy_settings_test.go
+++ b/selfservice/strategy/oidc/strategy_settings_test.go
@@ -392,11 +392,9 @@ func TestSettingsStrategy(t *testing.T) {
 
 			agent, provider := "githuber", "google"
 			body, res, _ := link(t, agent, provider)
-			assert.Contains(t, res.Request.URL.String(), errTS.URL)
 
-			t.Logf("%s", body)
-			assert.EqualValues(t, 409, gjson.GetBytes(body, `0.code`).Int())
-			assert.Contains(t, gjson.GetBytes(body, `0.message`).String(), "insert or update resource because a resource")
+			assert.Contains(t, res.Request.URL.String(), uiTS.URL)
+			assert.Contains(t, gjson.GetBytes(body, "methods.oidc.config.messages.0.text").String(), "An account with the same identifier (email, phone, username, ...) exists already.", "%s", body)
 		})
 
 		t.Run("case=should not be able to link a connection which is missing the ID token", func(t *testing.T) {