diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index aa83b577faea..c06f0dc3bc58 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -53,7 +53,7 @@ jobs: steps: - run: | docker create --name cockroach -p 26257:26257 \ - cockroachdb/cockroach:v22.1.3 start-single-node --insecure + cockroachdb/cockroach:v22.2.6 start-single-node --insecure docker start cockroach name: Start CockroachDB - run: | @@ -145,7 +145,7 @@ jobs: node-version: 16 - run: | docker create --name cockroach -p 26257:26257 \ - cockroachdb/cockroach:v20.2.5 start-single-node --insecure + cockroachdb/cockroach:v22.2.6 start-single-node --insecure docker start cockroach name: Start CockroachDB - uses: browser-actions/setup-chrome@latest diff --git a/.schema/api.openapi.json b/.schema/api.openapi.json index fe62c9cda00c..4297cfe045e9 100644 --- a/.schema/api.openapi.json +++ b/.schema/api.openapi.json @@ -1143,7 +1143,7 @@ "type": "array" }, "verificationFlow": { - "description": "Used to verify an out-of-band communication\nchannel such as an email address or a phone number.\n\nFor more information head over to: https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation", + "description": "Used to verify an out-of-band communication\nchannel such as an email address or a phone number.\n\nFor more information head over to: https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation", "properties": { "active": { "description": "Active, if set, contains the registration method that is being used. It is initially\nnot set.", @@ -2860,7 +2860,7 @@ }, "/self-service/verification/api": { "get": { - "description": "This endpoint initiates a verification flow for API clients such as mobile devices, smart TVs, and so on.\n\nTo fetch an existing verification flow call `/self-service/verification/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "This endpoint initiates a verification flow for API clients such as mobile devices, smart TVs, and so on.\n\nTo fetch an existing verification flow call `/self-service/verification/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "initializeSelfServiceVerificationViaAPIFlow", "responses": { "200": { @@ -2902,7 +2902,7 @@ }, "/self-service/verification/browser": { "get": { - "description": "This endpoint initializes a browser-based account verification flow. Once initialized, the browser will be redirected to\n`selfservice.flows.verification.ui_url` with the flow ID set as the query parameter `?flow=`.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "This endpoint initializes a browser-based account verification flow. Once initialized, the browser will be redirected to\n`selfservice.flows.verification.ui_url` with the flow ID set as the query parameter `?flow=`.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "initializeSelfServiceVerificationViaBrowserFlow", "responses": { "302": { @@ -2927,7 +2927,7 @@ }, "/self-service/verification/flows": { "get": { - "description": "This endpoint returns a verification flow's context with, for example, error details and other information.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "This endpoint returns a verification flow's context with, for example, error details and other information.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "getSelfServiceVerificationFlow", "parameters": [ { @@ -2991,7 +2991,7 @@ }, "/self-service/verification/methods/link": { "post": { - "description": "Use this endpoint to complete a verification flow using the link method. This endpoint\nbehaves differently for API and browser flows and has several states:\n\n`choose_method` expects `flow` (in the URL query) and `email` (in the body) to be sent\nand works with API- and Browser-initiated flows.\nFor API clients it either returns a HTTP 200 OK when the form is valid and HTTP 400 OK when the form is invalid\nand a HTTP 302 Found redirect with a fresh verification flow if the flow was otherwise invalid (e.g. expired).\nFor Browser clients it returns a HTTP 302 Found redirect to the Verification UI URL with the Verification Flow ID appended.\n`sent_email` is the success state after `choose_method` and allows the user to request another verification email. It\nworks for both API and Browser-initiated flows and returns the same responses as the flow in `choose_method` state.\n`passed_challenge` expects a `token` to be sent in the URL query and given the nature of the flow (\"sending a verification link\")\ndoes not have any API capabilities. The server responds with a HTTP 302 Found redirect either to the Settings UI URL\n(if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with\na new Verification Flow ID which contains an error message that the verification link was invalid.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "Use this endpoint to complete a verification flow using the link method. This endpoint\nbehaves differently for API and browser flows and has several states:\n\n`choose_method` expects `flow` (in the URL query) and `email` (in the body) to be sent\nand works with API- and Browser-initiated flows.\nFor API clients it either returns a HTTP 200 OK when the form is valid and HTTP 400 OK when the form is invalid\nand a HTTP 302 Found redirect with a fresh verification flow if the flow was otherwise invalid (e.g. expired).\nFor Browser clients it returns a HTTP 302 Found redirect to the Verification UI URL with the Verification Flow ID appended.\n`sent_email` is the success state after `choose_method` and allows the user to request another verification email. It\nworks for both API and Browser-initiated flows and returns the same responses as the flow in `choose_method` state.\n`passed_challenge` expects a `token` to be sent in the URL query and given the nature of the flow (\"sending a verification link\")\ndoes not have any API capabilities. The server responds with a HTTP 302 Found redirect either to the Settings UI URL\n(if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with\na new Verification Flow ID which contains an error message that the verification link was invalid.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "completeSelfServiceVerificationFlowWithLinkMethod", "parameters": [ { diff --git a/.schema/openapi.json b/.schema/openapi.json index da9c0c742d55..ca625ad3913e 100644 --- a/.schema/openapi.json +++ b/.schema/openapi.json @@ -1216,7 +1216,7 @@ "type": "array" }, "verificationFlow": { - "description": "Used to verify an out-of-band communication\nchannel such as an email address or a phone number.\n\nFor more information head over to: https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation", + "description": "Used to verify an out-of-band communication\nchannel such as an email address or a phone number.\n\nFor more information head over to: https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation", "properties": { "active": { "description": "Active, if set, contains the registration method that is being used. It is initially\nnot set.", @@ -2909,7 +2909,7 @@ }, "/self-service/verification/api": { "get": { - "description": "This endpoint initiates a verification flow for API clients such as mobile devices, smart TVs, and so on.\n\nTo fetch an existing verification flow call `/self-service/verification/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "This endpoint initiates a verification flow for API clients such as mobile devices, smart TVs, and so on.\n\nTo fetch an existing verification flow call `/self-service/verification/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "initializeSelfServiceVerificationViaAPIFlow", "responses": { "200": { @@ -2951,7 +2951,7 @@ }, "/self-service/verification/browser": { "get": { - "description": "This endpoint initializes a browser-based account verification flow. Once initialized, the browser will be redirected to\n`selfservice.flows.verification.ui_url` with the flow ID set as the query parameter `?flow=`.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "This endpoint initializes a browser-based account verification flow. Once initialized, the browser will be redirected to\n`selfservice.flows.verification.ui_url` with the flow ID set as the query parameter `?flow=`.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "initializeSelfServiceVerificationViaBrowserFlow", "responses": { "302": { @@ -2976,7 +2976,7 @@ }, "/self-service/verification/flows": { "get": { - "description": "This endpoint returns a verification flow's context with, for example, error details and other information.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "This endpoint returns a verification flow's context with, for example, error details and other information.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "getSelfServiceVerificationFlow", "parameters": [ { @@ -3040,7 +3040,7 @@ }, "/self-service/verification/methods/link": { "post": { - "description": "Use this endpoint to complete a verification flow using the link method. This endpoint\nbehaves differently for API and browser flows and has several states:\n\n`choose_method` expects `flow` (in the URL query) and `email` (in the body) to be sent\nand works with API- and Browser-initiated flows.\nFor API clients it either returns a HTTP 200 OK when the form is valid and HTTP 400 OK when the form is invalid\nand a HTTP 302 Found redirect with a fresh verification flow if the flow was otherwise invalid (e.g. expired).\nFor Browser clients it returns a HTTP 302 Found redirect to the Verification UI URL with the Verification Flow ID appended.\n`sent_email` is the success state after `choose_method` and allows the user to request another verification email. It\nworks for both API and Browser-initiated flows and returns the same responses as the flow in `choose_method` state.\n`passed_challenge` expects a `token` to be sent in the URL query and given the nature of the flow (\"sending a verification link\")\ndoes not have any API capabilities. The server responds with a HTTP 302 Found redirect either to the Settings UI URL\n(if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with\na new Verification Flow ID which contains an error message that the verification link was invalid.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "Use this endpoint to complete a verification flow using the link method. This endpoint\nbehaves differently for API and browser flows and has several states:\n\n`choose_method` expects `flow` (in the URL query) and `email` (in the body) to be sent\nand works with API- and Browser-initiated flows.\nFor API clients it either returns a HTTP 200 OK when the form is valid and HTTP 400 OK when the form is invalid\nand a HTTP 302 Found redirect with a fresh verification flow if the flow was otherwise invalid (e.g. expired).\nFor Browser clients it returns a HTTP 302 Found redirect to the Verification UI URL with the Verification Flow ID appended.\n`sent_email` is the success state after `choose_method` and allows the user to request another verification email. It\nworks for both API and Browser-initiated flows and returns the same responses as the flow in `choose_method` state.\n`passed_challenge` expects a `token` to be sent in the URL query and given the nature of the flow (\"sending a verification link\")\ndoes not have any API capabilities. The server responds with a HTTP 302 Found redirect either to the Settings UI URL\n(if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with\na new Verification Flow ID which contains an error message that the verification link was invalid.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "completeSelfServiceVerificationFlowWithLinkMethod", "parameters": [ { diff --git a/CHANGELOG.md b/CHANGELOG.md index f11f5ea33fa7..e0fea0736550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ **Table of Contents** -- [ (2023-03-13)](#2023-03-13) +- [ (2023-03-17)](#2023-03-17) - [Breaking Changes](#breaking-changes) - [Bug Fixes](#bug-fixes) - [Code Refactoring](#code-refactoring) @@ -294,7 +294,7 @@ -# [](https://github.com/ory/kratos/compare/v0.11.1...v) (2023-03-13) +# [](https://github.com/ory/kratos/compare/v0.11.1...v) (2023-03-17) ## Breaking Changes @@ -326,6 +326,9 @@ flows. ([#3103](https://github.com/ory/kratos/issues/3103)) ([1193a56](https://github.com/ory/kratos/commit/1193a5681fbc25d03c1e26a4296fa0b9abd2452b)), closes [#2950](https://github.com/ory/kratos/issues/2950) +- Do not omit last page on identity list + ([#3169](https://github.com/ory/kratos/issues/3169)) + ([f95f48a](https://github.com/ory/kratos/commit/f95f48a79395b7b99c7482c0974bc5188e007cc0)) - Don't reuse ports in courier/SMTP tests ([#3156](https://github.com/ory/kratos/issues/3156)) ([e260fcf](https://github.com/ory/kratos/commit/e260fcf06181ce9339edc729ab74826aa4be78cf)) @@ -404,6 +407,9 @@ flows. - Webhook tracing and missing defers ([#3145](https://github.com/ory/kratos/issues/3145)) ([46eb063](https://github.com/ory/kratos/commit/46eb063f414a0ad9b901407cf781002ccb97ad93)) +- Wrong context in logout trace span + ([#3168](https://github.com/ory/kratos/issues/3168)) + ([b9ccccf](https://github.com/ory/kratos/commit/b9ccccf0f1b6a5ba903293133b2be15b528c8308)) ### Code Refactoring @@ -412,6 +418,9 @@ flows. ### Documentation +- Fix broken docs links and code example to get verification flow + ([#3170](https://github.com/ory/kratos/issues/3170)) + ([bdbddcc](https://github.com/ory/kratos/commit/bdbddcce2909b290e2e04dee493519b842715ab4)) - Update security email ([#3164](https://github.com/ory/kratos/issues/3164)) ([9252f5a](https://github.com/ory/kratos/commit/9252f5a3c746927a2f537efc39cb1eb0aba167a5)) @@ -425,6 +434,15 @@ flows. ([8aa75e9](https://github.com/ory/kratos/commit/8aa75e97e4bfee37e7cf551173b516c6244786ff)) - Add patreon oidc provider ([#3021](https://github.com/ory/kratos/issues/3021)) ([20ea29e](https://github.com/ory/kratos/commit/20ea29e018b33231cf6b2743de74d2233f756c2a)) +- Add token prefixes to session and logout tokens + ([#3132](https://github.com/ory/kratos/issues/3132)) + ([8210cd0](https://github.com/ory/kratos/commit/8210cd09200d370b101072649fddd1ad9a7f32a9)): + + This feature adds token prefixes to Ory session and logout tokens: + + - `ory_st_`: Ory session token prefix + - `ory_lt_`: Logout token prefix + - Add upstream parameters to oidc provider ([#3138](https://github.com/ory/kratos/issues/3138)) ([b6b1679](https://github.com/ory/kratos/commit/b6b1679c3bd053cd08ff8f26c762735e380fed67)), @@ -460,10 +478,43 @@ flows. ([4a3a076](https://github.com/ory/kratos/commit/4a3a07657d2eb2a39d777565b58882cb48e928fa)) - Don't pre-generate UUIDs for transient objects ([e17f307](https://github.com/ory/kratos/commit/e17f307732f8ced34727d5f3a70929866a0595e0)) +- Drop unused index ([#3165](https://github.com/ory/kratos/issues/3165)) + ([852dea9](https://github.com/ory/kratos/commit/852dea90881a7c9abdbfc127a2e8d1cc0aacb166)) - Identity by identifier ([#3077](https://github.com/ory/kratos/issues/3077)) ([c288d4d](https://github.com/ory/kratos/commit/c288d4d136bca1a9ed3931b4827967eb44e80ede)) - Improve tracing span naming in hooks ([bf828d3](https://github.com/ory/kratos/commit/bf828d3f5d56a963529e98958f4039f0dc569979)) +- Improved oidc flow on duplicate account registration + ([#3151](https://github.com/ory/kratos/issues/3151)) + ([4d2fda4](https://github.com/ory/kratos/commit/4d2fda453b16349589e941af06fcce312c2e5c37)): + + This PR improves the OIDC registration flow when a duplicate account error + happens. + + Currently the flow looks as follows: + + 1. User registers with password (or other credentials) + 2. User forgot they registered with password and tries to login through an + OIDC provider (e.g. Google) + 3. Kratos attempts a registration since the OIDC credentials do not exist + 4. (optional) User needs to add missing traits (e.g. full name) which could + not be retrieved from the OIDC provider + 5. User gets a duplicate account error with a "Continue" button. + 6. After submitting the "Continue" button the flow continues again to the OIDC + provider, back to Kratos and redirects to UI with duplicate error (Steps 3 + to 5) + + Instead of causing a confusing redirect loop we should show the user the error + with a fresh login flow (since the account exists). This also gives the user + the option to do a recovery flow. + + 1. User registers with password (or other credentials) + 2. User forgot they registered with password and tries to login through an + OIDC provider (e.g. Google) + 3. Kratos attempts a registration since the OIDC credentials do not exist + 4. (optional) User needs to add missing traits + 5. User is returned to a Login flow with the duplication error + - Let DB generate ID for session devices ([62402c7](https://github.com/ory/kratos/commit/62402c7bed3c57ef5b957572e4b84f56d9c530ae)) - Make notification to unknown recipients configurable diff --git a/cmd/clidoc/main.go b/cmd/clidoc/main.go index 874b4efa1d52..fc9956cbcd5e 100644 --- a/cmd/clidoc/main.go +++ b/cmd/clidoc/main.go @@ -95,6 +95,7 @@ func init() { "NewErrorValidationPasswordPolicyViolation": text.NewErrorValidationPasswordPolicyViolation("{reason}"), "NewErrorValidationInvalidCredentials": text.NewErrorValidationInvalidCredentials(), "NewErrorValidationDuplicateCredentials": text.NewErrorValidationDuplicateCredentials(), + "NewErrorValidationDuplicateCredentialsOnOIDCLink": text.NewErrorValidationDuplicateCredentialsOnOIDCLink(), "NewErrorValidationTOTPVerifierWrong": text.NewErrorValidationTOTPVerifierWrong(), "NewErrorValidationLookupAlreadyUsed": text.NewErrorValidationLookupAlreadyUsed(), "NewErrorValidationLookupInvalid": text.NewErrorValidationLookupInvalid(), diff --git a/go.mod b/go.mod index bd6ddf573def..3fc9ebc9c04d 100644 --- a/go.mod +++ b/go.mod @@ -78,6 +78,7 @@ require ( github.com/ory/mail/v3 v3.0.0 github.com/ory/nosurf v1.2.7 github.com/ory/x v0.0.537 + github.com/peterhellberg/link v1.2.0 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 diff --git a/go.sum b/go.sum index e842ada38784..0a0acf7285b1 100644 --- a/go.sum +++ b/go.sum @@ -1140,6 +1140,8 @@ github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c= +github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= diff --git a/identity/handler.go b/identity/handler.go index 20222b528df0..6eae04ceed7d 100644 --- a/identity/handler.go +++ b/identity/handler.go @@ -180,7 +180,7 @@ func (h *Handler) list(w http.ResponseWriter, r *http.Request, _ httprouter.Para isam[i] = WithCredentialsMetadataAndAdminMetadataInJSON(identity) } - migrationpagination.PaginationHeader(w, urlx.AppendPaths(h.r.Config().SelfAdminURL(r.Context()), RouteCollection), total, page, itemsPerPage) + migrationpagination.PaginationHeader(w, urlx.AppendPaths(h.r.Config().SelfAdminURL(r.Context()), RouteCollection), total+int64(itemsPerPage), page, itemsPerPage) h.r.Writer().Write(w, r, isam) } diff --git a/identity/handler_test.go b/identity/handler_test.go index 99f63368ddbf..e8d1107f181e 100644 --- a/identity/handler_test.go +++ b/identity/handler_test.go @@ -22,6 +22,7 @@ import ( "github.com/ory/x/snapshotx" "github.com/bxcodec/faker/v3" + "github.com/peterhellberg/link" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" @@ -1004,13 +1005,49 @@ func TestHandler(t *testing.T) { }) t.Run("case=should list all identities", func(t *testing.T) { + expected, err := reg.IdentityPool().ListIdentities(ctx, identity.ListIdentityParameters{PerPage: 1000}) + require.NoError(t, err) + expectedIDs := make([]string, len(expected)) + for k, v := range expected { + expectedIDs[k] = v.ID.String() + } + for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := get(t, ts, "/identities", http.StatusOK) - assert.False(t, res.Get("0.credentials").Exists(), "credentials config should be omitted: %s", res.Raw) - assert.True(t, res.Get("0.metadata_public").Exists(), "metadata_public config should be included: %s", res.Raw) - assert.True(t, res.Get("0.metadata_admin").Exists(), "metadata_admin config should be included: %s", res.Raw) - assert.EqualValues(t, "baz", res.Get(`#(traits.bar=="baz").traits.bar`).String(), "%s", res.Raw) + assert.Falsef(t, res.Get("0.credentials").Exists(), "credentials config should be omitted: %s", res.Raw) + assert.Truef(t, res.Get("0.metadata_public").Exists(), "metadata_public config should be included: %s", res.Raw) + assert.Truef(t, res.Get("0.metadata_admin").Exists(), "metadata_admin config should be included: %s", res.Raw) + assert.Truef(t, res.Get(`#(traits.bar=="baz").traits.bar`).Exists(), "some identity should have the traits {bar: baz}: %s", res.Raw) + + assert.EqualValuesf(t, len(expectedIDs), int(res.Get("#").Num), "%s", res.Raw) + res.Get("#.id").ForEach(func(key, value gjson.Result) bool { + return assert.Contains(t, expectedIDs, value.String(), "%s", res.Raw) + }) + + actualIDs := make([]string, 0, len(expectedIDs)) + for reqURL := ts.URL + "/identities?per_page=5&page=1"; ; { + res, err := ts.Client().Get(reqURL) + require.NoError(t, err) + body, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.NoError(t, res.Body.Close()) + + require.EqualValues(t, http.StatusOK, res.StatusCode, "%s", body) + var ids []identity.Identity + require.NoError(t, json.Unmarshal(body, &ids)) + for _, id := range ids { + actualIDs = append(actualIDs, id.ID.String()) + } + + links := link.ParseHeader(res.Header) + next, ok := links["next"] + if !ok { + break + } + reqURL = next.URI + } + assert.ElementsMatch(t, expectedIDs, actualIDs) }) } }) diff --git a/internal/client-go/api_frontend.go b/internal/client-go/api_frontend.go index 6a6aa3d69ec9..d2db9f9b432e 100644 --- a/internal/client-go/api_frontend.go +++ b/internal/client-go/api_frontend.go @@ -195,7 +195,7 @@ type FrontendApi interface { This endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...). - More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). + More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). * @return FrontendApiApiCreateBrowserVerificationFlowRequest */ @@ -559,8 +559,9 @@ type FrontendApi interface { res.render('verification', flow) }) + ``` - More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). + More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). * @return FrontendApiApiGetVerificationFlowRequest */ @@ -905,7 +906,7 @@ type FrontendApi interface { (if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with a new Verification Flow ID which contains an error message that the verification link was invalid. - More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). + More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). * @return FrontendApiApiUpdateVerificationFlowRequest */ @@ -1742,7 +1743,7 @@ If this endpoint is called via an AJAX request, the response contains the recove This endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...). -More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). +More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return FrontendApiApiCreateBrowserVerificationFlowRequest */ @@ -3729,8 +3730,9 @@ const flow = await client.getVerificationFlow(req.header('cookie'), req.query['f res.render('verification', flow) }) +``` -More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). +More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return FrontendApiApiGetVerificationFlowRequest */ @@ -5447,7 +5449,7 @@ does not have any API capabilities. The server responds with a HTTP 303 See Othe (if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with a new Verification Flow ID which contains an error message that the verification link was invalid. -More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). +More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return FrontendApiApiUpdateVerificationFlowRequest */ diff --git a/internal/client-go/model_self_service_verification_flow.go b/internal/client-go/model_self_service_verification_flow.go index 20532bc904e1..7ddccb2176cc 100644 --- a/internal/client-go/model_self_service_verification_flow.go +++ b/internal/client-go/model_self_service_verification_flow.go @@ -19,7 +19,7 @@ import ( "time" ) -// SelfServiceVerificationFlow Used to verify an out-of-band communication channel such as an email address or a phone number. For more information head over to: https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation +// SelfServiceVerificationFlow Used to verify an out-of-band communication channel such as an email address or a phone number. For more information head over to: https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation type SelfServiceVerificationFlow struct { // Active, if set, contains the registration method that is being used. It is initially not set. Active *string `json:"active,omitempty"` diff --git a/internal/client-go/model_verification_flow.go b/internal/client-go/model_verification_flow.go index 8e02baa6b41f..5190da660254 100644 --- a/internal/client-go/model_verification_flow.go +++ b/internal/client-go/model_verification_flow.go @@ -16,7 +16,7 @@ import ( "time" ) -// VerificationFlow Used to verify an out-of-band communication channel such as an email address or a phone number. For more information head over to: https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation +// VerificationFlow Used to verify an out-of-band communication channel such as an email address or a phone number. For more information head over to: https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation type VerificationFlow struct { // Active, if set, contains the registration method that is being used. It is initially not set. Active *string `json:"active,omitempty"` diff --git a/internal/httpclient/api_frontend.go b/internal/httpclient/api_frontend.go index 6a6aa3d69ec9..d2db9f9b432e 100644 --- a/internal/httpclient/api_frontend.go +++ b/internal/httpclient/api_frontend.go @@ -195,7 +195,7 @@ type FrontendApi interface { This endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...). - More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). + More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). * @return FrontendApiApiCreateBrowserVerificationFlowRequest */ @@ -559,8 +559,9 @@ type FrontendApi interface { res.render('verification', flow) }) + ``` - More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). + More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). * @return FrontendApiApiGetVerificationFlowRequest */ @@ -905,7 +906,7 @@ type FrontendApi interface { (if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with a new Verification Flow ID which contains an error message that the verification link was invalid. - More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). + More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). * @return FrontendApiApiUpdateVerificationFlowRequest */ @@ -1742,7 +1743,7 @@ If this endpoint is called via an AJAX request, the response contains the recove This endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...). -More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). +More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return FrontendApiApiCreateBrowserVerificationFlowRequest */ @@ -3729,8 +3730,9 @@ const flow = await client.getVerificationFlow(req.header('cookie'), req.query['f res.render('verification', flow) }) +``` -More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). +More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return FrontendApiApiGetVerificationFlowRequest */ @@ -5447,7 +5449,7 @@ does not have any API capabilities. The server responds with a HTTP 303 See Othe (if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with a new Verification Flow ID which contains an error message that the verification link was invalid. -More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). +More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return FrontendApiApiUpdateVerificationFlowRequest */ diff --git a/internal/httpclient/model_verification_flow.go b/internal/httpclient/model_verification_flow.go index 8e02baa6b41f..5190da660254 100644 --- a/internal/httpclient/model_verification_flow.go +++ b/internal/httpclient/model_verification_flow.go @@ -16,7 +16,7 @@ import ( "time" ) -// VerificationFlow Used to verify an out-of-band communication channel such as an email address or a phone number. For more information head over to: https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation +// VerificationFlow Used to verify an out-of-band communication channel such as an email address or a phone number. For more information head over to: https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation type VerificationFlow struct { // Active, if set, contains the registration method that is being used. It is initially not set. Active *string `json:"active,omitempty"` diff --git a/persistence/sql/migratest/fixtures/session/068f6bb6-d15f-436d-94f7-b3fd0489c9ef.json b/persistence/sql/migratest/fixtures/session/068f6bb6-d15f-436d-94f7-b3fd0489c9ef.json new file mode 100644 index 000000000000..8c641a193e30 --- /dev/null +++ b/persistence/sql/migratest/fixtures/session/068f6bb6-d15f-436d-94f7-b3fd0489c9ef.json @@ -0,0 +1,44 @@ +{ + "id": "068f6bb6-d15f-436d-94f7-b3fd0489c9ef", + "active": false, + "expires_at": "2013-10-07T08:23:19Z", + "authenticated_at": "2013-10-07T08:23:19Z", + "authenticator_assurance_level": "aal2", + "authentication_methods": [ + { + "method": "password", + "aal": "", + "completed_at": "0001-01-01T00:00:00Z" + }, + { + "method": "totp", + "aal": "", + "completed_at": "0001-01-01T00:00:00Z" + } + ], + "issued_at": "2013-10-07T08:23:19Z", + "identity": { + "id": "5ff66179-c240-4703-b0d8-494592cefff5", + "schema_id": "default", + "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", + "state": "active", + "traits": { + "email": "bazbar@ory.sh" + }, + "verifiable_addresses": [ + { + "id": "45e867e9-2745-4f16-8dd4-84334a252b61", + "value": "foo@ory.sh", + "verified": false, + "via": "email", + "status": "pending", + "created_at": "2013-10-07T08:23:19Z", + "updated_at": "2013-10-07T08:23:19Z" + } + ], + "metadata_public": null, + "created_at": "2013-10-07T08:23:19Z", + "updated_at": "2013-10-07T08:23:19Z" + }, + "devices": [] +} diff --git a/persistence/sql/migratest/migration_test.go b/persistence/sql/migratest/migration_test.go index 1e54469d4cbf..ea7c940b7044 100644 --- a/persistence/sql/migratest/migration_test.go +++ b/persistence/sql/migratest/migration_test.go @@ -88,7 +88,7 @@ func TestMigrations(t *testing.T) { connections["mysql"] = dockertest.ConnectToTestMySQLPop(t) }, func() { - connections["cockroach"] = dockertest.ConnectToTestCockroachDBPop(t) + connections["cockroach"] = dockertest.ConnectPop(t, dockertest.RunTestCockroachDBWithVersion(t, "v22.2.6")) }, }) } diff --git a/persistence/sql/migratest/testdata/20230313141439_testdata.sql b/persistence/sql/migratest/testdata/20230313141439_testdata.sql new file mode 100644 index 000000000000..780229208de1 --- /dev/null +++ b/persistence/sql/migratest/testdata/20230313141439_testdata.sql @@ -0,0 +1,6 @@ +INSERT INTO sessions (id, nid, issued_at, expires_at, authenticated_at, created_at, updated_at, token, identity_id, + active, logout_token, aal, authentication_methods) +VALUES ('068f6bb6-d15f-436d-94f7-b3fd0489c9ef', '884f556e-eb3a-4b9f-bee3-11345642c6c0', '2013-10-07 08:23:19', + '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', + 'ory_lo_5e5aad0f7a4143452df3d23733a68e3', '5ff66179-c240-4703-b0d8-494592cefff5', true, 'ory_st_5e5aad0f7a4143452df3d23733a68e2', 'aal2', + '[{"method":"password"},{"method":"totp"}]'); diff --git a/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.cockroach.down.sql b/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.cockroach.down.sql new file mode 100644 index 000000000000..6f611981bdda --- /dev/null +++ b/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.cockroach.down.sql @@ -0,0 +1 @@ +CREATE UNIQUE INDEX IF NOT EXISTS unique_session_device ON session_devices (nid, session_id, ip_address, user_agent); diff --git a/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.cockroach.up.sql b/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.cockroach.up.sql new file mode 100644 index 000000000000..8c94c46746e6 --- /dev/null +++ b/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.cockroach.up.sql @@ -0,0 +1 @@ +DROP INDEX IF EXISTS session_devices@unique_session_device CASCADE; diff --git a/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.down.sql b/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.down.sql new file mode 100644 index 000000000000..6f611981bdda --- /dev/null +++ b/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.down.sql @@ -0,0 +1 @@ +CREATE UNIQUE INDEX IF NOT EXISTS unique_session_device ON session_devices (nid, session_id, ip_address, user_agent); diff --git a/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.mysql.down.sql b/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.mysql.down.sql new file mode 100644 index 000000000000..9ad96140caa5 --- /dev/null +++ b/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.mysql.down.sql @@ -0,0 +1 @@ +CREATE UNIQUE INDEX unique_session_device ON session_devices (nid, session_id, ip_address, user_agent); diff --git a/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.mysql.up.sql b/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.mysql.up.sql new file mode 100644 index 000000000000..569acddda594 --- /dev/null +++ b/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.mysql.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE session_devices DROP FOREIGN KEY session_devices_ibfk_2; +ALTER TABLE session_devices DROP INDEX unique_session_device; +ALTER TABLE session_devices ADD CONSTRAINT session_devices_ibfk_2 FOREIGN KEY (nid) REFERENCES networks(id) ON DELETE CASCADE; diff --git a/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.up.sql b/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.up.sql new file mode 100644 index 000000000000..2a326bb55197 --- /dev/null +++ b/persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.up.sql @@ -0,0 +1 @@ +DROP INDEX IF EXISTS session_devices.unique_session_device; diff --git a/persistence/sql/migrations/sql/20230313141439000000_session_token_length.cockroach.down.sql b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.cockroach.down.sql new file mode 100644 index 000000000000..8d8a4d027c58 --- /dev/null +++ b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.cockroach.down.sql @@ -0,0 +1,5 @@ +-- Downsizing is not yet supported in CockroachDB. Since this migration has no real-world impact on the application, we can safely +-- not execute it. +-- +-- ALTER TABLE sessions ALTER COLUMN token TYPE varchar(32); +-- ALTER TABLE sessions ALTER COLUMN logout_token TYPE varchar(32); diff --git a/persistence/sql/migrations/sql/20230313141439000000_session_token_length.down.sql b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.down.sql new file mode 100644 index 000000000000..809c038e7047 --- /dev/null +++ b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE sessions ALTER COLUMN token TYPE varchar(32); +ALTER TABLE sessions ALTER COLUMN logout_token TYPE varchar(32); diff --git a/persistence/sql/migrations/sql/20230313141439000000_session_token_length.mysql.down.sql b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.mysql.down.sql new file mode 100644 index 000000000000..6cd7e101faae --- /dev/null +++ b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.mysql.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE sessions MODIFY COLUMN token varchar(32) NULL; +ALTER TABLE sessions MODIFY COLUMN logout_token varchar(32) NULL; diff --git a/persistence/sql/migrations/sql/20230313141439000000_session_token_length.mysql.up.sql b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.mysql.up.sql new file mode 100644 index 000000000000..2adcb7ce80a5 --- /dev/null +++ b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.mysql.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE sessions MODIFY COLUMN token varchar(39) NULL; +ALTER TABLE sessions MODIFY COLUMN logout_token varchar(39) NULL; diff --git a/persistence/sql/migrations/sql/20230313141439000000_session_token_length.sqlite3.down.sql b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.sqlite3.down.sql new file mode 100644 index 000000000000..967a41890f8a --- /dev/null +++ b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.sqlite3.down.sql @@ -0,0 +1,29 @@ +DROP INDEX sessions_token_uq_idx; +DROP INDEX sessions_logout_token_uq_idx; +DROP INDEX sessions_token_nid_idx; + +ALTER TABLE sessions RENAME COLUMN token TO old_token; +ALTER TABLE sessions RENAME COLUMN logout_token TO old_logout_token; + +ALTER TABLE sessions + ADD COLUMN token varchar(32) NULL; +ALTER TABLE sessions + ADD COLUMN logout_token varchar(32) NULL; + +UPDATE sessions +SET token = old_token +WHERE true; + +UPDATE sessions +SET logout_token = old_logout_token +WHERE true; + +ALTER TABLE sessions + DROP COLUMN old_token; + +ALTER TABLE sessions + DROP COLUMN old_logout_token; + +CREATE UNIQUE INDEX sessions_token_uq_idx ON sessions (logout_token); +CREATE UNIQUE INDEX sessions_logout_token_uq_idx ON sessions (token); +CREATE INDEX sessions_token_nid_idx ON sessions (nid, token); diff --git a/persistence/sql/migrations/sql/20230313141439000000_session_token_length.sqlite3.up.sql b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.sqlite3.up.sql new file mode 100644 index 000000000000..92b88cc26e70 --- /dev/null +++ b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.sqlite3.up.sql @@ -0,0 +1,27 @@ +DROP INDEX sessions_token_uq_idx; +DROP INDEX sessions_logout_token_uq_idx; +DROP INDEX sessions_token_nid_idx; + +ALTER TABLE sessions RENAME COLUMN token TO old_token; +ALTER TABLE sessions RENAME COLUMN logout_token TO old_logout_token; +ALTER TABLE sessions + ADD COLUMN token varchar(39) NULL; +ALTER TABLE sessions + ADD COLUMN logout_token varchar(39) NULL; + +UPDATE sessions +SET token = old_token +WHERE true; + +UPDATE sessions +SET logout_token = old_logout_token +WHERE true; + +ALTER TABLE sessions + DROP COLUMN old_token; +ALTER TABLE sessions + DROP COLUMN old_logout_token; + +CREATE UNIQUE INDEX sessions_token_uq_idx ON sessions (logout_token); +CREATE UNIQUE INDEX sessions_logout_token_uq_idx ON sessions (token); +CREATE INDEX sessions_token_nid_idx ON sessions (nid, token); diff --git a/persistence/sql/migrations/sql/20230313141439000000_session_token_length.up.sql b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.up.sql new file mode 100644 index 000000000000..c78bf9b3bc1d --- /dev/null +++ b/persistence/sql/migrations/sql/20230313141439000000_session_token_length.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE sessions ALTER COLUMN token TYPE varchar(39); +ALTER TABLE sessions ALTER COLUMN logout_token TYPE varchar(39); diff --git a/persistence/sql/migrations/sql/20230313141439000001_session_token_length.down.sql b/persistence/sql/migrations/sql/20230313141439000001_session_token_length.down.sql new file mode 100644 index 000000000000..db28543f6986 --- /dev/null +++ b/persistence/sql/migrations/sql/20230313141439000001_session_token_length.down.sql @@ -0,0 +1,2 @@ +UPDATE sessions SET token = LEFT(token, 32) WHERE TRUE; +UPDATE sessions SET logout_token = LEFT(logout_token, 32) WHERE TRUE; diff --git a/persistence/sql/migrations/sql/20230313141439000001_session_token_length.sqlite3.down.sql b/persistence/sql/migrations/sql/20230313141439000001_session_token_length.sqlite3.down.sql new file mode 100644 index 000000000000..d0707dd681b8 --- /dev/null +++ b/persistence/sql/migrations/sql/20230313141439000001_session_token_length.sqlite3.down.sql @@ -0,0 +1,2 @@ +UPDATE sessions SET token = substr(token, 0, 32) WHERE TRUE; +UPDATE sessions SET logout_token = substr(logout_token, 0, 32) WHERE TRUE; diff --git a/persistence/sql/migrations/sql/20230313141439000001_session_token_length.up.sql b/persistence/sql/migrations/sql/20230313141439000001_session_token_length.up.sql new file mode 100644 index 000000000000..4ba280517af5 --- /dev/null +++ b/persistence/sql/migrations/sql/20230313141439000001_session_token_length.up.sql @@ -0,0 +1 @@ +-- diff --git a/quickstart-crdb.yml b/quickstart-crdb.yml index ba90f3d4bb18..c94c946b0587 100644 --- a/quickstart-crdb.yml +++ b/quickstart-crdb.yml @@ -10,7 +10,7 @@ services: - DSN=cockroach://root@cockroachd:26257/defaultdb?sslmode=disable&max_conns=20&max_idle_conns=4 cockroachd: - image: cockroachdb/cockroach:v21.2.4 + image: cockroachdb/cockroach:v22.2.6 ports: - "26257:26257" command: start-single-node --insecure diff --git a/script/testenv.sh b/script/testenv.sh index 7a21c61e4e5a..4a01e5b3e714 100755 --- a/script/testenv.sh +++ b/script/testenv.sh @@ -3,7 +3,7 @@ docker rm -f kratos_test_database_mysql kratos_test_database_postgres kratos_test_database_cockroach kratos_test_hydra || true docker run --platform linux/amd64 --name kratos_test_database_mysql -p 3444:3306 -e MYSQL_ROOT_PASSWORD=secret -d mysql:8.0.23 docker run --platform linux/amd64 --name kratos_test_database_postgres -p 3445:5432 -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=postgres -d postgres:11.8 postgres -c log_statement=all -docker run --platform linux/amd64 --name kratos_test_database_cockroach -p 3446:26257 -p 3447:8080 -d cockroachdb/cockroach:v21.2.6 start-single-node --insecure +docker run --platform linux/amd64 --name kratos_test_database_cockroach -p 3446:26257 -p 3447:8080 -d cockroachdb/cockroach:v22.2.6 start-single-node --insecure docker run --platform linux/amd64 --name kratos_test_hydra -p 4444:4444 -p 4445:4445 -d -e DSN=memory -e URLS_SELF_ISSUER=http://localhost:4444/ -e URLS_LOGIN=http://localhost:4446/login -e URLS_CONSENT=http://localhost:4446/consent oryd/hydra:v2.0.2 serve all --dev source script/test-envs.sh diff --git a/selfservice/flow/login/handler.go b/selfservice/flow/login/handler.go index 88b1c576c856..90fd2bed2231 100644 --- a/selfservice/flow/login/handler.go +++ b/selfservice/flow/login/handler.go @@ -102,6 +102,14 @@ func WithFlowReturnTo(returnTo string) FlowOption { } } +func WithFormErrorMessage(messages []text.Message) FlowOption { + return func(f *Flow) { + for i := range messages { + f.UI.Messages.Add(&messages[i]) + } + } +} + func (h *Handler) NewLoginFlow(w http.ResponseWriter, r *http.Request, ft flow.Type, opts ...FlowOption) (*Flow, *session.Session, error) { conf := h.d.Config() f, err := NewFlow(conf, conf.SelfServiceFlowLoginRequestLifespan(r.Context()), h.d.GenerateCSRFToken(r), r, ft) diff --git a/selfservice/flow/registration/error.go b/selfservice/flow/registration/error.go index d7e5f850e4d7..2daeafc5eeb6 100644 --- a/selfservice/flow/registration/error.go +++ b/selfservice/flow/registration/error.go @@ -8,6 +8,7 @@ import ( "path" "strconv" + "github.com/ory/kratos/schema" "github.com/ory/kratos/ui/node" "github.com/ory/kratos/selfservice/flow" @@ -22,9 +23,11 @@ import ( ) var ( - ErrHookAbortFlow = errors.New("aborted registration hook execution") - ErrAlreadyLoggedIn = herodot.ErrBadRequest.WithID(text.ErrIDAlreadyLoggedIn).WithError("you are already logged in").WithReason("A valid session was detected and thus registration is not possible.") - ErrRegistrationDisabled = herodot.ErrBadRequest.WithID(text.ErrIDSelfServiceFlowDisabled).WithError("registration flow disabled").WithReason("Registration is not allowed because it was disabled.") + ErrHookAbortFlow = errors.New("aborted registration hook execution") + ErrAlreadyLoggedIn = herodot.ErrBadRequest.WithID(text.ErrIDAlreadyLoggedIn).WithError("you are already logged in").WithReason("A valid session was detected and thus registration is not possible.") + ErrRegistrationDisabled = herodot.ErrBadRequest.WithID(text.ErrIDSelfServiceFlowDisabled).WithError("registration flow disabled").WithReason("Registration is not allowed because it was disabled.") + ErrDuplicateCredentials = herodot.ErrBadRequest.WithError(text.NewErrorValidationDuplicateCredentials().Text) + ErrDuplicateCredentialsOnOIDCLink = herodot.ErrBadRequest.WithError(text.NewErrorValidationDuplicateCredentialsOnOIDCLink().Text) ) type ( @@ -74,6 +77,11 @@ func (s *ErrorHandler) WriteFlowError( group node.UiNodeGroup, err error, ) { + + if errors.Is(err, ErrDuplicateCredentials) { + err = schema.NewDuplicateCredentialsError() + } + s.d.Audit(). WithError(err). WithRequest(r). diff --git a/selfservice/flow/registration/hook.go b/selfservice/flow/registration/hook.go index 25339d610afc..1eb4ce2f5976 100644 --- a/selfservice/flow/registration/hook.go +++ b/selfservice/flow/registration/hook.go @@ -20,7 +20,6 @@ import ( "github.com/ory/kratos/driver/config" "github.com/ory/kratos/hydra" "github.com/ory/kratos/identity" - "github.com/ory/kratos/schema" "github.com/ory/kratos/selfservice/flow" "github.com/ory/kratos/session" "github.com/ory/kratos/x" @@ -138,7 +137,10 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque // would imply that the identity has to exist already. } else if err := e.d.IdentityManager().Create(r.Context(), i); err != nil { if errors.Is(err, sqlcon.ErrUniqueViolation) { - return schema.NewDuplicateCredentialsError() + // In this case the user is already registered through another method. + // We handle this case by returning a spcial error that is handled by + // the caller. + return ErrDuplicateCredentials } return err } diff --git a/selfservice/flow/verification/flow.go b/selfservice/flow/verification/flow.go index 9779a0f0ab30..f529b1ab61e0 100644 --- a/selfservice/flow/verification/flow.go +++ b/selfservice/flow/verification/flow.go @@ -28,7 +28,7 @@ import ( // Used to verify an out-of-band communication // channel such as an email address or a phone number. // -// For more information head over to: https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation +// For more information head over to: https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation // // swagger:model verificationFlow type Flow struct { diff --git a/selfservice/flow/verification/handler.go b/selfservice/flow/verification/handler.go index b6ef8e3597ce..dd21a7db1135 100644 --- a/selfservice/flow/verification/handler.go +++ b/selfservice/flow/verification/handler.go @@ -178,7 +178,7 @@ type createBrowserVerificationFlow struct { // // This endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...). // -// More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). +// More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). // // Schemes: http, https // @@ -240,15 +240,16 @@ type getVerificationFlow struct { // If you use the browser-flow for server-side apps, the services need to run on a common top-level-domain // and you need to forward the incoming HTTP Cookie header to this endpoint: // -// ```js -// // pseudo-code example -// router.get('/recovery', async function (req, res) { -// const flow = await client.getVerificationFlow(req.header('cookie'), req.query['flow']) +// ```js +// // pseudo-code example +// router.get('/recovery', async function (req, res) { +// const flow = await client.getVerificationFlow(req.header('cookie'), req.query['flow']) // -// res.render('verification', flow) -// }) +// res.render('verification', flow) +// }) +// ``` // -// More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). +// More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). // // Produces: // - application/json @@ -367,7 +368,7 @@ type updateVerificationFlowBody struct{} // (if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with // a new Verification Flow ID which contains an error message that the verification link was invalid. // -// More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation). +// More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). // // Consumes: // - application/json diff --git a/selfservice/strategy/oidc/strategy.go b/selfservice/strategy/oidc/strategy.go index 9b34a1648ce3..87f41417baf2 100644 --- a/selfservice/strategy/oidc/strategy.go +++ b/selfservice/strategy/oidc/strategy.go @@ -445,6 +445,18 @@ func (s *Strategy) handleError(w http.ResponseWriter, r *http.Request, f flow.Fl // Reset all nodes to not confuse users. // This is kinda hacky and will probably need to be updated at some point. + if errors.Is(err, registration.ErrDuplicateCredentials) { + rf.UI.Messages.Add(text.NewErrorValidationDuplicateCredentialsOnOIDCLink()) + lf, err := s.registrationToLogin(w, r, rf, provider) + if err != nil { + return err + } + // return a new login flow with the error message embedded in the login flow. + x.AcceptToRedirectOrJSON(w, r, s.d.Writer(), lf, lf.AppendTo(s.d.Config().SelfServiceFlowLoginUI(r.Context())).String()) + // ensure the function does not continue to execute + return registration.ErrHookAbortFlow + } + rf.UI.Nodes = node.Nodes{} // Adds the "Continue" button diff --git a/selfservice/strategy/oidc/strategy_registration.go b/selfservice/strategy/oidc/strategy_registration.go index 6dda2a2ce4ee..feea21a4315f 100644 --- a/selfservice/strategy/oidc/strategy_registration.go +++ b/selfservice/strategy/oidc/strategy_registration.go @@ -281,7 +281,33 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat } } -func (s *Strategy) processRegistration(w http.ResponseWriter, r *http.Request, a *registration.Flow, token *oauth2.Token, claims *Claims, provider Provider, container *authCodeContainer) (*login.Flow, error) { +func (s *Strategy) registrationToLogin(w http.ResponseWriter, r *http.Request, rf *registration.Flow, providerID string) (*login.Flow, error) { + // If return_to was set before, we need to preserve it. + var opts []login.FlowOption + if len(rf.ReturnTo) > 0 { + opts = append(opts, login.WithFlowReturnTo(rf.ReturnTo)) + } + + if len(rf.UI.Messages) > 0 { + opts = append(opts, login.WithFormErrorMessage(rf.UI.Messages)) + } + + // This endpoint only handles browser flow at the moment. + lf, _, err := s.d.LoginHandler().NewLoginFlow(w, r, flow.TypeBrowser, opts...) + + if err != nil { + return nil, err + } + + lf.RequestURL, err = x.TakeOverReturnToParameter(rf.RequestURL, lf.RequestURL) + if err != nil { + return nil, err + } + + return lf, nil +} + +func (s *Strategy) processRegistration(w http.ResponseWriter, r *http.Request, rf *registration.Flow, token *oauth2.Token, claims *Claims, provider Provider, container *authCodeContainer) (*login.Flow, error) { if _, _, err := s.d.PrivilegedIdentityPool().FindByCredentialsIdentifier(r.Context(), identity.CredentialsTypeOIDC, identity.OIDCUniqueID(provider.Config().ID, claims.Subject)); err == nil { // If the identity already exists, we should perform the login flow instead. @@ -296,70 +322,59 @@ func (s *Strategy) processRegistration(w http.ResponseWriter, r *http.Request, a WithField("subject", claims.Subject). Debug("Received successful OpenID Connect callback but user is already registered. Re-initializing login flow now.") - // If return_to was set before, we need to preserve it. - var opts []login.FlowOption - if len(a.ReturnTo) > 0 { - opts = append(opts, login.WithFlowReturnTo(a.ReturnTo)) - } - - // This endpoint only handles browser flow at the moment. - ar, _, err := s.d.LoginHandler().NewLoginFlow(w, r, flow.TypeBrowser, opts...) + lf, err := s.registrationToLogin(w, r, rf, provider.Config().ID) if err != nil { - return nil, s.handleError(w, r, a, provider.Config().ID, nil, err) + return nil, s.handleError(w, r, rf, provider.Config().ID, nil, err) } - ar.RequestURL, err = x.TakeOverReturnToParameter(a.RequestURL, ar.RequestURL) - if err != nil { - return nil, s.handleError(w, r, a, provider.Config().ID, nil, err) + if _, err := s.processLogin(w, r, lf, token, claims, provider, container); err != nil { + return lf, s.handleError(w, r, rf, provider.Config().ID, nil, err) } - if _, err := s.processLogin(w, r, ar, token, claims, provider, container); err != nil { - return ar, err - } return nil, nil } fetch := fetcher.NewFetcher(fetcher.WithClient(s.d.HTTPClient(r.Context()))) jn, err := fetch.Fetch(provider.Config().Mapper) if err != nil { - return nil, s.handleError(w, r, a, provider.Config().ID, nil, err) + return nil, s.handleError(w, r, rf, provider.Config().ID, nil, err) } - i, err := s.createIdentity(w, r, a, claims, provider, container, jn) + i, err := s.createIdentity(w, r, rf, claims, provider, container, jn) if err != nil { - return nil, s.handleError(w, r, a, provider.Config().ID, nil, err) + return nil, s.handleError(w, r, rf, provider.Config().ID, nil, err) } // Validate the identity itself if err := s.d.IdentityValidator().Validate(r.Context(), i); err != nil { - return nil, s.handleError(w, r, a, provider.Config().ID, i.Traits, err) + return nil, s.handleError(w, r, rf, provider.Config().ID, i.Traits, err) } var it string if idToken, ok := token.Extra("id_token").(string); ok { if it, err = s.d.Cipher(r.Context()).Encrypt(r.Context(), []byte(idToken)); err != nil { - return nil, s.handleError(w, r, a, provider.Config().ID, i.Traits, err) + return nil, s.handleError(w, r, rf, provider.Config().ID, i.Traits, err) } } cat, err := s.d.Cipher(r.Context()).Encrypt(r.Context(), []byte(token.AccessToken)) if err != nil { - return nil, s.handleError(w, r, a, provider.Config().ID, i.Traits, err) + return nil, s.handleError(w, r, rf, provider.Config().ID, i.Traits, err) } crt, err := s.d.Cipher(r.Context()).Encrypt(r.Context(), []byte(token.RefreshToken)) if err != nil { - return nil, s.handleError(w, r, a, provider.Config().ID, i.Traits, err) + return nil, s.handleError(w, r, rf, provider.Config().ID, i.Traits, err) } creds, err := identity.NewCredentialsOIDC(it, cat, crt, provider.Config().ID, claims.Subject) if err != nil { - return nil, s.handleError(w, r, a, provider.Config().ID, i.Traits, err) + return nil, s.handleError(w, r, rf, provider.Config().ID, i.Traits, err) } i.SetCredentials(s.ID(), *creds) - if err := s.d.RegistrationExecutor().PostRegistrationHook(w, r, identity.CredentialsTypeOIDC, a, i); err != nil { - return nil, s.handleError(w, r, a, provider.Config().ID, i.Traits, err) + if err := s.d.RegistrationExecutor().PostRegistrationHook(w, r, identity.CredentialsTypeOIDC, rf, i); err != nil { + return nil, s.handleError(w, r, rf, provider.Config().ID, i.Traits, err) } return nil, nil diff --git a/selfservice/strategy/oidc/strategy_test.go b/selfservice/strategy/oidc/strategy_test.go index 4f60b3d6faf9..cc1278598927 100644 --- a/selfservice/strategy/oidc/strategy_test.go +++ b/selfservice/strategy/oidc/strategy_test.go @@ -643,7 +643,7 @@ func TestStrategy(t *testing.T) { }) }) - t.Run("case=should fail to register if email is already being used by password credentials", func(t *testing.T) { + t.Run("case=should fail to register and return fresh login flow if email is already being used by password credentials", func(t *testing.T) { subject = "email-exist-with-password-strategy@ory.sh" scope = []string{"openid"} @@ -681,7 +681,8 @@ func TestStrategy(t *testing.T) { r := newRegistrationFlowBrowser(t, returnTS.URL, time.Minute) action := afv(t, r.ID, "valid") res, body := makeRequest(t, "valid", action, url.Values{}) - aue(t, res, body, "An account with the same identifier (email, phone, username, ...) exists already.") + aue(t, res, body, "An account with the same identifier (email, phone, username, ...) exists already. Please sign in to your existing account and link your social profile in the settings page.") + require.Contains(t, gjson.GetBytes(body, "ui.action").String(), "/self-service/login") }) t.Run("case=webview", func(t *testing.T) { diff --git a/session/manager_http.go b/session/manager_http.go index 7bc438111002..a756e5bfe7e7 100644 --- a/session/manager_http.go +++ b/session/manager_http.go @@ -203,7 +203,7 @@ func (s *ManagerHTTP) FetchFromRequest(ctx context.Context, r *http.Request) (_ } }() - token := s.extractToken(r) + token := s.extractToken(r.WithContext(ctx)) if token == "" { return nil, errors.WithStack(NewErrNoCredentialsForSession()) } diff --git a/session/session.go b/session/session.go index b072c8240b38..9638f410ae1c 100644 --- a/session/session.go +++ b/session/session.go @@ -12,6 +12,8 @@ import ( "strings" "time" + "github.com/ory/kratos/x" + "github.com/ory/x/httpx" "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/stringsx" @@ -214,8 +216,8 @@ func NewActiveSession(r *http.Request, i *identity.Identity, c lifespanProvider, func NewInactiveSession() *Session { return &Session{ ID: uuid.Nil, - Token: randx.MustString(32, randx.AlphaNum), - LogoutToken: randx.MustString(32, randx.AlphaNum), + Token: x.OrySessionToken + randx.MustString(32, randx.AlphaNum), + LogoutToken: x.OryLogoutToken + randx.MustString(32, randx.AlphaNum), Active: false, AuthenticatorAssuranceLevel: identity.NoAuthenticatorAssuranceLevel, } diff --git a/spec/api.json b/spec/api.json index ae9f57c340f8..a05eef6b3ae0 100755 --- a/spec/api.json +++ b/spec/api.json @@ -3001,7 +3001,7 @@ "type": "object" }, "verificationFlow": { - "description": "Used to verify an out-of-band communication\nchannel such as an email address or a phone number.\n\nFor more information head over to: https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation", + "description": "Used to verify an out-of-band communication\nchannel such as an email address or a phone number.\n\nFor more information head over to: https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation", "properties": { "active": { "description": "Active, if set, contains the registration method that is being used. It is initially\nnot set.", @@ -5998,7 +5998,7 @@ }, "/self-service/verification": { "post": { - "description": "Use this endpoint to complete a verification flow. This endpoint\nbehaves differently for API and browser flows and has several states:\n\n`choose_method` expects `flow` (in the URL query) and `email` (in the body) to be sent\nand works with API- and Browser-initiated flows.\nFor API clients and Browser clients with HTTP Header `Accept: application/json` it either returns a HTTP 200 OK when the form is valid and HTTP 400 OK when the form is invalid\nand a HTTP 303 See Other redirect with a fresh verification flow if the flow was otherwise invalid (e.g. expired).\nFor Browser clients without HTTP Header `Accept` or with `Accept: text/*` it returns a HTTP 303 See Other redirect to the Verification UI URL with the Verification Flow ID appended.\n`sent_email` is the success state after `choose_method` when using the `link` method and allows the user to request another verification email. It\nworks for both API and Browser-initiated flows and returns the same responses as the flow in `choose_method` state.\n`passed_challenge` expects a `token` to be sent in the URL query and given the nature of the flow (\"sending a verification link\")\ndoes not have any API capabilities. The server responds with a HTTP 303 See Other redirect either to the Settings UI URL\n(if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with\na new Verification Flow ID which contains an error message that the verification link was invalid.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "Use this endpoint to complete a verification flow. This endpoint\nbehaves differently for API and browser flows and has several states:\n\n`choose_method` expects `flow` (in the URL query) and `email` (in the body) to be sent\nand works with API- and Browser-initiated flows.\nFor API clients and Browser clients with HTTP Header `Accept: application/json` it either returns a HTTP 200 OK when the form is valid and HTTP 400 OK when the form is invalid\nand a HTTP 303 See Other redirect with a fresh verification flow if the flow was otherwise invalid (e.g. expired).\nFor Browser clients without HTTP Header `Accept` or with `Accept: text/*` it returns a HTTP 303 See Other redirect to the Verification UI URL with the Verification Flow ID appended.\n`sent_email` is the success state after `choose_method` when using the `link` method and allows the user to request another verification email. It\nworks for both API and Browser-initiated flows and returns the same responses as the flow in `choose_method` state.\n`passed_challenge` expects a `token` to be sent in the URL query and given the nature of the flow (\"sending a verification link\")\ndoes not have any API capabilities. The server responds with a HTTP 303 See Other redirect either to the Settings UI URL\n(if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with\na new Verification Flow ID which contains an error message that the verification link was invalid.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "updateVerificationFlow", "parameters": [ { @@ -6138,7 +6138,7 @@ }, "/self-service/verification/browser": { "get": { - "description": "This endpoint initializes a browser-based account verification flow. Once initialized, the browser will be redirected to\n`selfservice.flows.verification.ui_url` with the flow ID set as the query parameter `?flow=`.\n\nIf this endpoint is called via an AJAX request, the response contains the recovery flow without any redirects.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "This endpoint initializes a browser-based account verification flow. Once initialized, the browser will be redirected to\n`selfservice.flows.verification.ui_url` with the flow ID set as the query parameter `?flow=`.\n\nIf this endpoint is called via an AJAX request, the response contains the recovery flow without any redirects.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "createBrowserVerificationFlow", "parameters": [ { @@ -6183,7 +6183,7 @@ }, "/self-service/verification/flows": { "get": { - "description": "This endpoint returns a verification flow's context with, for example, error details and other information.\n\nBrowser flows expect the anti-CSRF cookie to be included in the request's HTTP Cookie Header.\nFor AJAX requests you must ensure that cookies are included in the request or requests will fail.\n\nIf you use the browser-flow for server-side apps, the services need to run on a common top-level-domain\nand you need to forward the incoming HTTP Cookie header to this endpoint:\n\n```js\npseudo-code example\nrouter.get('/recovery', async function (req, res) {\nconst flow = await client.getVerificationFlow(req.header('cookie'), req.query['flow'])\n\nres.render('verification', flow)\n})\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "This endpoint returns a verification flow's context with, for example, error details and other information.\n\nBrowser flows expect the anti-CSRF cookie to be included in the request's HTTP Cookie Header.\nFor AJAX requests you must ensure that cookies are included in the request or requests will fail.\n\nIf you use the browser-flow for server-side apps, the services need to run on a common top-level-domain\nand you need to forward the incoming HTTP Cookie header to this endpoint:\n\n```js\npseudo-code example\nrouter.get('/recovery', async function (req, res) {\nconst flow = await client.getVerificationFlow(req.header('cookie'), req.query['flow'])\n\nres.render('verification', flow)\n})\n```\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "getVerificationFlow", "parameters": [ { diff --git a/spec/swagger.json b/spec/swagger.json index 072159518f44..1e9029d5644c 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -2443,7 +2443,7 @@ }, "/self-service/verification": { "post": { - "description": "Use this endpoint to complete a verification flow. This endpoint\nbehaves differently for API and browser flows and has several states:\n\n`choose_method` expects `flow` (in the URL query) and `email` (in the body) to be sent\nand works with API- and Browser-initiated flows.\nFor API clients and Browser clients with HTTP Header `Accept: application/json` it either returns a HTTP 200 OK when the form is valid and HTTP 400 OK when the form is invalid\nand a HTTP 303 See Other redirect with a fresh verification flow if the flow was otherwise invalid (e.g. expired).\nFor Browser clients without HTTP Header `Accept` or with `Accept: text/*` it returns a HTTP 303 See Other redirect to the Verification UI URL with the Verification Flow ID appended.\n`sent_email` is the success state after `choose_method` when using the `link` method and allows the user to request another verification email. It\nworks for both API and Browser-initiated flows and returns the same responses as the flow in `choose_method` state.\n`passed_challenge` expects a `token` to be sent in the URL query and given the nature of the flow (\"sending a verification link\")\ndoes not have any API capabilities. The server responds with a HTTP 303 See Other redirect either to the Settings UI URL\n(if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with\na new Verification Flow ID which contains an error message that the verification link was invalid.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "Use this endpoint to complete a verification flow. This endpoint\nbehaves differently for API and browser flows and has several states:\n\n`choose_method` expects `flow` (in the URL query) and `email` (in the body) to be sent\nand works with API- and Browser-initiated flows.\nFor API clients and Browser clients with HTTP Header `Accept: application/json` it either returns a HTTP 200 OK when the form is valid and HTTP 400 OK when the form is invalid\nand a HTTP 303 See Other redirect with a fresh verification flow if the flow was otherwise invalid (e.g. expired).\nFor Browser clients without HTTP Header `Accept` or with `Accept: text/*` it returns a HTTP 303 See Other redirect to the Verification UI URL with the Verification Flow ID appended.\n`sent_email` is the success state after `choose_method` when using the `link` method and allows the user to request another verification email. It\nworks for both API and Browser-initiated flows and returns the same responses as the flow in `choose_method` state.\n`passed_challenge` expects a `token` to be sent in the URL query and given the nature of the flow (\"sending a verification link\")\ndoes not have any API capabilities. The server responds with a HTTP 303 See Other redirect either to the Settings UI URL\n(if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with\na new Verification Flow ID which contains an error message that the verification link was invalid.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "consumes": [ "application/json", "application/x-www-form-urlencoded" @@ -2556,7 +2556,7 @@ }, "/self-service/verification/browser": { "get": { - "description": "This endpoint initializes a browser-based account verification flow. Once initialized, the browser will be redirected to\n`selfservice.flows.verification.ui_url` with the flow ID set as the query parameter `?flow=`.\n\nIf this endpoint is called via an AJAX request, the response contains the recovery flow without any redirects.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "This endpoint initializes a browser-based account verification flow. Once initialized, the browser will be redirected to\n`selfservice.flows.verification.ui_url` with the flow ID set as the query parameter `?flow=`.\n\nIf this endpoint is called via an AJAX request, the response contains the recovery flow without any redirects.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "schemes": [ "http", "https" @@ -2595,7 +2595,7 @@ }, "/self-service/verification/flows": { "get": { - "description": "This endpoint returns a verification flow's context with, for example, error details and other information.\n\nBrowser flows expect the anti-CSRF cookie to be included in the request's HTTP Cookie Header.\nFor AJAX requests you must ensure that cookies are included in the request or requests will fail.\n\nIf you use the browser-flow for server-side apps, the services need to run on a common top-level-domain\nand you need to forward the incoming HTTP Cookie header to this endpoint:\n\n```js\npseudo-code example\nrouter.get('/recovery', async function (req, res) {\nconst flow = await client.getVerificationFlow(req.header('cookie'), req.query['flow'])\n\nres.render('verification', flow)\n})\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation).", + "description": "This endpoint returns a verification flow's context with, for example, error details and other information.\n\nBrowser flows expect the anti-CSRF cookie to be included in the request's HTTP Cookie Header.\nFor AJAX requests you must ensure that cookies are included in the request or requests will fail.\n\nIf you use the browser-flow for server-side apps, the services need to run on a common top-level-domain\nand you need to forward the incoming HTTP Cookie header to this endpoint:\n\n```js\npseudo-code example\nrouter.get('/recovery', async function (req, res) {\nconst flow = await client.getVerificationFlow(req.header('cookie'), req.query['flow'])\n\nres.render('verification', flow)\n})\n```\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "produces": [ "application/json" ], @@ -5640,7 +5640,7 @@ } }, "verificationFlow": { - "description": "Used to verify an out-of-band communication\nchannel such as an email address or a phone number.\n\nFor more information head over to: https://www.ory.sh/docs/kratos/selfservice/flows/verify-email-account-activation", + "description": "Used to verify an out-of-band communication\nchannel such as an email address or a phone number.\n\nFor more information head over to: https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation", "type": "object", "title": "A Verification Flow", "required": [ diff --git a/test/e2e/cypress/integration/profiles/oidc/registration/success.spec.ts b/test/e2e/cypress/integration/profiles/oidc/registration/success.spec.ts index 53c5a3fd73a8..ca2e2700ef7a 100644 --- a/test/e2e/cypress/integration/profiles/oidc/registration/success.spec.ts +++ b/test/e2e/cypress/integration/profiles/oidc/registration/success.spec.ts @@ -211,6 +211,56 @@ context("Social Sign Up Successes", () => { .its("request.url") .should("include", "login_hint=" + encodeURIComponent(email)) }) + + it("oidc registration with duplicate identifier should return new login flow with duplicate error", () => { + cy.visit(registration) + + const email = gen.email() + const password = gen.password() + + cy.get('input[name="traits.email"]').type(email) + cy.get('input[name="password"]').type(password) + cy.get('input[name="traits.website"]').type(website) + cy.get('[name="traits.consent"]').siblings("label").click() + cy.get('[name="traits.newsletter"]').siblings("label").click() + + cy.submitPasswordForm() + + cy.location("pathname").should("not.contain", "/registration") + + cy.getSession().should(shouldSession(email)) + + cy.logout() + cy.noSession() + + // register the account through the OIDC provider + cy.registerOidc({ + app, + acceptConsent: true, + acceptLogin: true, + expectSession: false, + email, + website, + route: registration, + }) + + cy.get('[data-testid="ui/message/4000027"]').should("be.visible") + + cy.location("href").should("contain", "/login") + + cy.get("[name='provider'][value='hydra']").should("be.visible") + cy.get("[name='provider'][value='google']").should("be.visible") + cy.get("[name='provider'][value='github']").should("be.visible") + + if (app === "express") { + cy.get("[data-testid='forgot-password-link']").should("be.visible") + } + + cy.get("input[name='identifier']").type(email) + cy.get("input[name='password']").type(password) + cy.submitPasswordForm() + cy.getSession() + }) }) }) }) diff --git a/test/e2e/cypress/integration/profiles/oidc/settings/success.spec.ts b/test/e2e/cypress/integration/profiles/oidc/settings/success.spec.ts index 404ca718d173..fefa54d513ac 100644 --- a/test/e2e/cypress/integration/profiles/oidc/settings/success.spec.ts +++ b/test/e2e/cypress/integration/profiles/oidc/settings/success.spec.ts @@ -42,7 +42,7 @@ context("Social Sign In Settings Success", () => { cy.get('input[name="traits.website"]').clear().type(website) cy.triggerOidc(app, "hydra") - cy.get('[data-testid="ui/message/4000007"]').should( + cy.get('[data-testid="ui/message/4000027"]').should( "contain.text", "An account with the same identifier", ) diff --git a/test/e2e/profiles/oidc/.kratos.yml b/test/e2e/profiles/oidc/.kratos.yml index d61f50507568..d1d6b5f696b5 100644 --- a/test/e2e/profiles/oidc/.kratos.yml +++ b/test/e2e/profiles/oidc/.kratos.yml @@ -4,8 +4,7 @@ selfservice: enabled: true config: providers: - - - id: hydra + - id: hydra label: Ory provider: generic client_id: ${OIDC_HYDRA_CLIENT_ID} @@ -14,8 +13,7 @@ selfservice: scope: - offline mapper_url: file://test/e2e/profiles/oidc/hydra.jsonnet - - - id: google + - id: google provider: generic client_id: ${OIDC_GOOGLE_CLIENT_ID} client_secret: ${OIDC_GOOGLE_CLIENT_SECRET} @@ -23,8 +21,7 @@ selfservice: scope: - offline mapper_url: file://test/e2e/profiles/oidc/hydra.jsonnet - - - id: github + - id: github provider: generic client_id: ${OIDC_GITHUB_CLIENT_ID} client_secret: ${OIDC_GITHUB_CLIENT_SECRET} @@ -45,10 +42,12 @@ selfservice: registration: ui_url: http://localhost:4455/registration after: + password: + hooks: + - hook: session oidc: hooks: - - - hook: session + - hook: session login: ui_url: http://localhost:4455/login error: @@ -60,9 +59,13 @@ selfservice: identity: schemas: - - id: default - url: file://test/e2e/profiles/oidc/identity.traits.schema.json + - id: default + url: file://test/e2e/profiles/oidc/identity.traits.schema.json secrets: cipher: - secret-thirty-two-character-long + +session: + whoami: + required_aal: aal1 diff --git a/test/e2e/run.sh b/test/e2e/run.sh index bfa80ed77f27..073197014e32 100755 --- a/test/e2e/run.sh +++ b/test/e2e/run.sh @@ -79,7 +79,7 @@ prepare() { docker rm -f kratos_test_database_mysql kratos_test_database_postgres kratos_test_database_cockroach || true docker run --platform linux/amd64 --name kratos_test_database_mysql -p 3444:3306 -e MYSQL_ROOT_PASSWORD=secret -d mysql:5.7 docker run --name kratos_test_database_postgres -p 3445:5432 -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=postgres -d postgres:9.6 postgres -c log_statement=all - docker run --name kratos_test_database_cockroach -p 3446:26257 -d cockroachdb/cockroach:v20.2.4 start-single-node --insecure + docker run --name kratos_test_database_cockroach -p 3446:26257 -d cockroachdb/cockroach:v22.2.6 start-single-node --insecure export TEST_DATABASE_MYSQL="mysql://root:secret@(localhost:3444)/mysql?parseTime=true&multiStatements=true" export TEST_DATABASE_POSTGRESQL="postgres://postgres:secret@localhost:3445/postgres?sslmode=disable" diff --git a/text/id.go b/text/id.go index ff3892262304..97a4d8062ff8 100644 --- a/text/id.go +++ b/text/id.go @@ -119,6 +119,7 @@ const ( ErrorValidationUniqueItems ErrorValidationWrongType ErrorValidationOIDCUserNotFound + ErrorValidationDuplicateCredentialsOnOIDCLink ) const ( diff --git a/text/message_validation.go b/text/message_validation.go index 5fb5e8856b2f..b74047d52d4c 100644 --- a/text/message_validation.go +++ b/text/message_validation.go @@ -164,6 +164,15 @@ func NewErrorValidationDuplicateCredentials() *Message { } } +func NewErrorValidationDuplicateCredentialsOnOIDCLink() *Message { + return &Message{ + ID: ErrorValidationDuplicateCredentialsOnOIDCLink, + Text: "An account with the same identifier (email, phone, username, ...) exists already. Please sign in to your existing account and link your social profile in the settings page.", + Type: Error, + Context: context(nil), + } +} + func NewErrorValidationTOTPVerifierWrong() *Message { return &Message{ ID: ErrorValidationTOTPVerifierWrong, diff --git a/x/token_prefixes.go b/x/token_prefixes.go new file mode 100644 index 000000000000..577723e6f7ee --- /dev/null +++ b/x/token_prefixes.go @@ -0,0 +1,7 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package x + +const OrySessionToken = "ory_st_" +const OryLogoutToken = "ory_lo_"