From 6ffe86ca23c167540c7d7217e8096795a28795bd Mon Sep 17 00:00:00 2001 From: Jonas Hungershausen Date: Mon, 20 Mar 2023 09:09:47 +0100 Subject: [PATCH] feat: return `verification_flow_id` after registration --- .github/workflows/ci.yaml | 12 +- .schema/openapi/patches/schema.yaml | 14 + .../kratos/email-password/kratos.yml | 1 + driver/config/stub/.kratos.yaml | 2 +- driver/registry_default.go | 9 +- driver/registry_default_hooks.go | 9 + driver/registry_default_test.go | 13 + embedx/config.schema.json | 17 +- identity/identity_verification.go | 3 +- internal/client-go/.openapi-generator/FILES | 8 + internal/client-go/README.md | 4 + internal/client-go/model_continue_with.go | 146 +++++++ .../model_continue_with_flow_view.go | 150 +++++++ ...del_continue_with_set_ory_session_token.go | 138 ++++++ .../model_continue_with_set_token.go | 138 ++++++ .../model_continue_with_verification_ui.go | 137 ++++++ ...el_continue_with_verification_ui_all_of.go | 114 +++++ ...ntinue_with_verification_ui_all_of_flow.go | 114 +++++ ...odel_continue_with_verification_ui_flow.go | 175 ++++++++ .../client-go/model_flow_redirect_required.go | 403 ++++++++++++++++++ internal/client-go/model_settings_flow.go | 37 ++ .../model_successful_native_registration.go | 41 +- internal/httpclient/.openapi-generator/FILES | 8 + internal/httpclient/README.md | 4 + internal/httpclient/model_continue_with.go | 146 +++++++ ...del_continue_with_set_ory_session_token.go | 138 ++++++ .../model_continue_with_verification_ui.go | 137 ++++++ ...odel_continue_with_verification_ui_flow.go | 175 ++++++++ internal/httpclient/model_settings_flow.go | 37 ++ .../model_successful_native_registration.go | 41 +- internal/testhelpers/config.go | 18 + internal/testhelpers/selfservice_settings.go | 5 +- internal/testhelpers/snapshot.go | 1 + selfservice/flow/continue_with.go | 108 +++++ selfservice/flow/registration/flow.go | 18 +- selfservice/flow/registration/hook.go | 5 +- selfservice/flow/registration/hook_test.go | 39 ++ selfservice/flow/registration/session.go | 9 + .../stub/registration-multi-email.schema.json | 29 ++ .../stub/registration.schema.json | 13 + selfservice/flow/settings/flow.go | 18 +- selfservice/flow/settings/hook.go | 4 + selfservice/flow/settings/hook_test.go | 31 +- .../settings/stub/multi-email.schema.json | 29 ++ selfservice/flow/verification/flow.go | 2 + selfservice/hook/hooks.go | 1 + selfservice/hook/session_issuer.go | 13 +- selfservice/hook/session_issuer_test.go | 19 +- selfservice/hook/show_verification_ui.go | 67 +++ selfservice/hook/verification.go | 32 +- selfservice/hook/verification_test.go | 46 +- ...all_the_correct_verification_payloads.json | 26 +- ...erification_payloads_after_submission.json | 26 +- .../strategy/code/strategy_verification.go | 85 ++-- .../code/strategy_verification_test.go | 4 +- spec/api.json | 98 +++++ spec/swagger.json | 83 ++++ .../email/registration/success.spec.ts | 59 +++ test/e2e/cypress/support/commands.ts | 42 +- test/e2e/cypress/support/index.d.ts | 28 +- test/e2e/profiles/email/.kratos.yml | 5 +- .../profiles/oidc/identity.traits.schema.json | 8 +- 62 files changed, 3200 insertions(+), 142 deletions(-) create mode 100644 internal/client-go/model_continue_with.go create mode 100644 internal/client-go/model_continue_with_flow_view.go create mode 100644 internal/client-go/model_continue_with_set_ory_session_token.go create mode 100644 internal/client-go/model_continue_with_set_token.go create mode 100644 internal/client-go/model_continue_with_verification_ui.go create mode 100644 internal/client-go/model_continue_with_verification_ui_all_of.go create mode 100644 internal/client-go/model_continue_with_verification_ui_all_of_flow.go create mode 100644 internal/client-go/model_continue_with_verification_ui_flow.go create mode 100644 internal/client-go/model_flow_redirect_required.go create mode 100644 internal/httpclient/model_continue_with.go create mode 100644 internal/httpclient/model_continue_with_set_ory_session_token.go create mode 100644 internal/httpclient/model_continue_with_verification_ui.go create mode 100644 internal/httpclient/model_continue_with_verification_ui_flow.go create mode 100644 selfservice/flow/continue_with.go create mode 100644 selfservice/flow/registration/stub/registration-multi-email.schema.json create mode 100644 selfservice/flow/settings/stub/multi-email.schema.json create mode 100644 selfservice/hook/show_verification_ui.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c06f0dc3bc58..74279d3851a4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -184,17 +184,25 @@ jobs: with: repository: ory/kratos-selfservice-ui-node path: node-ui + - name: Install selfservice-ui-react-nextjs + uses: actions/checkout@v3 + with: + repository: ory/kratos-selfservice-ui-react-nextjs + path: react-ui + ref: jonas-jonas/feat/showVerificationAfterRegistration - run: | - cd node-ui - npm install + cd react-ui + npm ci - run: | echo 'RN_UI_PATH='"$(realpath react-native-ui)" >> $GITHUB_ENV echo 'NODE_UI_PATH='"$(realpath node-ui)" >> $GITHUB_ENV + echo 'REACT_UI_PATH='"$(realpath react-ui)" >> $GITHUB_ENV - run: | ./test/e2e/run.sh ${{ matrix.database }} env: RN_UI_PATH: react-native-ui NODE_UI_PATH: node-ui + REACT_UI_PATH: react-ui CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - if: failure() uses: actions/upload-artifact@v2 diff --git a/.schema/openapi/patches/schema.yaml b/.schema/openapi/patches/schema.yaml index e5e8606e82ce..c00ee48a04cd 100644 --- a/.schema/openapi/patches/schema.yaml +++ b/.schema/openapi/patches/schema.yaml @@ -33,3 +33,17 @@ - op: remove path: "#/components/schemas/identityTraits/type" + +- op: add + path: /components/schemas/continueWith/discriminator + value: + propertyName: action + mapping: + verification_ui: "#/components/schemas/continueWithVerificationUi" + set_ory_session_token: "#/components/schemas/continueWithSetOrySessionToken" + +- op: add + path: /components/schemas/continueWith/oneOf + value: + - "$ref": "#/components/schemas/continueWithVerificationUi" + - "$ref": "#/components/schemas/continueWithSetOrySessionToken" diff --git a/contrib/quickstart/kratos/email-password/kratos.yml b/contrib/quickstart/kratos/email-password/kratos.yml index d435f894429c..02a7fcd7bd81 100644 --- a/contrib/quickstart/kratos/email-password/kratos.yml +++ b/contrib/quickstart/kratos/email-password/kratos.yml @@ -65,6 +65,7 @@ selfservice: password: hooks: - hook: session + - hook: show_verification_ui log: level: debug diff --git a/driver/config/stub/.kratos.yaml b/driver/config/stub/.kratos.yaml index 836c600503eb..ce4d2f3c27a5 100644 --- a/driver/config/stub/.kratos.yaml +++ b/driver/config/stub/.kratos.yaml @@ -109,7 +109,7 @@ selfservice: body: /path/to/template.jsonnet settings: - ui_url: http://test.kratos.ory.sh/settings + ui_url: http://test.kratos.ory.sh/settings lifespan: 99m privileged_session_max_age: 5m after: diff --git a/driver/registry_default.go b/driver/registry_default.go index f02bb440d465..bb64d40c0eee 100644 --- a/driver/registry_default.go +++ b/driver/registry_default.go @@ -96,10 +96,11 @@ type RegistryDefault struct { persister persistence.Persister migrationStatus popx.MigrationStatuses - hookVerifier *hook.Verifier - hookSessionIssuer *hook.SessionIssuer - hookSessionDestroyer *hook.SessionDestroyer - hookAddressVerifier *hook.AddressVerifier + hookVerifier *hook.Verifier + hookSessionIssuer *hook.SessionIssuer + hookSessionDestroyer *hook.SessionDestroyer + hookAddressVerifier *hook.AddressVerifier + hookShowVerificationUI *hook.ShowVerificationUI identityHandler *identity.Handler identityValidator *identity.Validator diff --git a/driver/registry_default_hooks.go b/driver/registry_default_hooks.go index bd7c7c677c01..e0bef5cb0434 100644 --- a/driver/registry_default_hooks.go +++ b/driver/registry_default_hooks.go @@ -36,6 +36,13 @@ func (m *RegistryDefault) HookAddressVerifier() *hook.AddressVerifier { return m.hookAddressVerifier } +func (m *RegistryDefault) HookShowVerificationUI() *hook.ShowVerificationUI { + if m.hookShowVerificationUI == nil { + m.hookShowVerificationUI = hook.NewShowVerificationUI(m) + } + return m.hookShowVerificationUI +} + func (m *RegistryDefault) WithHooks(hooks map[string]func(config.SelfServiceHook) interface{}) { m.injectedSelfserviceHooks = hooks } @@ -51,6 +58,8 @@ func (m *RegistryDefault) getHooks(credentialsType string, configs []config.Self i = append(i, hook.NewWebHook(m, h.Config)) case hook.KeyAddressVerifier: i = append(i, m.HookAddressVerifier()) + case hook.KeyVerificationUI: + i = append(i, m.HookShowVerificationUI()) default: var found bool for name, m := range m.injectedSelfserviceHooks { diff --git a/driver/registry_default_test.go b/driver/registry_default_test.go index 9defafb76dfc..aa3426a1771a 100644 --- a/driver/registry_default_test.go +++ b/driver/registry_default_test.go @@ -315,6 +315,19 @@ func TestDriverDefault_Hooks(t *testing.T) { } }, }, + { + uc: "show_verification_ui is configured", + prep: func(conf *config.Config) { + conf.MustSet(ctx, config.ViperKeySelfServiceRegistrationAfter+".hooks", []map[string]interface{}{ + {"hook": "show_verification_ui"}, + }) + }, + expect: func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor { + return []registration.PostHookPostPersistExecutor{ + hook.NewShowVerificationUI(reg), + } + }, + }, } { t.Run(fmt.Sprintf("after/uc=%s", tc.uc), func(t *testing.T) { conf, reg := internal.NewVeryFastRegistryWithoutDB(t) diff --git a/embedx/config.schema.json b/embedx/config.schema.json index 3a5d1ce439ba..f7567a5143bd 100644 --- a/embedx/config.schema.json +++ b/embedx/config.schema.json @@ -84,6 +84,18 @@ "hook" ] }, + "selfServiceShowVerificationUIHook": { + "type": "object", + "properties": { + "hook": { + "const": "show_verification_ui" + } + }, + "additionalProperties": false, + "required": [ + "hook" + ] + }, "webHookAuthBasicAuthProperties": { "properties": { "type": { @@ -744,6 +756,9 @@ }, { "$ref": "#/definitions/selfServiceWebHook" + }, + { + "$ref": "#/definitions/selfServiceShowVerificationUIHook" } ] }, @@ -2647,4 +2662,4 @@ "selfservice" ], "additionalProperties": false -} \ No newline at end of file +} diff --git a/identity/identity_verification.go b/identity/identity_verification.go index cf3c91b8faa8..697af0594b4f 100644 --- a/identity/identity_verification.go +++ b/identity/identity_verification.go @@ -84,8 +84,7 @@ type VerifiableAddress struct { // IdentityID is a helper struct field for gobuffalo.pop. IdentityID uuid.UUID `json:"-" faker:"-" db:"identity_id"` - // CreatedAt is a helper struct field for gobuffalo.pop. - NID uuid.UUID `json:"-" faker:"-" db:"nid"` + NID uuid.UUID `json:"-" faker:"-" db:"nid"` } func (v VerifiableAddressType) HTMLFormInputType() string { diff --git a/internal/client-go/.openapi-generator/FILES b/internal/client-go/.openapi-generator/FILES index 856acc65ec1d..cd0e668eddb6 100644 --- a/internal/client-go/.openapi-generator/FILES +++ b/internal/client-go/.openapi-generator/FILES @@ -9,6 +9,10 @@ api_metadata.go client.go configuration.go docs/AuthenticatorAssuranceLevel.md +docs/ContinueWith.md +docs/ContinueWithSetOrySessionToken.md +docs/ContinueWithVerificationUi.md +docs/ContinueWithVerificationUiFlow.md docs/CourierApi.md docs/CourierMessageStatus.md docs/CourierMessageType.md @@ -113,6 +117,10 @@ git_push.sh go.mod go.sum model_authenticator_assurance_level.go +model_continue_with.go +model_continue_with_set_ory_session_token.go +model_continue_with_verification_ui.go +model_continue_with_verification_ui_flow.go model_courier_message_status.go model_courier_message_type.go model_create_identity_body.go diff --git a/internal/client-go/README.md b/internal/client-go/README.md index 25fe3c7e244d..e5d1f5735fd2 100644 --- a/internal/client-go/README.md +++ b/internal/client-go/README.md @@ -135,6 +135,10 @@ Class | Method | HTTP request | Description ## Documentation For Models - [AuthenticatorAssuranceLevel](docs/AuthenticatorAssuranceLevel.md) + - [ContinueWith](docs/ContinueWith.md) + - [ContinueWithSetOrySessionToken](docs/ContinueWithSetOrySessionToken.md) + - [ContinueWithVerificationUi](docs/ContinueWithVerificationUi.md) + - [ContinueWithVerificationUiFlow](docs/ContinueWithVerificationUiFlow.md) - [CourierMessageStatus](docs/CourierMessageStatus.md) - [CourierMessageType](docs/CourierMessageType.md) - [CreateIdentityBody](docs/CreateIdentityBody.md) diff --git a/internal/client-go/model_continue_with.go b/internal/client-go/model_continue_with.go new file mode 100644 index 000000000000..2cd5dd77542c --- /dev/null +++ b/internal/client-go/model_continue_with.go @@ -0,0 +1,146 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" + "fmt" +) + +// ContinueWith - struct for ContinueWith +type ContinueWith struct { + ContinueWithSetOrySessionToken *ContinueWithSetOrySessionToken + ContinueWithVerificationUi *ContinueWithVerificationUi +} + +// ContinueWithSetOrySessionTokenAsContinueWith is a convenience function that returns ContinueWithSetOrySessionToken wrapped in ContinueWith +func ContinueWithSetOrySessionTokenAsContinueWith(v *ContinueWithSetOrySessionToken) ContinueWith { + return ContinueWith{ + ContinueWithSetOrySessionToken: v, + } +} + +// ContinueWithVerificationUiAsContinueWith is a convenience function that returns ContinueWithVerificationUi wrapped in ContinueWith +func ContinueWithVerificationUiAsContinueWith(v *ContinueWithVerificationUi) ContinueWith { + return ContinueWith{ + ContinueWithVerificationUi: v, + } +} + +// Unmarshal JSON data into one of the pointers in the struct +func (dst *ContinueWith) UnmarshalJSON(data []byte) error { + var err error + match := 0 + // try to unmarshal data into ContinueWithSetOrySessionToken + err = newStrictDecoder(data).Decode(&dst.ContinueWithSetOrySessionToken) + if err == nil { + jsonContinueWithSetOrySessionToken, _ := json.Marshal(dst.ContinueWithSetOrySessionToken) + if string(jsonContinueWithSetOrySessionToken) == "{}" { // empty struct + dst.ContinueWithSetOrySessionToken = nil + } else { + match++ + } + } else { + dst.ContinueWithSetOrySessionToken = nil + } + + // try to unmarshal data into ContinueWithVerificationUi + err = newStrictDecoder(data).Decode(&dst.ContinueWithVerificationUi) + if err == nil { + jsonContinueWithVerificationUi, _ := json.Marshal(dst.ContinueWithVerificationUi) + if string(jsonContinueWithVerificationUi) == "{}" { // empty struct + dst.ContinueWithVerificationUi = nil + } else { + match++ + } + } else { + dst.ContinueWithVerificationUi = nil + } + + if match > 1 { // more than 1 match + // reset to nil + dst.ContinueWithSetOrySessionToken = nil + dst.ContinueWithVerificationUi = nil + + return fmt.Errorf("Data matches more than one schema in oneOf(ContinueWith)") + } else if match == 1 { + return nil // exactly one match + } else { // no match + return fmt.Errorf("Data failed to match schemas in oneOf(ContinueWith)") + } +} + +// Marshal data from the first non-nil pointers in the struct to JSON +func (src ContinueWith) MarshalJSON() ([]byte, error) { + if src.ContinueWithSetOrySessionToken != nil { + return json.Marshal(&src.ContinueWithSetOrySessionToken) + } + + if src.ContinueWithVerificationUi != nil { + return json.Marshal(&src.ContinueWithVerificationUi) + } + + return nil, nil // no data in oneOf schemas +} + +// Get the actual instance +func (obj *ContinueWith) GetActualInstance() interface{} { + if obj == nil { + return nil + } + if obj.ContinueWithSetOrySessionToken != nil { + return obj.ContinueWithSetOrySessionToken + } + + if obj.ContinueWithVerificationUi != nil { + return obj.ContinueWithVerificationUi + } + + // all schemas are nil + return nil +} + +type NullableContinueWith struct { + value *ContinueWith + isSet bool +} + +func (v NullableContinueWith) Get() *ContinueWith { + return v.value +} + +func (v *NullableContinueWith) Set(val *ContinueWith) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWith) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWith) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWith(val *ContinueWith) *NullableContinueWith { + return &NullableContinueWith{value: val, isSet: true} +} + +func (v NullableContinueWith) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWith) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/client-go/model_continue_with_flow_view.go b/internal/client-go/model_continue_with_flow_view.go new file mode 100644 index 000000000000..66d7e57fda0e --- /dev/null +++ b/internal/client-go/model_continue_with_flow_view.go @@ -0,0 +1,150 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// ContinueWithFlowView struct for ContinueWithFlowView +type ContinueWithFlowView struct { + Class *string `json:"class,omitempty"` + Id *string `json:"id,omitempty"` +} + +// NewContinueWithFlowView instantiates a new ContinueWithFlowView object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewContinueWithFlowView() *ContinueWithFlowView { + this := ContinueWithFlowView{} + return &this +} + +// NewContinueWithFlowViewWithDefaults instantiates a new ContinueWithFlowView object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewContinueWithFlowViewWithDefaults() *ContinueWithFlowView { + this := ContinueWithFlowView{} + return &this +} + +// GetClass returns the Class field value if set, zero value otherwise. +func (o *ContinueWithFlowView) GetClass() string { + if o == nil || o.Class == nil { + var ret string + return ret + } + return *o.Class +} + +// GetClassOk returns a tuple with the Class field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *ContinueWithFlowView) GetClassOk() (*string, bool) { + if o == nil || o.Class == nil { + return nil, false + } + return o.Class, true +} + +// HasClass returns a boolean if a field has been set. +func (o *ContinueWithFlowView) HasClass() bool { + if o != nil && o.Class != nil { + return true + } + + return false +} + +// SetClass gets a reference to the given string and assigns it to the Class field. +func (o *ContinueWithFlowView) SetClass(v string) { + o.Class = &v +} + +// GetId returns the Id field value if set, zero value otherwise. +func (o *ContinueWithFlowView) GetId() string { + if o == nil || o.Id == nil { + var ret string + return ret + } + return *o.Id +} + +// GetIdOk returns a tuple with the Id field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *ContinueWithFlowView) GetIdOk() (*string, bool) { + if o == nil || o.Id == nil { + return nil, false + } + return o.Id, true +} + +// HasId returns a boolean if a field has been set. +func (o *ContinueWithFlowView) HasId() bool { + if o != nil && o.Id != nil { + return true + } + + return false +} + +// SetId gets a reference to the given string and assigns it to the Id field. +func (o *ContinueWithFlowView) SetId(v string) { + o.Id = &v +} + +func (o ContinueWithFlowView) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.Class != nil { + toSerialize["class"] = o.Class + } + if o.Id != nil { + toSerialize["id"] = o.Id + } + return json.Marshal(toSerialize) +} + +type NullableContinueWithFlowView struct { + value *ContinueWithFlowView + isSet bool +} + +func (v NullableContinueWithFlowView) Get() *ContinueWithFlowView { + return v.value +} + +func (v *NullableContinueWithFlowView) Set(val *ContinueWithFlowView) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWithFlowView) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWithFlowView) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWithFlowView(val *ContinueWithFlowView) *NullableContinueWithFlowView { + return &NullableContinueWithFlowView{value: val, isSet: true} +} + +func (v NullableContinueWithFlowView) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWithFlowView) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/client-go/model_continue_with_set_ory_session_token.go b/internal/client-go/model_continue_with_set_ory_session_token.go new file mode 100644 index 000000000000..2ea664a546a7 --- /dev/null +++ b/internal/client-go/model_continue_with_set_ory_session_token.go @@ -0,0 +1,138 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// ContinueWithSetOrySessionToken Indicates that a session was issued, and the application should use this token for authenticated requests +type ContinueWithSetOrySessionToken struct { + // Action will always be `set_ory_session_token` set_ory_session_token ContinueWithActionSetOrySessionToken verification_ui ContinueWithActionVerificationUI + Action string `json:"action"` + // Token is the token of the session + OrySessionToken string `json:"ory_session_token"` +} + +// NewContinueWithSetOrySessionToken instantiates a new ContinueWithSetOrySessionToken object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewContinueWithSetOrySessionToken(action string, orySessionToken string) *ContinueWithSetOrySessionToken { + this := ContinueWithSetOrySessionToken{} + this.Action = action + this.OrySessionToken = orySessionToken + return &this +} + +// NewContinueWithSetOrySessionTokenWithDefaults instantiates a new ContinueWithSetOrySessionToken object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewContinueWithSetOrySessionTokenWithDefaults() *ContinueWithSetOrySessionToken { + this := ContinueWithSetOrySessionToken{} + return &this +} + +// GetAction returns the Action field value +func (o *ContinueWithSetOrySessionToken) GetAction() string { + if o == nil { + var ret string + return ret + } + + return o.Action +} + +// GetActionOk returns a tuple with the Action field value +// and a boolean to check if the value has been set. +func (o *ContinueWithSetOrySessionToken) GetActionOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Action, true +} + +// SetAction sets field value +func (o *ContinueWithSetOrySessionToken) SetAction(v string) { + o.Action = v +} + +// GetOrySessionToken returns the OrySessionToken field value +func (o *ContinueWithSetOrySessionToken) GetOrySessionToken() string { + if o == nil { + var ret string + return ret + } + + return o.OrySessionToken +} + +// GetOrySessionTokenOk returns a tuple with the OrySessionToken field value +// and a boolean to check if the value has been set. +func (o *ContinueWithSetOrySessionToken) GetOrySessionTokenOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.OrySessionToken, true +} + +// SetOrySessionToken sets field value +func (o *ContinueWithSetOrySessionToken) SetOrySessionToken(v string) { + o.OrySessionToken = v +} + +func (o ContinueWithSetOrySessionToken) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if true { + toSerialize["action"] = o.Action + } + if true { + toSerialize["ory_session_token"] = o.OrySessionToken + } + return json.Marshal(toSerialize) +} + +type NullableContinueWithSetOrySessionToken struct { + value *ContinueWithSetOrySessionToken + isSet bool +} + +func (v NullableContinueWithSetOrySessionToken) Get() *ContinueWithSetOrySessionToken { + return v.value +} + +func (v *NullableContinueWithSetOrySessionToken) Set(val *ContinueWithSetOrySessionToken) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWithSetOrySessionToken) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWithSetOrySessionToken) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWithSetOrySessionToken(val *ContinueWithSetOrySessionToken) *NullableContinueWithSetOrySessionToken { + return &NullableContinueWithSetOrySessionToken{value: val, isSet: true} +} + +func (v NullableContinueWithSetOrySessionToken) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWithSetOrySessionToken) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/client-go/model_continue_with_set_token.go b/internal/client-go/model_continue_with_set_token.go new file mode 100644 index 000000000000..b7dc65ea984f --- /dev/null +++ b/internal/client-go/model_continue_with_set_token.go @@ -0,0 +1,138 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// ContinueWithSetToken Indicates that a session was issued, and the application should use this token for authenticated requests +type ContinueWithSetToken struct { + // Action will always be `set_token` set_token ContinueWithActionSetToken verification_ui ContinueWithActionVerificationUI + Action string `json:"action"` + // Token is the token of the session + Token string `json:"token"` +} + +// NewContinueWithSetToken instantiates a new ContinueWithSetToken object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewContinueWithSetToken(action string, token string) *ContinueWithSetToken { + this := ContinueWithSetToken{} + this.Action = action + this.Token = token + return &this +} + +// NewContinueWithSetTokenWithDefaults instantiates a new ContinueWithSetToken object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewContinueWithSetTokenWithDefaults() *ContinueWithSetToken { + this := ContinueWithSetToken{} + return &this +} + +// GetAction returns the Action field value +func (o *ContinueWithSetToken) GetAction() string { + if o == nil { + var ret string + return ret + } + + return o.Action +} + +// GetActionOk returns a tuple with the Action field value +// and a boolean to check if the value has been set. +func (o *ContinueWithSetToken) GetActionOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Action, true +} + +// SetAction sets field value +func (o *ContinueWithSetToken) SetAction(v string) { + o.Action = v +} + +// GetToken returns the Token field value +func (o *ContinueWithSetToken) GetToken() string { + if o == nil { + var ret string + return ret + } + + return o.Token +} + +// GetTokenOk returns a tuple with the Token field value +// and a boolean to check if the value has been set. +func (o *ContinueWithSetToken) GetTokenOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Token, true +} + +// SetToken sets field value +func (o *ContinueWithSetToken) SetToken(v string) { + o.Token = v +} + +func (o ContinueWithSetToken) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if true { + toSerialize["action"] = o.Action + } + if true { + toSerialize["token"] = o.Token + } + return json.Marshal(toSerialize) +} + +type NullableContinueWithSetToken struct { + value *ContinueWithSetToken + isSet bool +} + +func (v NullableContinueWithSetToken) Get() *ContinueWithSetToken { + return v.value +} + +func (v *NullableContinueWithSetToken) Set(val *ContinueWithSetToken) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWithSetToken) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWithSetToken) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWithSetToken(val *ContinueWithSetToken) *NullableContinueWithSetToken { + return &NullableContinueWithSetToken{value: val, isSet: true} +} + +func (v NullableContinueWithSetToken) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWithSetToken) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/client-go/model_continue_with_verification_ui.go b/internal/client-go/model_continue_with_verification_ui.go new file mode 100644 index 000000000000..85cd7c0ef30a --- /dev/null +++ b/internal/client-go/model_continue_with_verification_ui.go @@ -0,0 +1,137 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// ContinueWithVerificationUi Indicates, that the UI flow could be continued by showing a verification ui +type ContinueWithVerificationUi struct { + // Action will always be `verification_ui` set_ory_session_token ContinueWithActionSetOrySessionToken verification_ui ContinueWithActionVerificationUI + Action string `json:"action"` + Flow ContinueWithVerificationUiFlow `json:"flow"` +} + +// NewContinueWithVerificationUi instantiates a new ContinueWithVerificationUi object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewContinueWithVerificationUi(action string, flow ContinueWithVerificationUiFlow) *ContinueWithVerificationUi { + this := ContinueWithVerificationUi{} + this.Action = action + this.Flow = flow + return &this +} + +// NewContinueWithVerificationUiWithDefaults instantiates a new ContinueWithVerificationUi object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewContinueWithVerificationUiWithDefaults() *ContinueWithVerificationUi { + this := ContinueWithVerificationUi{} + return &this +} + +// GetAction returns the Action field value +func (o *ContinueWithVerificationUi) GetAction() string { + if o == nil { + var ret string + return ret + } + + return o.Action +} + +// GetActionOk returns a tuple with the Action field value +// and a boolean to check if the value has been set. +func (o *ContinueWithVerificationUi) GetActionOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Action, true +} + +// SetAction sets field value +func (o *ContinueWithVerificationUi) SetAction(v string) { + o.Action = v +} + +// GetFlow returns the Flow field value +func (o *ContinueWithVerificationUi) GetFlow() ContinueWithVerificationUiFlow { + if o == nil { + var ret ContinueWithVerificationUiFlow + return ret + } + + return o.Flow +} + +// GetFlowOk returns a tuple with the Flow field value +// and a boolean to check if the value has been set. +func (o *ContinueWithVerificationUi) GetFlowOk() (*ContinueWithVerificationUiFlow, bool) { + if o == nil { + return nil, false + } + return &o.Flow, true +} + +// SetFlow sets field value +func (o *ContinueWithVerificationUi) SetFlow(v ContinueWithVerificationUiFlow) { + o.Flow = v +} + +func (o ContinueWithVerificationUi) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if true { + toSerialize["action"] = o.Action + } + if true { + toSerialize["flow"] = o.Flow + } + return json.Marshal(toSerialize) +} + +type NullableContinueWithVerificationUi struct { + value *ContinueWithVerificationUi + isSet bool +} + +func (v NullableContinueWithVerificationUi) Get() *ContinueWithVerificationUi { + return v.value +} + +func (v *NullableContinueWithVerificationUi) Set(val *ContinueWithVerificationUi) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWithVerificationUi) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWithVerificationUi) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWithVerificationUi(val *ContinueWithVerificationUi) *NullableContinueWithVerificationUi { + return &NullableContinueWithVerificationUi{value: val, isSet: true} +} + +func (v NullableContinueWithVerificationUi) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWithVerificationUi) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/client-go/model_continue_with_verification_ui_all_of.go b/internal/client-go/model_continue_with_verification_ui_all_of.go new file mode 100644 index 000000000000..ac19e398f12a --- /dev/null +++ b/internal/client-go/model_continue_with_verification_ui_all_of.go @@ -0,0 +1,114 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// ContinueWithVerificationUiAllOf struct for ContinueWithVerificationUiAllOf +type ContinueWithVerificationUiAllOf struct { + Flow *ContinueWithVerificationUiAllOfFlow `json:"flow,omitempty"` +} + +// NewContinueWithVerificationUiAllOf instantiates a new ContinueWithVerificationUiAllOf object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewContinueWithVerificationUiAllOf() *ContinueWithVerificationUiAllOf { + this := ContinueWithVerificationUiAllOf{} + return &this +} + +// NewContinueWithVerificationUiAllOfWithDefaults instantiates a new ContinueWithVerificationUiAllOf object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewContinueWithVerificationUiAllOfWithDefaults() *ContinueWithVerificationUiAllOf { + this := ContinueWithVerificationUiAllOf{} + return &this +} + +// GetFlow returns the Flow field value if set, zero value otherwise. +func (o *ContinueWithVerificationUiAllOf) GetFlow() ContinueWithVerificationUiAllOfFlow { + if o == nil || o.Flow == nil { + var ret ContinueWithVerificationUiAllOfFlow + return ret + } + return *o.Flow +} + +// GetFlowOk returns a tuple with the Flow field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *ContinueWithVerificationUiAllOf) GetFlowOk() (*ContinueWithVerificationUiAllOfFlow, bool) { + if o == nil || o.Flow == nil { + return nil, false + } + return o.Flow, true +} + +// HasFlow returns a boolean if a field has been set. +func (o *ContinueWithVerificationUiAllOf) HasFlow() bool { + if o != nil && o.Flow != nil { + return true + } + + return false +} + +// SetFlow gets a reference to the given ContinueWithVerificationUiAllOfFlow and assigns it to the Flow field. +func (o *ContinueWithVerificationUiAllOf) SetFlow(v ContinueWithVerificationUiAllOfFlow) { + o.Flow = &v +} + +func (o ContinueWithVerificationUiAllOf) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.Flow != nil { + toSerialize["flow"] = o.Flow + } + return json.Marshal(toSerialize) +} + +type NullableContinueWithVerificationUiAllOf struct { + value *ContinueWithVerificationUiAllOf + isSet bool +} + +func (v NullableContinueWithVerificationUiAllOf) Get() *ContinueWithVerificationUiAllOf { + return v.value +} + +func (v *NullableContinueWithVerificationUiAllOf) Set(val *ContinueWithVerificationUiAllOf) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWithVerificationUiAllOf) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWithVerificationUiAllOf) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWithVerificationUiAllOf(val *ContinueWithVerificationUiAllOf) *NullableContinueWithVerificationUiAllOf { + return &NullableContinueWithVerificationUiAllOf{value: val, isSet: true} +} + +func (v NullableContinueWithVerificationUiAllOf) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWithVerificationUiAllOf) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/client-go/model_continue_with_verification_ui_all_of_flow.go b/internal/client-go/model_continue_with_verification_ui_all_of_flow.go new file mode 100644 index 000000000000..0ac337cc7935 --- /dev/null +++ b/internal/client-go/model_continue_with_verification_ui_all_of_flow.go @@ -0,0 +1,114 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// ContinueWithVerificationUiAllOfFlow struct for ContinueWithVerificationUiAllOfFlow +type ContinueWithVerificationUiAllOfFlow struct { + Id *string `json:"id,omitempty"` +} + +// NewContinueWithVerificationUiAllOfFlow instantiates a new ContinueWithVerificationUiAllOfFlow object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewContinueWithVerificationUiAllOfFlow() *ContinueWithVerificationUiAllOfFlow { + this := ContinueWithVerificationUiAllOfFlow{} + return &this +} + +// NewContinueWithVerificationUiAllOfFlowWithDefaults instantiates a new ContinueWithVerificationUiAllOfFlow object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewContinueWithVerificationUiAllOfFlowWithDefaults() *ContinueWithVerificationUiAllOfFlow { + this := ContinueWithVerificationUiAllOfFlow{} + return &this +} + +// GetId returns the Id field value if set, zero value otherwise. +func (o *ContinueWithVerificationUiAllOfFlow) GetId() string { + if o == nil || o.Id == nil { + var ret string + return ret + } + return *o.Id +} + +// GetIdOk returns a tuple with the Id field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *ContinueWithVerificationUiAllOfFlow) GetIdOk() (*string, bool) { + if o == nil || o.Id == nil { + return nil, false + } + return o.Id, true +} + +// HasId returns a boolean if a field has been set. +func (o *ContinueWithVerificationUiAllOfFlow) HasId() bool { + if o != nil && o.Id != nil { + return true + } + + return false +} + +// SetId gets a reference to the given string and assigns it to the Id field. +func (o *ContinueWithVerificationUiAllOfFlow) SetId(v string) { + o.Id = &v +} + +func (o ContinueWithVerificationUiAllOfFlow) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.Id != nil { + toSerialize["id"] = o.Id + } + return json.Marshal(toSerialize) +} + +type NullableContinueWithVerificationUiAllOfFlow struct { + value *ContinueWithVerificationUiAllOfFlow + isSet bool +} + +func (v NullableContinueWithVerificationUiAllOfFlow) Get() *ContinueWithVerificationUiAllOfFlow { + return v.value +} + +func (v *NullableContinueWithVerificationUiAllOfFlow) Set(val *ContinueWithVerificationUiAllOfFlow) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWithVerificationUiAllOfFlow) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWithVerificationUiAllOfFlow) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWithVerificationUiAllOfFlow(val *ContinueWithVerificationUiAllOfFlow) *NullableContinueWithVerificationUiAllOfFlow { + return &NullableContinueWithVerificationUiAllOfFlow{value: val, isSet: true} +} + +func (v NullableContinueWithVerificationUiAllOfFlow) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWithVerificationUiAllOfFlow) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/client-go/model_continue_with_verification_ui_flow.go b/internal/client-go/model_continue_with_verification_ui_flow.go new file mode 100644 index 000000000000..8fdd4609cf93 --- /dev/null +++ b/internal/client-go/model_continue_with_verification_ui_flow.go @@ -0,0 +1,175 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// ContinueWithVerificationUiFlow struct for ContinueWithVerificationUiFlow +type ContinueWithVerificationUiFlow struct { + // The ID of the verification flow + Id string `json:"id"` + // The URL of the verification flow + Url *string `json:"url,omitempty"` + // The address that should be verified in this flow + VerifiableAddress string `json:"verifiable_address"` +} + +// NewContinueWithVerificationUiFlow instantiates a new ContinueWithVerificationUiFlow object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewContinueWithVerificationUiFlow(id string, verifiableAddress string) *ContinueWithVerificationUiFlow { + this := ContinueWithVerificationUiFlow{} + this.Id = id + this.VerifiableAddress = verifiableAddress + return &this +} + +// NewContinueWithVerificationUiFlowWithDefaults instantiates a new ContinueWithVerificationUiFlow object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewContinueWithVerificationUiFlowWithDefaults() *ContinueWithVerificationUiFlow { + this := ContinueWithVerificationUiFlow{} + return &this +} + +// GetId returns the Id field value +func (o *ContinueWithVerificationUiFlow) GetId() string { + if o == nil { + var ret string + return ret + } + + return o.Id +} + +// GetIdOk returns a tuple with the Id field value +// and a boolean to check if the value has been set. +func (o *ContinueWithVerificationUiFlow) GetIdOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Id, true +} + +// SetId sets field value +func (o *ContinueWithVerificationUiFlow) SetId(v string) { + o.Id = v +} + +// GetUrl returns the Url field value if set, zero value otherwise. +func (o *ContinueWithVerificationUiFlow) GetUrl() string { + if o == nil || o.Url == nil { + var ret string + return ret + } + return *o.Url +} + +// GetUrlOk returns a tuple with the Url field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *ContinueWithVerificationUiFlow) GetUrlOk() (*string, bool) { + if o == nil || o.Url == nil { + return nil, false + } + return o.Url, true +} + +// HasUrl returns a boolean if a field has been set. +func (o *ContinueWithVerificationUiFlow) HasUrl() bool { + if o != nil && o.Url != nil { + return true + } + + return false +} + +// SetUrl gets a reference to the given string and assigns it to the Url field. +func (o *ContinueWithVerificationUiFlow) SetUrl(v string) { + o.Url = &v +} + +// GetVerifiableAddress returns the VerifiableAddress field value +func (o *ContinueWithVerificationUiFlow) GetVerifiableAddress() string { + if o == nil { + var ret string + return ret + } + + return o.VerifiableAddress +} + +// GetVerifiableAddressOk returns a tuple with the VerifiableAddress field value +// and a boolean to check if the value has been set. +func (o *ContinueWithVerificationUiFlow) GetVerifiableAddressOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.VerifiableAddress, true +} + +// SetVerifiableAddress sets field value +func (o *ContinueWithVerificationUiFlow) SetVerifiableAddress(v string) { + o.VerifiableAddress = v +} + +func (o ContinueWithVerificationUiFlow) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if true { + toSerialize["id"] = o.Id + } + if o.Url != nil { + toSerialize["url"] = o.Url + } + if true { + toSerialize["verifiable_address"] = o.VerifiableAddress + } + return json.Marshal(toSerialize) +} + +type NullableContinueWithVerificationUiFlow struct { + value *ContinueWithVerificationUiFlow + isSet bool +} + +func (v NullableContinueWithVerificationUiFlow) Get() *ContinueWithVerificationUiFlow { + return v.value +} + +func (v *NullableContinueWithVerificationUiFlow) Set(val *ContinueWithVerificationUiFlow) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWithVerificationUiFlow) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWithVerificationUiFlow) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWithVerificationUiFlow(val *ContinueWithVerificationUiFlow) *NullableContinueWithVerificationUiFlow { + return &NullableContinueWithVerificationUiFlow{value: val, isSet: true} +} + +func (v NullableContinueWithVerificationUiFlow) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWithVerificationUiFlow) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/client-go/model_flow_redirect_required.go b/internal/client-go/model_flow_redirect_required.go new file mode 100644 index 000000000000..793147f6d032 --- /dev/null +++ b/internal/client-go/model_flow_redirect_required.go @@ -0,0 +1,403 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// FlowRedirectRequired struct for FlowRedirectRequired +type FlowRedirectRequired struct { + // The status code + Code *int64 `json:"code,omitempty"` + // Debug information This field is often not exposed to protect against leaking sensitive information. + Debug *string `json:"debug,omitempty"` + // Further error details + Details map[string]interface{} `json:"details,omitempty"` + // The error ID Useful when trying to identify various errors in application logic. + Id *string `json:"id,omitempty"` + // Error message The error's message. + Message string `json:"message"` + // A human-readable reason for the error + Reason *string `json:"reason,omitempty"` + // The request ID The request ID is often exposed internally in order to trace errors across service architectures. This is often a UUID. + Request *string `json:"request,omitempty"` + SessionToken *string `json:"session_token,omitempty"` + // The status description + Status *string `json:"status,omitempty"` +} + +// NewFlowRedirectRequired instantiates a new FlowRedirectRequired object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewFlowRedirectRequired(message string) *FlowRedirectRequired { + this := FlowRedirectRequired{} + this.Message = message + return &this +} + +// NewFlowRedirectRequiredWithDefaults instantiates a new FlowRedirectRequired object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewFlowRedirectRequiredWithDefaults() *FlowRedirectRequired { + this := FlowRedirectRequired{} + return &this +} + +// GetCode returns the Code field value if set, zero value otherwise. +func (o *FlowRedirectRequired) GetCode() int64 { + if o == nil || o.Code == nil { + var ret int64 + return ret + } + return *o.Code +} + +// GetCodeOk returns a tuple with the Code field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *FlowRedirectRequired) GetCodeOk() (*int64, bool) { + if o == nil || o.Code == nil { + return nil, false + } + return o.Code, true +} + +// HasCode returns a boolean if a field has been set. +func (o *FlowRedirectRequired) HasCode() bool { + if o != nil && o.Code != nil { + return true + } + + return false +} + +// SetCode gets a reference to the given int64 and assigns it to the Code field. +func (o *FlowRedirectRequired) SetCode(v int64) { + o.Code = &v +} + +// GetDebug returns the Debug field value if set, zero value otherwise. +func (o *FlowRedirectRequired) GetDebug() string { + if o == nil || o.Debug == nil { + var ret string + return ret + } + return *o.Debug +} + +// GetDebugOk returns a tuple with the Debug field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *FlowRedirectRequired) GetDebugOk() (*string, bool) { + if o == nil || o.Debug == nil { + return nil, false + } + return o.Debug, true +} + +// HasDebug returns a boolean if a field has been set. +func (o *FlowRedirectRequired) HasDebug() bool { + if o != nil && o.Debug != nil { + return true + } + + return false +} + +// SetDebug gets a reference to the given string and assigns it to the Debug field. +func (o *FlowRedirectRequired) SetDebug(v string) { + o.Debug = &v +} + +// GetDetails returns the Details field value if set, zero value otherwise. +func (o *FlowRedirectRequired) GetDetails() map[string]interface{} { + if o == nil || o.Details == nil { + var ret map[string]interface{} + return ret + } + return o.Details +} + +// GetDetailsOk returns a tuple with the Details field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *FlowRedirectRequired) GetDetailsOk() (map[string]interface{}, bool) { + if o == nil || o.Details == nil { + return nil, false + } + return o.Details, true +} + +// HasDetails returns a boolean if a field has been set. +func (o *FlowRedirectRequired) HasDetails() bool { + if o != nil && o.Details != nil { + return true + } + + return false +} + +// SetDetails gets a reference to the given map[string]interface{} and assigns it to the Details field. +func (o *FlowRedirectRequired) SetDetails(v map[string]interface{}) { + o.Details = v +} + +// GetId returns the Id field value if set, zero value otherwise. +func (o *FlowRedirectRequired) GetId() string { + if o == nil || o.Id == nil { + var ret string + return ret + } + return *o.Id +} + +// GetIdOk returns a tuple with the Id field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *FlowRedirectRequired) GetIdOk() (*string, bool) { + if o == nil || o.Id == nil { + return nil, false + } + return o.Id, true +} + +// HasId returns a boolean if a field has been set. +func (o *FlowRedirectRequired) HasId() bool { + if o != nil && o.Id != nil { + return true + } + + return false +} + +// SetId gets a reference to the given string and assigns it to the Id field. +func (o *FlowRedirectRequired) SetId(v string) { + o.Id = &v +} + +// GetMessage returns the Message field value +func (o *FlowRedirectRequired) GetMessage() string { + if o == nil { + var ret string + return ret + } + + return o.Message +} + +// GetMessageOk returns a tuple with the Message field value +// and a boolean to check if the value has been set. +func (o *FlowRedirectRequired) GetMessageOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Message, true +} + +// SetMessage sets field value +func (o *FlowRedirectRequired) SetMessage(v string) { + o.Message = v +} + +// GetReason returns the Reason field value if set, zero value otherwise. +func (o *FlowRedirectRequired) GetReason() string { + if o == nil || o.Reason == nil { + var ret string + return ret + } + return *o.Reason +} + +// GetReasonOk returns a tuple with the Reason field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *FlowRedirectRequired) GetReasonOk() (*string, bool) { + if o == nil || o.Reason == nil { + return nil, false + } + return o.Reason, true +} + +// HasReason returns a boolean if a field has been set. +func (o *FlowRedirectRequired) HasReason() bool { + if o != nil && o.Reason != nil { + return true + } + + return false +} + +// SetReason gets a reference to the given string and assigns it to the Reason field. +func (o *FlowRedirectRequired) SetReason(v string) { + o.Reason = &v +} + +// GetRequest returns the Request field value if set, zero value otherwise. +func (o *FlowRedirectRequired) GetRequest() string { + if o == nil || o.Request == nil { + var ret string + return ret + } + return *o.Request +} + +// GetRequestOk returns a tuple with the Request field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *FlowRedirectRequired) GetRequestOk() (*string, bool) { + if o == nil || o.Request == nil { + return nil, false + } + return o.Request, true +} + +// HasRequest returns a boolean if a field has been set. +func (o *FlowRedirectRequired) HasRequest() bool { + if o != nil && o.Request != nil { + return true + } + + return false +} + +// SetRequest gets a reference to the given string and assigns it to the Request field. +func (o *FlowRedirectRequired) SetRequest(v string) { + o.Request = &v +} + +// GetSessionToken returns the SessionToken field value if set, zero value otherwise. +func (o *FlowRedirectRequired) GetSessionToken() string { + if o == nil || o.SessionToken == nil { + var ret string + return ret + } + return *o.SessionToken +} + +// GetSessionTokenOk returns a tuple with the SessionToken field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *FlowRedirectRequired) GetSessionTokenOk() (*string, bool) { + if o == nil || o.SessionToken == nil { + return nil, false + } + return o.SessionToken, true +} + +// HasSessionToken returns a boolean if a field has been set. +func (o *FlowRedirectRequired) HasSessionToken() bool { + if o != nil && o.SessionToken != nil { + return true + } + + return false +} + +// SetSessionToken gets a reference to the given string and assigns it to the SessionToken field. +func (o *FlowRedirectRequired) SetSessionToken(v string) { + o.SessionToken = &v +} + +// GetStatus returns the Status field value if set, zero value otherwise. +func (o *FlowRedirectRequired) GetStatus() string { + if o == nil || o.Status == nil { + var ret string + return ret + } + return *o.Status +} + +// GetStatusOk returns a tuple with the Status field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *FlowRedirectRequired) GetStatusOk() (*string, bool) { + if o == nil || o.Status == nil { + return nil, false + } + return o.Status, true +} + +// HasStatus returns a boolean if a field has been set. +func (o *FlowRedirectRequired) HasStatus() bool { + if o != nil && o.Status != nil { + return true + } + + return false +} + +// SetStatus gets a reference to the given string and assigns it to the Status field. +func (o *FlowRedirectRequired) SetStatus(v string) { + o.Status = &v +} + +func (o FlowRedirectRequired) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.Code != nil { + toSerialize["code"] = o.Code + } + if o.Debug != nil { + toSerialize["debug"] = o.Debug + } + if o.Details != nil { + toSerialize["details"] = o.Details + } + if o.Id != nil { + toSerialize["id"] = o.Id + } + if true { + toSerialize["message"] = o.Message + } + if o.Reason != nil { + toSerialize["reason"] = o.Reason + } + if o.Request != nil { + toSerialize["request"] = o.Request + } + if o.SessionToken != nil { + toSerialize["session_token"] = o.SessionToken + } + if o.Status != nil { + toSerialize["status"] = o.Status + } + return json.Marshal(toSerialize) +} + +type NullableFlowRedirectRequired struct { + value *FlowRedirectRequired + isSet bool +} + +func (v NullableFlowRedirectRequired) Get() *FlowRedirectRequired { + return v.value +} + +func (v *NullableFlowRedirectRequired) Set(val *FlowRedirectRequired) { + v.value = val + v.isSet = true +} + +func (v NullableFlowRedirectRequired) IsSet() bool { + return v.isSet +} + +func (v *NullableFlowRedirectRequired) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableFlowRedirectRequired(val *FlowRedirectRequired) *NullableFlowRedirectRequired { + return &NullableFlowRedirectRequired{value: val, isSet: true} +} + +func (v NullableFlowRedirectRequired) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableFlowRedirectRequired) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/client-go/model_settings_flow.go b/internal/client-go/model_settings_flow.go index 34bc9d1b69f0..a1dc0aa98dc6 100644 --- a/internal/client-go/model_settings_flow.go +++ b/internal/client-go/model_settings_flow.go @@ -20,6 +20,8 @@ import ( type SettingsFlow struct { // Active, if set, contains the registration method that is being used. It is initially not set. Active *string `json:"active,omitempty"` + // Contains a list of actions, that could follow this flow It can, for example, contain a reference to the verification flow, created as part of the user's registration. + ContinueWith []ContinueWith `json:"continue_with,omitempty"` // ExpiresAt is the time (UTC) when the flow expires. If the user still wishes to update the setting, a new flow has to be initiated. ExpiresAt time.Time `json:"expires_at"` // ID represents the flow's unique ID. When performing the settings flow, this represents the id in the settings ui's query parameter: http://?flow= @@ -94,6 +96,38 @@ func (o *SettingsFlow) SetActive(v string) { o.Active = &v } +// GetContinueWith returns the ContinueWith field value if set, zero value otherwise. +func (o *SettingsFlow) GetContinueWith() []ContinueWith { + if o == nil || o.ContinueWith == nil { + var ret []ContinueWith + return ret + } + return o.ContinueWith +} + +// GetContinueWithOk returns a tuple with the ContinueWith field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SettingsFlow) GetContinueWithOk() ([]ContinueWith, bool) { + if o == nil || o.ContinueWith == nil { + return nil, false + } + return o.ContinueWith, true +} + +// HasContinueWith returns a boolean if a field has been set. +func (o *SettingsFlow) HasContinueWith() bool { + if o != nil && o.ContinueWith != nil { + return true + } + + return false +} + +// SetContinueWith gets a reference to the given []ContinueWith and assigns it to the ContinueWith field. +func (o *SettingsFlow) SetContinueWith(v []ContinueWith) { + o.ContinueWith = v +} + // GetExpiresAt returns the ExpiresAt field value func (o *SettingsFlow) GetExpiresAt() time.Time { if o == nil { @@ -323,6 +357,9 @@ func (o SettingsFlow) MarshalJSON() ([]byte, error) { if o.Active != nil { toSerialize["active"] = o.Active } + if o.ContinueWith != nil { + toSerialize["continue_with"] = o.ContinueWith + } if true { toSerialize["expires_at"] = o.ExpiresAt } diff --git a/internal/client-go/model_successful_native_registration.go b/internal/client-go/model_successful_native_registration.go index 97eb44834aad..b56cc42bc6e5 100644 --- a/internal/client-go/model_successful_native_registration.go +++ b/internal/client-go/model_successful_native_registration.go @@ -17,8 +17,10 @@ import ( // SuccessfulNativeRegistration The Response for Registration Flows via API type SuccessfulNativeRegistration struct { - Identity Identity `json:"identity"` - Session *Session `json:"session,omitempty"` + // Contains a list of actions, that could follow this flow It can, for example, this will contain a reference to the verification flow, created as part of the user's registration or the token of the session. + ContinueWith []ContinueWith `json:"continue_with,omitempty"` + Identity Identity `json:"identity"` + Session *Session `json:"session,omitempty"` // The Session Token This field is only set when the session hook is configured as a post-registration hook. A session token is equivalent to a session cookie, but it can be sent in the HTTP Authorization Header: Authorization: bearer ${session-token} The session token is only issued for API flows, not for Browser flows! SessionToken *string `json:"session_token,omitempty"` } @@ -41,6 +43,38 @@ func NewSuccessfulNativeRegistrationWithDefaults() *SuccessfulNativeRegistration return &this } +// GetContinueWith returns the ContinueWith field value if set, zero value otherwise. +func (o *SuccessfulNativeRegistration) GetContinueWith() []ContinueWith { + if o == nil || o.ContinueWith == nil { + var ret []ContinueWith + return ret + } + return o.ContinueWith +} + +// GetContinueWithOk returns a tuple with the ContinueWith field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SuccessfulNativeRegistration) GetContinueWithOk() ([]ContinueWith, bool) { + if o == nil || o.ContinueWith == nil { + return nil, false + } + return o.ContinueWith, true +} + +// HasContinueWith returns a boolean if a field has been set. +func (o *SuccessfulNativeRegistration) HasContinueWith() bool { + if o != nil && o.ContinueWith != nil { + return true + } + + return false +} + +// SetContinueWith gets a reference to the given []ContinueWith and assigns it to the ContinueWith field. +func (o *SuccessfulNativeRegistration) SetContinueWith(v []ContinueWith) { + o.ContinueWith = v +} + // GetIdentity returns the Identity field value func (o *SuccessfulNativeRegistration) GetIdentity() Identity { if o == nil { @@ -131,6 +165,9 @@ func (o *SuccessfulNativeRegistration) SetSessionToken(v string) { func (o SuccessfulNativeRegistration) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} + if o.ContinueWith != nil { + toSerialize["continue_with"] = o.ContinueWith + } if true { toSerialize["identity"] = o.Identity } diff --git a/internal/httpclient/.openapi-generator/FILES b/internal/httpclient/.openapi-generator/FILES index d11e26721f68..697267cdad38 100644 --- a/internal/httpclient/.openapi-generator/FILES +++ b/internal/httpclient/.openapi-generator/FILES @@ -10,6 +10,10 @@ api_metadata.go client.go configuration.go docs/AuthenticatorAssuranceLevel.md +docs/ContinueWith.md +docs/ContinueWithSetOrySessionToken.md +docs/ContinueWithVerificationUi.md +docs/ContinueWithVerificationUiFlow.md docs/CourierApi.md docs/CourierMessageStatus.md docs/CourierMessageType.md @@ -114,6 +118,10 @@ git_push.sh go.mod go.sum model_authenticator_assurance_level.go +model_continue_with.go +model_continue_with_set_ory_session_token.go +model_continue_with_verification_ui.go +model_continue_with_verification_ui_flow.go model_courier_message_status.go model_courier_message_type.go model_create_identity_body.go diff --git a/internal/httpclient/README.md b/internal/httpclient/README.md index 25fe3c7e244d..e5d1f5735fd2 100644 --- a/internal/httpclient/README.md +++ b/internal/httpclient/README.md @@ -135,6 +135,10 @@ Class | Method | HTTP request | Description ## Documentation For Models - [AuthenticatorAssuranceLevel](docs/AuthenticatorAssuranceLevel.md) + - [ContinueWith](docs/ContinueWith.md) + - [ContinueWithSetOrySessionToken](docs/ContinueWithSetOrySessionToken.md) + - [ContinueWithVerificationUi](docs/ContinueWithVerificationUi.md) + - [ContinueWithVerificationUiFlow](docs/ContinueWithVerificationUiFlow.md) - [CourierMessageStatus](docs/CourierMessageStatus.md) - [CourierMessageType](docs/CourierMessageType.md) - [CreateIdentityBody](docs/CreateIdentityBody.md) diff --git a/internal/httpclient/model_continue_with.go b/internal/httpclient/model_continue_with.go new file mode 100644 index 000000000000..2cd5dd77542c --- /dev/null +++ b/internal/httpclient/model_continue_with.go @@ -0,0 +1,146 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" + "fmt" +) + +// ContinueWith - struct for ContinueWith +type ContinueWith struct { + ContinueWithSetOrySessionToken *ContinueWithSetOrySessionToken + ContinueWithVerificationUi *ContinueWithVerificationUi +} + +// ContinueWithSetOrySessionTokenAsContinueWith is a convenience function that returns ContinueWithSetOrySessionToken wrapped in ContinueWith +func ContinueWithSetOrySessionTokenAsContinueWith(v *ContinueWithSetOrySessionToken) ContinueWith { + return ContinueWith{ + ContinueWithSetOrySessionToken: v, + } +} + +// ContinueWithVerificationUiAsContinueWith is a convenience function that returns ContinueWithVerificationUi wrapped in ContinueWith +func ContinueWithVerificationUiAsContinueWith(v *ContinueWithVerificationUi) ContinueWith { + return ContinueWith{ + ContinueWithVerificationUi: v, + } +} + +// Unmarshal JSON data into one of the pointers in the struct +func (dst *ContinueWith) UnmarshalJSON(data []byte) error { + var err error + match := 0 + // try to unmarshal data into ContinueWithSetOrySessionToken + err = newStrictDecoder(data).Decode(&dst.ContinueWithSetOrySessionToken) + if err == nil { + jsonContinueWithSetOrySessionToken, _ := json.Marshal(dst.ContinueWithSetOrySessionToken) + if string(jsonContinueWithSetOrySessionToken) == "{}" { // empty struct + dst.ContinueWithSetOrySessionToken = nil + } else { + match++ + } + } else { + dst.ContinueWithSetOrySessionToken = nil + } + + // try to unmarshal data into ContinueWithVerificationUi + err = newStrictDecoder(data).Decode(&dst.ContinueWithVerificationUi) + if err == nil { + jsonContinueWithVerificationUi, _ := json.Marshal(dst.ContinueWithVerificationUi) + if string(jsonContinueWithVerificationUi) == "{}" { // empty struct + dst.ContinueWithVerificationUi = nil + } else { + match++ + } + } else { + dst.ContinueWithVerificationUi = nil + } + + if match > 1 { // more than 1 match + // reset to nil + dst.ContinueWithSetOrySessionToken = nil + dst.ContinueWithVerificationUi = nil + + return fmt.Errorf("Data matches more than one schema in oneOf(ContinueWith)") + } else if match == 1 { + return nil // exactly one match + } else { // no match + return fmt.Errorf("Data failed to match schemas in oneOf(ContinueWith)") + } +} + +// Marshal data from the first non-nil pointers in the struct to JSON +func (src ContinueWith) MarshalJSON() ([]byte, error) { + if src.ContinueWithSetOrySessionToken != nil { + return json.Marshal(&src.ContinueWithSetOrySessionToken) + } + + if src.ContinueWithVerificationUi != nil { + return json.Marshal(&src.ContinueWithVerificationUi) + } + + return nil, nil // no data in oneOf schemas +} + +// Get the actual instance +func (obj *ContinueWith) GetActualInstance() interface{} { + if obj == nil { + return nil + } + if obj.ContinueWithSetOrySessionToken != nil { + return obj.ContinueWithSetOrySessionToken + } + + if obj.ContinueWithVerificationUi != nil { + return obj.ContinueWithVerificationUi + } + + // all schemas are nil + return nil +} + +type NullableContinueWith struct { + value *ContinueWith + isSet bool +} + +func (v NullableContinueWith) Get() *ContinueWith { + return v.value +} + +func (v *NullableContinueWith) Set(val *ContinueWith) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWith) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWith) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWith(val *ContinueWith) *NullableContinueWith { + return &NullableContinueWith{value: val, isSet: true} +} + +func (v NullableContinueWith) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWith) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/httpclient/model_continue_with_set_ory_session_token.go b/internal/httpclient/model_continue_with_set_ory_session_token.go new file mode 100644 index 000000000000..2ea664a546a7 --- /dev/null +++ b/internal/httpclient/model_continue_with_set_ory_session_token.go @@ -0,0 +1,138 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// ContinueWithSetOrySessionToken Indicates that a session was issued, and the application should use this token for authenticated requests +type ContinueWithSetOrySessionToken struct { + // Action will always be `set_ory_session_token` set_ory_session_token ContinueWithActionSetOrySessionToken verification_ui ContinueWithActionVerificationUI + Action string `json:"action"` + // Token is the token of the session + OrySessionToken string `json:"ory_session_token"` +} + +// NewContinueWithSetOrySessionToken instantiates a new ContinueWithSetOrySessionToken object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewContinueWithSetOrySessionToken(action string, orySessionToken string) *ContinueWithSetOrySessionToken { + this := ContinueWithSetOrySessionToken{} + this.Action = action + this.OrySessionToken = orySessionToken + return &this +} + +// NewContinueWithSetOrySessionTokenWithDefaults instantiates a new ContinueWithSetOrySessionToken object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewContinueWithSetOrySessionTokenWithDefaults() *ContinueWithSetOrySessionToken { + this := ContinueWithSetOrySessionToken{} + return &this +} + +// GetAction returns the Action field value +func (o *ContinueWithSetOrySessionToken) GetAction() string { + if o == nil { + var ret string + return ret + } + + return o.Action +} + +// GetActionOk returns a tuple with the Action field value +// and a boolean to check if the value has been set. +func (o *ContinueWithSetOrySessionToken) GetActionOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Action, true +} + +// SetAction sets field value +func (o *ContinueWithSetOrySessionToken) SetAction(v string) { + o.Action = v +} + +// GetOrySessionToken returns the OrySessionToken field value +func (o *ContinueWithSetOrySessionToken) GetOrySessionToken() string { + if o == nil { + var ret string + return ret + } + + return o.OrySessionToken +} + +// GetOrySessionTokenOk returns a tuple with the OrySessionToken field value +// and a boolean to check if the value has been set. +func (o *ContinueWithSetOrySessionToken) GetOrySessionTokenOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.OrySessionToken, true +} + +// SetOrySessionToken sets field value +func (o *ContinueWithSetOrySessionToken) SetOrySessionToken(v string) { + o.OrySessionToken = v +} + +func (o ContinueWithSetOrySessionToken) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if true { + toSerialize["action"] = o.Action + } + if true { + toSerialize["ory_session_token"] = o.OrySessionToken + } + return json.Marshal(toSerialize) +} + +type NullableContinueWithSetOrySessionToken struct { + value *ContinueWithSetOrySessionToken + isSet bool +} + +func (v NullableContinueWithSetOrySessionToken) Get() *ContinueWithSetOrySessionToken { + return v.value +} + +func (v *NullableContinueWithSetOrySessionToken) Set(val *ContinueWithSetOrySessionToken) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWithSetOrySessionToken) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWithSetOrySessionToken) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWithSetOrySessionToken(val *ContinueWithSetOrySessionToken) *NullableContinueWithSetOrySessionToken { + return &NullableContinueWithSetOrySessionToken{value: val, isSet: true} +} + +func (v NullableContinueWithSetOrySessionToken) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWithSetOrySessionToken) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/httpclient/model_continue_with_verification_ui.go b/internal/httpclient/model_continue_with_verification_ui.go new file mode 100644 index 000000000000..85cd7c0ef30a --- /dev/null +++ b/internal/httpclient/model_continue_with_verification_ui.go @@ -0,0 +1,137 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// ContinueWithVerificationUi Indicates, that the UI flow could be continued by showing a verification ui +type ContinueWithVerificationUi struct { + // Action will always be `verification_ui` set_ory_session_token ContinueWithActionSetOrySessionToken verification_ui ContinueWithActionVerificationUI + Action string `json:"action"` + Flow ContinueWithVerificationUiFlow `json:"flow"` +} + +// NewContinueWithVerificationUi instantiates a new ContinueWithVerificationUi object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewContinueWithVerificationUi(action string, flow ContinueWithVerificationUiFlow) *ContinueWithVerificationUi { + this := ContinueWithVerificationUi{} + this.Action = action + this.Flow = flow + return &this +} + +// NewContinueWithVerificationUiWithDefaults instantiates a new ContinueWithVerificationUi object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewContinueWithVerificationUiWithDefaults() *ContinueWithVerificationUi { + this := ContinueWithVerificationUi{} + return &this +} + +// GetAction returns the Action field value +func (o *ContinueWithVerificationUi) GetAction() string { + if o == nil { + var ret string + return ret + } + + return o.Action +} + +// GetActionOk returns a tuple with the Action field value +// and a boolean to check if the value has been set. +func (o *ContinueWithVerificationUi) GetActionOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Action, true +} + +// SetAction sets field value +func (o *ContinueWithVerificationUi) SetAction(v string) { + o.Action = v +} + +// GetFlow returns the Flow field value +func (o *ContinueWithVerificationUi) GetFlow() ContinueWithVerificationUiFlow { + if o == nil { + var ret ContinueWithVerificationUiFlow + return ret + } + + return o.Flow +} + +// GetFlowOk returns a tuple with the Flow field value +// and a boolean to check if the value has been set. +func (o *ContinueWithVerificationUi) GetFlowOk() (*ContinueWithVerificationUiFlow, bool) { + if o == nil { + return nil, false + } + return &o.Flow, true +} + +// SetFlow sets field value +func (o *ContinueWithVerificationUi) SetFlow(v ContinueWithVerificationUiFlow) { + o.Flow = v +} + +func (o ContinueWithVerificationUi) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if true { + toSerialize["action"] = o.Action + } + if true { + toSerialize["flow"] = o.Flow + } + return json.Marshal(toSerialize) +} + +type NullableContinueWithVerificationUi struct { + value *ContinueWithVerificationUi + isSet bool +} + +func (v NullableContinueWithVerificationUi) Get() *ContinueWithVerificationUi { + return v.value +} + +func (v *NullableContinueWithVerificationUi) Set(val *ContinueWithVerificationUi) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWithVerificationUi) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWithVerificationUi) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWithVerificationUi(val *ContinueWithVerificationUi) *NullableContinueWithVerificationUi { + return &NullableContinueWithVerificationUi{value: val, isSet: true} +} + +func (v NullableContinueWithVerificationUi) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWithVerificationUi) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/httpclient/model_continue_with_verification_ui_flow.go b/internal/httpclient/model_continue_with_verification_ui_flow.go new file mode 100644 index 000000000000..8fdd4609cf93 --- /dev/null +++ b/internal/httpclient/model_continue_with_verification_ui_flow.go @@ -0,0 +1,175 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// ContinueWithVerificationUiFlow struct for ContinueWithVerificationUiFlow +type ContinueWithVerificationUiFlow struct { + // The ID of the verification flow + Id string `json:"id"` + // The URL of the verification flow + Url *string `json:"url,omitempty"` + // The address that should be verified in this flow + VerifiableAddress string `json:"verifiable_address"` +} + +// NewContinueWithVerificationUiFlow instantiates a new ContinueWithVerificationUiFlow object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewContinueWithVerificationUiFlow(id string, verifiableAddress string) *ContinueWithVerificationUiFlow { + this := ContinueWithVerificationUiFlow{} + this.Id = id + this.VerifiableAddress = verifiableAddress + return &this +} + +// NewContinueWithVerificationUiFlowWithDefaults instantiates a new ContinueWithVerificationUiFlow object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewContinueWithVerificationUiFlowWithDefaults() *ContinueWithVerificationUiFlow { + this := ContinueWithVerificationUiFlow{} + return &this +} + +// GetId returns the Id field value +func (o *ContinueWithVerificationUiFlow) GetId() string { + if o == nil { + var ret string + return ret + } + + return o.Id +} + +// GetIdOk returns a tuple with the Id field value +// and a boolean to check if the value has been set. +func (o *ContinueWithVerificationUiFlow) GetIdOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Id, true +} + +// SetId sets field value +func (o *ContinueWithVerificationUiFlow) SetId(v string) { + o.Id = v +} + +// GetUrl returns the Url field value if set, zero value otherwise. +func (o *ContinueWithVerificationUiFlow) GetUrl() string { + if o == nil || o.Url == nil { + var ret string + return ret + } + return *o.Url +} + +// GetUrlOk returns a tuple with the Url field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *ContinueWithVerificationUiFlow) GetUrlOk() (*string, bool) { + if o == nil || o.Url == nil { + return nil, false + } + return o.Url, true +} + +// HasUrl returns a boolean if a field has been set. +func (o *ContinueWithVerificationUiFlow) HasUrl() bool { + if o != nil && o.Url != nil { + return true + } + + return false +} + +// SetUrl gets a reference to the given string and assigns it to the Url field. +func (o *ContinueWithVerificationUiFlow) SetUrl(v string) { + o.Url = &v +} + +// GetVerifiableAddress returns the VerifiableAddress field value +func (o *ContinueWithVerificationUiFlow) GetVerifiableAddress() string { + if o == nil { + var ret string + return ret + } + + return o.VerifiableAddress +} + +// GetVerifiableAddressOk returns a tuple with the VerifiableAddress field value +// and a boolean to check if the value has been set. +func (o *ContinueWithVerificationUiFlow) GetVerifiableAddressOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.VerifiableAddress, true +} + +// SetVerifiableAddress sets field value +func (o *ContinueWithVerificationUiFlow) SetVerifiableAddress(v string) { + o.VerifiableAddress = v +} + +func (o ContinueWithVerificationUiFlow) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if true { + toSerialize["id"] = o.Id + } + if o.Url != nil { + toSerialize["url"] = o.Url + } + if true { + toSerialize["verifiable_address"] = o.VerifiableAddress + } + return json.Marshal(toSerialize) +} + +type NullableContinueWithVerificationUiFlow struct { + value *ContinueWithVerificationUiFlow + isSet bool +} + +func (v NullableContinueWithVerificationUiFlow) Get() *ContinueWithVerificationUiFlow { + return v.value +} + +func (v *NullableContinueWithVerificationUiFlow) Set(val *ContinueWithVerificationUiFlow) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWithVerificationUiFlow) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWithVerificationUiFlow) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWithVerificationUiFlow(val *ContinueWithVerificationUiFlow) *NullableContinueWithVerificationUiFlow { + return &NullableContinueWithVerificationUiFlow{value: val, isSet: true} +} + +func (v NullableContinueWithVerificationUiFlow) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWithVerificationUiFlow) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/httpclient/model_settings_flow.go b/internal/httpclient/model_settings_flow.go index 34bc9d1b69f0..a1dc0aa98dc6 100644 --- a/internal/httpclient/model_settings_flow.go +++ b/internal/httpclient/model_settings_flow.go @@ -20,6 +20,8 @@ import ( type SettingsFlow struct { // Active, if set, contains the registration method that is being used. It is initially not set. Active *string `json:"active,omitempty"` + // Contains a list of actions, that could follow this flow It can, for example, contain a reference to the verification flow, created as part of the user's registration. + ContinueWith []ContinueWith `json:"continue_with,omitempty"` // ExpiresAt is the time (UTC) when the flow expires. If the user still wishes to update the setting, a new flow has to be initiated. ExpiresAt time.Time `json:"expires_at"` // ID represents the flow's unique ID. When performing the settings flow, this represents the id in the settings ui's query parameter: http://?flow= @@ -94,6 +96,38 @@ func (o *SettingsFlow) SetActive(v string) { o.Active = &v } +// GetContinueWith returns the ContinueWith field value if set, zero value otherwise. +func (o *SettingsFlow) GetContinueWith() []ContinueWith { + if o == nil || o.ContinueWith == nil { + var ret []ContinueWith + return ret + } + return o.ContinueWith +} + +// GetContinueWithOk returns a tuple with the ContinueWith field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SettingsFlow) GetContinueWithOk() ([]ContinueWith, bool) { + if o == nil || o.ContinueWith == nil { + return nil, false + } + return o.ContinueWith, true +} + +// HasContinueWith returns a boolean if a field has been set. +func (o *SettingsFlow) HasContinueWith() bool { + if o != nil && o.ContinueWith != nil { + return true + } + + return false +} + +// SetContinueWith gets a reference to the given []ContinueWith and assigns it to the ContinueWith field. +func (o *SettingsFlow) SetContinueWith(v []ContinueWith) { + o.ContinueWith = v +} + // GetExpiresAt returns the ExpiresAt field value func (o *SettingsFlow) GetExpiresAt() time.Time { if o == nil { @@ -323,6 +357,9 @@ func (o SettingsFlow) MarshalJSON() ([]byte, error) { if o.Active != nil { toSerialize["active"] = o.Active } + if o.ContinueWith != nil { + toSerialize["continue_with"] = o.ContinueWith + } if true { toSerialize["expires_at"] = o.ExpiresAt } diff --git a/internal/httpclient/model_successful_native_registration.go b/internal/httpclient/model_successful_native_registration.go index 97eb44834aad..b56cc42bc6e5 100644 --- a/internal/httpclient/model_successful_native_registration.go +++ b/internal/httpclient/model_successful_native_registration.go @@ -17,8 +17,10 @@ import ( // SuccessfulNativeRegistration The Response for Registration Flows via API type SuccessfulNativeRegistration struct { - Identity Identity `json:"identity"` - Session *Session `json:"session,omitempty"` + // Contains a list of actions, that could follow this flow It can, for example, this will contain a reference to the verification flow, created as part of the user's registration or the token of the session. + ContinueWith []ContinueWith `json:"continue_with,omitempty"` + Identity Identity `json:"identity"` + Session *Session `json:"session,omitempty"` // The Session Token This field is only set when the session hook is configured as a post-registration hook. A session token is equivalent to a session cookie, but it can be sent in the HTTP Authorization Header: Authorization: bearer ${session-token} The session token is only issued for API flows, not for Browser flows! SessionToken *string `json:"session_token,omitempty"` } @@ -41,6 +43,38 @@ func NewSuccessfulNativeRegistrationWithDefaults() *SuccessfulNativeRegistration return &this } +// GetContinueWith returns the ContinueWith field value if set, zero value otherwise. +func (o *SuccessfulNativeRegistration) GetContinueWith() []ContinueWith { + if o == nil || o.ContinueWith == nil { + var ret []ContinueWith + return ret + } + return o.ContinueWith +} + +// GetContinueWithOk returns a tuple with the ContinueWith field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SuccessfulNativeRegistration) GetContinueWithOk() ([]ContinueWith, bool) { + if o == nil || o.ContinueWith == nil { + return nil, false + } + return o.ContinueWith, true +} + +// HasContinueWith returns a boolean if a field has been set. +func (o *SuccessfulNativeRegistration) HasContinueWith() bool { + if o != nil && o.ContinueWith != nil { + return true + } + + return false +} + +// SetContinueWith gets a reference to the given []ContinueWith and assigns it to the ContinueWith field. +func (o *SuccessfulNativeRegistration) SetContinueWith(v []ContinueWith) { + o.ContinueWith = v +} + // GetIdentity returns the Identity field value func (o *SuccessfulNativeRegistration) GetIdentity() Identity { if o == nil { @@ -131,6 +165,9 @@ func (o *SuccessfulNativeRegistration) SetSessionToken(v string) { func (o SuccessfulNativeRegistration) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} + if o.ContinueWith != nil { + toSerialize["continue_with"] = o.ContinueWith + } if true { toSerialize["identity"] = o.Identity } diff --git a/internal/testhelpers/config.go b/internal/testhelpers/config.go index c80de7e9bccb..c5b87065ab07 100644 --- a/internal/testhelpers/config.go +++ b/internal/testhelpers/config.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ory/x/configx" + "github.com/ory/x/randx" ) func UseConfigFile(t *testing.T, path string) *pflag.FlagSet { @@ -30,6 +31,23 @@ func SetDefaultIdentitySchema(conf *config.Config, url string) { }) } +// UseIdentitySchema registered an identity schema in the config with a random ID and returns the ID +// +// It also registered a test cleanup function, to reset the schemas to the original values, after the test finishes +func UseIdentitySchema(t *testing.T, conf *config.Config, url string) (id string) { + id = randx.MustString(16, randx.Alpha) + schemas, err := conf.IdentityTraitsSchemas(context.Background()) + require.NoError(t, err) + conf.MustSet(context.Background(), config.ViperKeyIdentitySchemas, append(schemas, config.Schema{ + ID: id, + URL: url, + })) + t.Cleanup(func() { + conf.MustSet(context.Background(), config.ViperKeyIdentitySchemas, schemas) + }) + return id +} + // SetDefaultIdentitySchemaFromRaw allows setting the default identity schema from a raw JSON string. func SetDefaultIdentitySchemaFromRaw(conf *config.Config, schema []byte) { conf.MustSet(context.Background(), config.ViperKeyDefaultIdentitySchemaID, "default") diff --git a/internal/testhelpers/selfservice_settings.go b/internal/testhelpers/selfservice_settings.go index aea2a8b8b3db..37a0e97ac472 100644 --- a/internal/testhelpers/selfservice_settings.go +++ b/internal/testhelpers/selfservice_settings.go @@ -45,6 +45,7 @@ func NewSettingsUIFlowEchoServer(t *testing.T, reg driver.Registry) *httptest.Se } func InitializeSettingsFlowViaBrowser(t *testing.T, client *http.Client, isSPA bool, ts *httptest.Server) *kratos.SettingsFlow { + t.Helper() publicClient := NewSDKCustomClient(ts, client) req, err := http.NewRequest("GET", ts.URL+settings.RouteInitBrowserFlow, nil) @@ -66,9 +67,9 @@ func InitializeSettingsFlowViaBrowser(t *testing.T, client *http.Client, isSPA b require.NoError(t, res.Body.Close()) - rs, _, err := publicClient.FrontendApi.GetSettingsFlow(context.Background()). + rs, res, err := publicClient.FrontendApi.GetSettingsFlow(context.Background()). Id(flowID).Execute() - require.NoError(t, err) + require.NoError(t, err, "%s", ioutilx.MustReadAll(res.Body)) assert.Empty(t, rs.Active) return rs diff --git a/internal/testhelpers/snapshot.go b/internal/testhelpers/snapshot.go index 847ed758187a..152ee0d47ce7 100644 --- a/internal/testhelpers/snapshot.go +++ b/internal/testhelpers/snapshot.go @@ -13,6 +13,7 @@ import ( ) func SnapshotTExcept(t *testing.T, actual interface{}, except []string) { + t.Helper() compare, err := json.MarshalIndent(actual, "", " ") require.NoError(t, err, "%+v", actual) for _, e := range except { diff --git a/selfservice/flow/continue_with.go b/selfservice/flow/continue_with.go new file mode 100644 index 000000000000..b0bfbd6046af --- /dev/null +++ b/selfservice/flow/continue_with.go @@ -0,0 +1,108 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package flow + +import ( + "net/url" + + "github.com/gofrs/uuid" + + "github.com/ory/x/urlx" +) + +// swagger:model continueWith +type ContinueWith any + +// swagger:enum ContinueWithAction +type ContinueWithAction string + +// #nosec G101 -- only a key constant +const ( + ContinueWithActionSetOrySessionToken ContinueWithAction = "set_ory_session_token" + ContinueWithActionVerificationUI ContinueWithAction = "verification_ui" +) + +var _ ContinueWith = new(ContinueWithSetToken) + +// Indicates that a session was issued, and the application should use this token for authenticated requests +// swagger:model continueWithSetOrySessionToken +type ContinueWithSetToken struct { + // Action will always be `set_ory_session_token` + // + // required: true + Action ContinueWithAction `json:"action"` + + // Token is the token of the session + // + // required: true + OrySessionToken string `json:"ory_session_token"` +} + +func (ContinueWithSetToken) AppendTo(url.Values) url.Values { + return nil +} + +func NewContinueWithSetToken(t string) *ContinueWithSetToken { + return &ContinueWithSetToken{ + Action: ContinueWithActionSetOrySessionToken, + OrySessionToken: t, + } +} + +var _ ContinueWith = new(ContinueWithVerificationUI) + +// Indicates, that the UI flow could be continued by showing a verification ui +// +// swagger:model continueWithVerificationUi +type ContinueWithVerificationUI struct { + // Action will always be `verification_ui` + // + // required: true + Action ContinueWithAction `json:"action"` + // Flow contains the ID of the verification flow + // + // required: true + Flow ContinueWithVerificationUIFlow `json:"flow"` +} + +// swagger:model continueWithVerificationUiFlow +type ContinueWithVerificationUIFlow struct { + // The ID of the verification flow + // + // required: true + ID uuid.UUID `json:"id"` + + // The address that should be verified in this flow + // + // required: true + VerifiableAddress string `json:"verifiable_address"` + + // The URL of the verification flow + // + // required: false + URL string `json:"url,omitempty"` +} + +func NewContinueWithVerificationUI(f Flow, address, url string) *ContinueWithVerificationUI { + return &ContinueWithVerificationUI{ + Action: ContinueWithActionVerificationUI, + Flow: ContinueWithVerificationUIFlow{ + ID: f.GetID(), + VerifiableAddress: address, + URL: url, + }, + } +} + +func (c ContinueWithVerificationUI) AppendTo(src *url.URL) *url.URL { + values := src.Query() + values.Set("flow", c.Flow.ID.String()) + return urlx.CopyWithQuery(src, values) +} + +type FlowWithContinueWith interface { + Flow + AddContinueWith(ContinueWith) + ContinueWith() []ContinueWith +} diff --git a/selfservice/flow/registration/flow.go b/selfservice/flow/registration/flow.go index 3be4dcbe6e4e..376dbdfd0f9b 100644 --- a/selfservice/flow/registration/flow.go +++ b/selfservice/flow/registration/flow.go @@ -97,10 +97,16 @@ type Flow struct { // CSRFToken contains the anti-csrf token associated with this flow. Only set for browser flows. CSRFToken string `json:"-" db:"csrf_token"` - NID uuid.UUID `json:"-" faker:"-" db:"nid"` + NID uuid.UUID `json:"-" faker:"-" db:"nid"` // TransientPayload is used to pass data from the registration to a webhook - TransientPayload json.RawMessage `json:"transient_payload,omitempty" faker:"-" db:"-"` + TransientPayload json.RawMessage `json:"transient_payload,omitempty" faker:"-" db:"-"` + + // Contains a list of actions, that could follow this flow + // + // It can, for example, contain a reference to the verification flow, created as part of the user's + // registration. + ContinueWithItems []flow.ContinueWith `json:"-" db:"-" faker:"-" ` } func NewFlow(conf *config.Config, exp time.Duration, csrf string, r *http.Request, ft flow.Type) (*Flow, error) { @@ -206,3 +212,11 @@ func (f *Flow) AfterSave(*pop.Connection) error { func (f *Flow) GetUI() *container.Container { return f.UI } + +func (f *Flow) AddContinueWith(c flow.ContinueWith) { + f.ContinueWithItems = append(f.ContinueWithItems, c) +} + +func (f *Flow) ContinueWith() []flow.ContinueWith { + return f.ContinueWithItems +} diff --git a/selfservice/flow/registration/hook.go b/selfservice/flow/registration/hook.go index 1eb4ce2f5976..7700f684e2c4 100644 --- a/selfservice/flow/registration/hook.go +++ b/selfservice/flow/registration/hook.go @@ -219,7 +219,10 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque Debug("Post registration execution hooks completed successfully.") if a.Type == flow.TypeAPI || x.IsJSONRequest(r) { - e.d.Writer().Write(w, r, &APIFlowResponse{Identity: i}) + e.d.Writer().Write(w, r, &APIFlowResponse{ + Identity: i, + ContinueWith: a.ContinueWith(), + }) return nil } diff --git a/selfservice/flow/registration/hook_test.go b/selfservice/flow/registration/hook_test.go index 5a95188a5d37..88cdfddc8d77 100644 --- a/selfservice/flow/registration/hook_test.go +++ b/selfservice/flow/registration/hook_test.go @@ -22,6 +22,7 @@ import ( "github.com/ory/kratos/internal/testhelpers" "github.com/ory/kratos/selfservice/flow" "github.com/ory/kratos/selfservice/flow/registration" + "github.com/ory/kratos/selfservice/hook" "github.com/ory/kratos/x" ) @@ -159,6 +160,44 @@ func TestRegistrationExecutor(t *testing.T) { assert.Empty(t, gjson.Get(body, "session.token")) assert.Empty(t, gjson.Get(body, "session_token")) }) + + t.Run("case=should return flow id as part of return to", func(t *testing.T) { + verificationTS := testhelpers.NewVerificationUIFlowEchoServer(t, reg) + t.Cleanup(testhelpers.SelfServiceHookConfigReset(t, conf)) + conf.Set(ctx, config.ViperKeySelfServiceVerificationEnabled, true) + conf.Set(ctx, config.ViperKeySelfServiceRegistrationAfter+".hooks", []map[string]interface{}{ + { + "hook": hook.KeyVerificationUI, + }, + }) + i := testhelpers.SelfServiceHookFakeIdentity(t) + i.Traits = identity.Traits(`{"email": "verifiable@ory.sh"}`) + + res, _ := makeRequestPost(t, newServer(t, i, flow.TypeBrowser), false, url.Values{}) + assert.EqualValues(t, http.StatusOK, res.StatusCode) + assert.Contains(t, res.Request.URL.String(), verificationTS.URL) + assert.NotEmpty(t, res.Request.URL.Query().Get("flow")) + }) + + t.Run("case=should return flow ids as part of return to for multiple verifiable email addresses", func(t *testing.T) { + verificationTS := testhelpers.NewVerificationUIFlowEchoServer(t, reg) + t.Cleanup(testhelpers.SelfServiceHookConfigReset(t, conf)) + conf.Set(ctx, config.ViperKeySelfServiceVerificationEnabled, true) + conf.Set(ctx, config.ViperKeySelfServiceRegistrationAfter+".hooks", []map[string]interface{}{ + { + "hook": hook.KeyVerificationUI, + }, + }) + + i := testhelpers.SelfServiceHookFakeIdentity(t) + i.SchemaID = testhelpers.UseIdentitySchema(t, conf, "file://./stub/registration-multi-email.schema.json") + i.Traits = identity.Traits(`{"emails": ["one@ory.sh", "two@ory.sh"]}`) + + res, _ := makeRequestPost(t, newServer(t, i, flow.TypeBrowser), false, url.Values{}) + assert.EqualValues(t, http.StatusOK, res.StatusCode) + assert.Contains(t, res.Request.URL.String(), verificationTS.URL) + assert.NotEmpty(t, res.Request.URL.Query().Get("flow")) + }) }) for _, kind := range []flow.Type{flow.TypeBrowser, flow.TypeAPI} { diff --git a/selfservice/flow/registration/session.go b/selfservice/flow/registration/session.go index 77d71b87d4ac..ed7047bd5f6d 100644 --- a/selfservice/flow/registration/session.go +++ b/selfservice/flow/registration/session.go @@ -5,6 +5,7 @@ package registration import ( "github.com/ory/kratos/identity" + "github.com/ory/kratos/selfservice/flow" "github.com/ory/kratos/session" ) @@ -38,4 +39,12 @@ type APIFlowResponse struct { // // required: true Identity *identity.Identity `json:"identity"` + + // Contains a list of actions, that could follow this flow + // + // It can, for example, this will contain a reference to the verification flow, created as part of the user's + // registration or the token of the session. + // + // required: false + ContinueWith []flow.ContinueWith `json:"continue_with"` } diff --git a/selfservice/flow/registration/stub/registration-multi-email.schema.json b/selfservice/flow/registration/stub/registration-multi-email.schema.json new file mode 100644 index 000000000000..442f27836ef4 --- /dev/null +++ b/selfservice/flow/registration/stub/registration-multi-email.schema.json @@ -0,0 +1,29 @@ +{ + "$id": "https://example.com/registration.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Person", + "type": "object", + "properties": { + "traits": { + "type": "object", + "properties": { + "emails": { + "type": "array", + "items": { + "type": "string", + "ory.sh/kratos": { + "credentials": { + "password": { + "identifier": true + } + }, + "verification": { + "via": "email" + } + } + } + } + } + } + } +} diff --git a/selfservice/flow/registration/stub/registration.schema.json b/selfservice/flow/registration/stub/registration.schema.json index c7005d87ce8d..78fcb22c6587 100644 --- a/selfservice/flow/registration/stub/registration.schema.json +++ b/selfservice/flow/registration/stub/registration.schema.json @@ -9,6 +9,19 @@ "properties": { "bar": { "type": "string" + }, + "email": { + "type": "string", + "ory.sh/kratos": { + "credentials": { + "password": { + "identifier": true + } + }, + "verification": { + "via": "email" + } + } } } } diff --git a/selfservice/flow/settings/flow.go b/selfservice/flow/settings/flow.go index c8a6c3df8bd2..ef69a03f042e 100644 --- a/selfservice/flow/settings/flow.go +++ b/selfservice/flow/settings/flow.go @@ -110,7 +110,15 @@ type Flow struct { CreatedAt time.Time `json:"-" faker:"-" db:"created_at"` // UpdatedAt is a helper struct field for gobuffalo.pop. UpdatedAt time.Time `json:"-" faker:"-" db:"updated_at"` - NID uuid.UUID `json:"-" faker:"-" db:"nid"` + NID uuid.UUID `json:"-" faker:"-" db:"nid"` + + // Contains a list of actions, that could follow this flow + // + // It can, for example, contain a reference to the verification flow, created as part of the user's + // registration. + // + // required: false + ContinueWithItems []flow.ContinueWith `json:"continue_with,omitempty" db:"-" faker:"-" ` } func MustNewFlow(conf *config.Config, exp time.Duration, r *http.Request, i *identity.Identity, ft flow.Type) *Flow { @@ -226,3 +234,11 @@ func (f *Flow) AfterSave(*pop.Connection) error { func (f *Flow) GetUI() *container.Container { return f.UI } + +func (f *Flow) AddContinueWith(c flow.ContinueWith) { + f.ContinueWithItems = append(f.ContinueWithItems, c) +} + +func (f *Flow) ContinueWith() []flow.ContinueWith { + return f.ContinueWithItems +} diff --git a/selfservice/flow/settings/hook.go b/selfservice/flow/settings/hook.go index 438c2161bbc9..e07314df8ba1 100644 --- a/selfservice/flow/settings/hook.go +++ b/selfservice/flow/settings/hook.go @@ -283,6 +283,8 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, if err != nil { return err } + // ContinueWith items are transient items, not stored in the database, and need to be carried over here, so they can be returned to the client here. + updatedFlow.ContinueWithItems = ctxUpdate.Flow.ContinueWithItems e.d.Writer().Write(w, r, updatedFlow) return nil @@ -297,6 +299,8 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, if err != nil { return err } + // ContinueWith items are transient items, not stored in the database, and need to be carried over here, so they can be returned to the client here. + updatedFlow.ContinueWithItems = ctxUpdate.Flow.ContinueWithItems e.d.Writer().Write(w, r, updatedFlow) return nil diff --git a/selfservice/flow/settings/hook_test.go b/selfservice/flow/settings/hook_test.go index 6590bffa741d..0ab28a1504f9 100644 --- a/selfservice/flow/settings/hook_test.go +++ b/selfservice/flow/settings/hook_test.go @@ -46,11 +46,14 @@ func TestSettingsExecutor(t *testing.T) { }, }) - newServer := func(t *testing.T, ft flow.Type) *httptest.Server { + newServer := func(t *testing.T, i *identity.Identity, ft flow.Type) *httptest.Server { + t.Helper() router := httprouter.New() handleErr := testhelpers.SelfServiceHookSettingsErrorHandler router.GET("/settings/pre", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - i := testhelpers.SelfServiceHookCreateFakeIdentity(t, reg) + if i == nil { + i = testhelpers.SelfServiceHookCreateFakeIdentity(t, reg) + } sess, _ := session.NewActiveSession(r, i, conf, time.Now().UTC(), identity.CredentialsTypePassword, identity.AuthenticatorAssuranceLevel1) f, err := settings.NewFlow(conf, time.Minute, r, sess.Identity, ft) @@ -61,7 +64,9 @@ func TestSettingsExecutor(t *testing.T) { }) router.GET("/settings/post", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - i := testhelpers.SelfServiceHookCreateFakeIdentity(t, reg) + if i == nil { + i = testhelpers.SelfServiceHookCreateFakeIdentity(t, reg) + } sess, _ := session.NewActiveSession(r, i, conf, time.Now().UTC(), identity.CredentialsTypePassword, identity.AuthenticatorAssuranceLevel1) a, err := settings.NewFlow(conf, time.Minute, r, sess.Identity, ft) @@ -91,7 +96,7 @@ func TestSettingsExecutor(t *testing.T) { t.Run("case=pass without hooks", func(t *testing.T) { t.Cleanup(testhelpers.SelfServiceHookConfigReset(t, conf)) - res, _ := makeRequestPost(t, newServer(t, flow.TypeBrowser), false, url.Values{}) + res, _ := makeRequestPost(t, newServer(t, nil, flow.TypeBrowser), false, url.Values{}) assert.EqualValues(t, http.StatusOK, res.StatusCode) assert.Contains(t, res.Request.URL.String(), uiURL) }) @@ -100,7 +105,7 @@ func TestSettingsExecutor(t *testing.T) { t.Cleanup(testhelpers.SelfServiceHookConfigReset(t, conf)) viperSetPost(strategy, []config.SelfServiceHook{{Name: "err", Config: []byte(`{}`)}}) - res, _ := makeRequestPost(t, newServer(t, flow.TypeBrowser), false, url.Values{}) + res, _ := makeRequestPost(t, newServer(t, nil, flow.TypeBrowser), false, url.Values{}) assert.EqualValues(t, http.StatusOK, res.StatusCode) assert.Contains(t, res.Request.URL.String(), uiURL) }) @@ -109,7 +114,7 @@ func TestSettingsExecutor(t *testing.T) { t.Cleanup(testhelpers.SelfServiceHookConfigReset(t, conf)) viperSetPost(strategy, []config.SelfServiceHook{{Name: "err", Config: []byte(`{"ExecuteSettingsPrePersistHook": "abort"}`)}}) - res, body := makeRequestPost(t, newServer(t, flow.TypeBrowser), false, url.Values{}) + res, body := makeRequestPost(t, newServer(t, nil, flow.TypeBrowser), false, url.Values{}) assert.EqualValues(t, http.StatusOK, res.StatusCode) assert.Equal(t, "", body) }) @@ -119,7 +124,7 @@ func TestSettingsExecutor(t *testing.T) { conf.MustSet(ctx, config.ViperKeyURLsAllowedReturnToDomains, []string{"https://www.ory.sh/"}) testhelpers.SelfServiceHookSettingsSetDefaultRedirectTo(t, conf, "https://www.ory.sh") - res, _ := makeRequestPost(t, newServer(t, flow.TypeBrowser), false, url.Values{"return_to": {"https://www.ory.sh/kratos/"}}) + res, _ := makeRequestPost(t, newServer(t, nil, flow.TypeBrowser), false, url.Values{"return_to": {"https://www.ory.sh/kratos/"}}) assert.EqualValues(t, http.StatusOK, res.StatusCode) assert.EqualValues(t, "https://www.ory.sh/kratos/", res.Request.URL.String()) }) @@ -128,7 +133,7 @@ func TestSettingsExecutor(t *testing.T) { t.Cleanup(testhelpers.SelfServiceHookConfigReset(t, conf)) testhelpers.SelfServiceHookSettingsSetDefaultRedirectTo(t, conf, "https://www.ory.sh/kratos") - res, _ := makeRequestPost(t, newServer(t, flow.TypeBrowser), false, url.Values{}) + res, _ := makeRequestPost(t, newServer(t, nil, flow.TypeBrowser), false, url.Values{}) assert.EqualValues(t, http.StatusOK, res.StatusCode) assert.EqualValues(t, "https://www.ory.sh/kratos/", res.Request.URL.String()) }) @@ -138,7 +143,7 @@ func TestSettingsExecutor(t *testing.T) { testhelpers.SelfServiceHookSettingsSetDefaultRedirectTo(t, conf, "https://www.ory.sh/not-kratos") testhelpers.SelfServiceHookSettingsSetDefaultRedirectToStrategy(t, conf, strategy, "https://www.ory.sh/kratos") - res, _ := makeRequestPost(t, newServer(t, flow.TypeBrowser), false, url.Values{}) + res, _ := makeRequestPost(t, newServer(t, nil, flow.TypeBrowser), false, url.Values{}) assert.EqualValues(t, http.StatusOK, res.StatusCode) assert.EqualValues(t, "https://www.ory.sh/kratos/", res.Request.URL.String()) }) @@ -146,7 +151,7 @@ func TestSettingsExecutor(t *testing.T) { t.Run("case=pass if hooks pass", func(t *testing.T) { t.Cleanup(testhelpers.SelfServiceHookConfigReset(t, conf)) viperSetPost(strategy, []config.SelfServiceHook{{Name: "err", Config: []byte(`{}`)}}) - res, _ := makeRequestPost(t, newServer(t, flow.TypeBrowser), false, url.Values{}) + res, _ := makeRequestPost(t, newServer(t, nil, flow.TypeBrowser), false, url.Values{}) assert.EqualValues(t, http.StatusOK, res.StatusCode) assert.Contains(t, res.Request.URL.String(), uiURL) }) @@ -154,7 +159,7 @@ func TestSettingsExecutor(t *testing.T) { t.Run("case=send a json response for API clients", func(t *testing.T) { t.Cleanup(testhelpers.SelfServiceHookConfigReset(t, conf)) viperSetPost(strategy, nil) - res, body := makeRequestPost(t, newServer(t, flow.TypeAPI), true, url.Values{}) + res, body := makeRequestPost(t, newServer(t, nil, flow.TypeAPI), true, url.Values{}) assert.EqualValues(t, http.StatusOK, res.StatusCode) assert.NotEmpty(t, gjson.Get(body, "identity.id")) }) @@ -162,7 +167,7 @@ func TestSettingsExecutor(t *testing.T) { t.Run("case=pass without hooks for browser flow with application/json", func(t *testing.T) { t.Cleanup(testhelpers.SelfServiceHookConfigReset(t, conf)) - res, body := makeRequestPost(t, newServer(t, flow.TypeBrowser), true, url.Values{}) + res, body := makeRequestPost(t, newServer(t, nil, flow.TypeBrowser), true, url.Values{}) assert.EqualValues(t, http.StatusOK, res.StatusCode) assert.NotEmpty(t, gjson.Get(body, "identity.id")) }) @@ -173,7 +178,7 @@ func TestSettingsExecutor(t *testing.T) { config.ViperKeySelfServiceSettingsBeforeHooks, testhelpers.SelfServiceMakeSettingsPreHookRequest, func(t *testing.T) *httptest.Server { - return newServer(t, kind) + return newServer(t, nil, kind) }, conf, )) diff --git a/selfservice/flow/settings/stub/multi-email.schema.json b/selfservice/flow/settings/stub/multi-email.schema.json new file mode 100644 index 000000000000..442f27836ef4 --- /dev/null +++ b/selfservice/flow/settings/stub/multi-email.schema.json @@ -0,0 +1,29 @@ +{ + "$id": "https://example.com/registration.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Person", + "type": "object", + "properties": { + "traits": { + "type": "object", + "properties": { + "emails": { + "type": "array", + "items": { + "type": "string", + "ory.sh/kratos": { + "credentials": { + "password": { + "identifier": true + } + }, + "verification": { + "via": "email" + } + } + } + } + } + } + } +} diff --git a/selfservice/flow/verification/flow.go b/selfservice/flow/verification/flow.go index f529b1ab61e0..c43ef74bb342 100644 --- a/selfservice/flow/verification/flow.go +++ b/selfservice/flow/verification/flow.go @@ -23,6 +23,8 @@ import ( "github.com/ory/x/urlx" ) +var _ flow.Flow = new(Flow) + // A Verification Flow // // Used to verify an out-of-band communication diff --git a/selfservice/hook/hooks.go b/selfservice/hook/hooks.go index a02795c7ea3a..3eabac19d036 100644 --- a/selfservice/hook/hooks.go +++ b/selfservice/hook/hooks.go @@ -8,4 +8,5 @@ const ( KeySessionDestroyer = "revoke_active_sessions" KeyWebHook = "web_hook" KeyAddressVerifier = "require_verified_address" + KeyVerificationUI = "show_verification_ui" ) diff --git a/selfservice/hook/session_issuer.go b/selfservice/hook/session_issuer.go index 9a46a7e60f91..4aed85284cb8 100644 --- a/selfservice/hook/session_issuer.go +++ b/selfservice/hook/session_issuer.go @@ -52,10 +52,12 @@ func (e *SessionIssuer) executePostRegistrationPostPersistHook(w http.ResponseWr } if a.Type == flow.TypeAPI { + a.AddContinueWith(flow.NewContinueWithSetToken(s.Token)) e.r.Writer().Write(w, r, ®istration.APIFlowResponse{ - Session: s, - Token: s.Token, - Identity: s.Identity, + Session: s, + Token: s.Token, + Identity: s.Identity, + ContinueWith: a.ContinueWithItems, }) return errors.WithStack(registration.ErrHookAbortFlow) } @@ -68,8 +70,9 @@ func (e *SessionIssuer) executePostRegistrationPostPersistHook(w http.ResponseWr // SPA flows additionally send the session if x.IsJSONRequest(r) { e.r.Writer().Write(w, r, ®istration.APIFlowResponse{ - Session: s, - Identity: s.Identity, + Session: s, + Identity: s.Identity, + ContinueWith: a.ContinueWithItems, }) return errors.WithStack(registration.ErrHookAbortFlow) } diff --git a/selfservice/hook/session_issuer_test.go b/selfservice/hook/session_issuer_test.go index 14353a2a9a5c..d11a0fa18413 100644 --- a/selfservice/hook/session_issuer_test.go +++ b/selfservice/hook/session_issuer_test.go @@ -5,7 +5,6 @@ package hook_test import ( "context" - "errors" "net/http" "net/http/httptest" "testing" @@ -44,8 +43,13 @@ func TestSessionIssuer(t *testing.T) { i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) + + f := ®istration.Flow{Type: flow.TypeBrowser} + require.NoError(t, h.ExecutePostRegistrationPostPersistHook(w, &r, - ®istration.Flow{Type: flow.TypeBrowser}, &session.Session{ID: sid, Identity: i, Token: randx.MustString(12, randx.AlphaLowerNum)})) + f, &session.Session{ID: sid, Identity: i, Token: randx.MustString(12, randx.AlphaLowerNum)})) + + require.Empty(t, f.ContinueWithItems) got, err := reg.SessionPersister().GetSession(context.Background(), sid, session.ExpandNothing) require.NoError(t, err) @@ -63,8 +67,14 @@ func TestSessionIssuer(t *testing.T) { f := ®istration.Flow{Type: flow.TypeAPI} require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) + err := h.ExecutePostRegistrationPostPersistHook(w, &http.Request{Header: http.Header{"Accept": {"application/json"}}}, f, s) - require.True(t, errors.Is(err, registration.ErrHookAbortFlow), "%+v", err) + require.ErrorIs(t, err, registration.ErrHookAbortFlow, "%+v", err) + require.Len(t, f.ContinueWithItems, 1) + + st := f.ContinueWithItems[0] + require.IsType(t, &flow.ContinueWithSetToken{}, st) + assert.NotEmpty(t, st.(*flow.ContinueWithSetToken).OrySessionToken) got, err := reg.SessionPersister().GetSession(context.Background(), s.ID, session.ExpandNothing) require.NoError(t, err) @@ -87,7 +97,8 @@ func TestSessionIssuer(t *testing.T) { require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) err := h.ExecutePostRegistrationPostPersistHook(w, &http.Request{Header: http.Header{"Accept": {"application/json"}}}, f, s) - require.True(t, errors.Is(err, registration.ErrHookAbortFlow), "%+v", err) + require.ErrorIs(t, err, registration.ErrHookAbortFlow, "%+v", err) + require.Empty(t, f.ContinueWithItems) got, err := reg.SessionPersister().GetSession(context.Background(), s.ID, session.ExpandNothing) require.NoError(t, err) diff --git a/selfservice/hook/show_verification_ui.go b/selfservice/hook/show_verification_ui.go new file mode 100644 index 000000000000..97dc39490fa5 --- /dev/null +++ b/selfservice/hook/show_verification_ui.go @@ -0,0 +1,67 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package hook + +import ( + "context" + "net/http" + + "github.com/ory/kratos/driver/config" + "github.com/ory/kratos/selfservice/flow" + "github.com/ory/kratos/selfservice/flow/registration" + "github.com/ory/kratos/session" + "github.com/ory/kratos/x" + "github.com/ory/x/otelx" +) + +var ( + _ registration.PostHookPostPersistExecutor = new(SessionIssuer) +) + +type ( + showVerificationUIDependencies interface { + x.WriterProvider + config.Provider + } + + ShowVerfificationUIProvider interface { + HookShowVerificationUI() *ShowVerificationUI + } + + ShowVerificationUI struct { + d showVerificationUIDependencies + } +) + +func NewShowVerificationUI(d showVerificationUIDependencies) *ShowVerificationUI { + return &ShowVerificationUI{d: d} +} + +func (e *ShowVerificationUI) ExecutePostRegistrationPostPersistHook(w http.ResponseWriter, r *http.Request, f *registration.Flow, s *session.Session) error { + return otelx.WithSpan(r.Context(), "selfservice.hook.SessionIssuer.ExecutePostRegistrationPostPersistHook", func(ctx context.Context) error { + return e.execute(w, r.WithContext(ctx), f, s) + }) +} + +func (e *ShowVerificationUI) execute(w http.ResponseWriter, r *http.Request, f *registration.Flow, s *session.Session) error { + if !x.IsBrowserRequest(r) { + // this hook is only intended to be used by browsers, as it redirects to the verification ui + // JSON API clients should use the `continue_with` field to continue the flow + return nil + } + + var vf *flow.ContinueWithVerificationUI + for _, c := range f.ContinueWithItems { + if item, ok := c.(*flow.ContinueWithVerificationUI); ok { + vf = item + } + } + + if vf != nil { + redir := e.d.Config().SelfServiceFlowVerificationUI(r.Context()) + x.ContentNegotiationRedirection(w, r, s.Declassified(), e.d.Writer(), vf.AppendTo(redir).String()) + } + + return nil +} diff --git a/selfservice/hook/verification.go b/selfservice/hook/verification.go index be5458fb7311..0c0f649bfb6d 100644 --- a/selfservice/hook/verification.go +++ b/selfservice/hook/verification.go @@ -24,9 +24,11 @@ var _ settings.PostHookPostPersistExecutor = new(Verifier) type ( verifierDependencies interface { config.Provider - x.CSRFTokenGeneratorProvider + x.CSRFProvider verification.StrategyProvider verification.FlowPersistenceProvider + identity.PrivilegedPoolProvider + x.WriterProvider } Verifier struct { r verifierDependencies @@ -37,19 +39,19 @@ func NewVerifier(r verifierDependencies) *Verifier { return &Verifier{r: r} } -func (e *Verifier) ExecutePostRegistrationPostPersistHook(_ http.ResponseWriter, r *http.Request, f *registration.Flow, s *session.Session) error { +func (e *Verifier) ExecutePostRegistrationPostPersistHook(w http.ResponseWriter, r *http.Request, f *registration.Flow, s *session.Session) error { return otelx.WithSpan(r.Context(), "selfservice.hook.Verifier.ExecutePostRegistrationPostPersistHook", func(ctx context.Context) error { - return e.do(r.WithContext(ctx), s.Identity, f) + return e.do(w, r.WithContext(ctx), s.Identity, f) }) } func (e *Verifier) ExecuteSettingsPostPersistHook(w http.ResponseWriter, r *http.Request, a *settings.Flow, i *identity.Identity) error { return otelx.WithSpan(r.Context(), "selfservice.hook.Verifier.ExecuteSettingsPostPersistHook", func(ctx context.Context) error { - return e.do(r.WithContext(ctx), i, a) + return e.do(w, r.WithContext(ctx), i, a) }) } -func (e *Verifier) do(r *http.Request, i *identity.Identity, f flow.Flow) error { +func (e *Verifier) do(w http.ResponseWriter, r *http.Request, i *identity.Identity, f flow.FlowWithContinueWith) error { // This is called after the identity has been created so we can safely assume that all addresses are available // already. @@ -63,15 +65,23 @@ func (e *Verifier) do(r *http.Request, i *identity.Identity, f flow.Flow) error if address.Status != identity.VerifiableAddressStatusPending { continue } + var csrf string + if f.GetType() == flow.TypeBrowser { + csrf = e.r.CSRFHandler().RegenerateToken(w, r) + } verificationFlow, err := verification.NewPostHookFlow(e.r.Config(), e.r.Config().SelfServiceFlowVerificationRequestLifespan(r.Context()), - e.r.GenerateCSRFToken(r), r, strategy, f) + csrf, r, strategy, f) if err != nil { return err } verificationFlow.State = verification.StateEmailSent + if err := strategy.PopulateVerificationMethod(r, verificationFlow); err != nil { + return err + } + if err := e.r.VerificationFlowPersister().CreateVerificationFlow(r.Context(), verificationFlow); err != nil { return err } @@ -80,6 +90,16 @@ func (e *Verifier) do(r *http.Request, i *identity.Identity, f flow.Flow) error return err } + flowURL := "" + if verificationFlow.Type == flow.TypeBrowser { + flowURL = verificationFlow.AppendTo(e.r.Config().SelfServiceFlowVerificationUI(r.Context())).String() + } + + f.AddContinueWith(flow.NewContinueWithVerificationUI(verificationFlow, address.Value, flowURL)) + + if err := e.r.PrivilegedIdentityPool().UpdateVerifiableAddress(r.Context(), address); err != nil { + return err + } } return nil } diff --git a/selfservice/hook/verification_test.go b/selfservice/hook/verification_test.go index db1f7c536ffa..07d3a199dd92 100644 --- a/selfservice/hook/verification_test.go +++ b/selfservice/hook/verification_test.go @@ -10,9 +10,8 @@ import ( "testing" "time" - "github.com/ory/kratos/internal/testhelpers" - "github.com/ory/kratos/courier" + "github.com/ory/kratos/internal/testhelpers" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -75,7 +74,7 @@ func TestVerifier(t *testing.T) { i, err = reg.IdentityPool().GetIdentity(context.Background(), i.ID, identity.ExpandDefault) require.NoError(t, err) - var originalFlow flow.Flow + var originalFlow flow.FlowWithContinueWith switch k { case "settings": originalFlow = &settings.Flow{RequestURL: "http://foo.com/settings?after_verification_return_to=verification_callback"} @@ -87,10 +86,13 @@ func TestVerifier(t *testing.T) { h := hook.NewVerifier(reg) require.NoError(t, hf(h, i, originalFlow)) - s, err := reg.GetActiveVerificationStrategy(ctx) - require.NoError(t, err) - expectedVerificationFlow, err := verification.NewPostHookFlow(conf, conf.SelfServiceFlowVerificationRequestLifespan(ctx), "", u, s, originalFlow) - require.NoError(t, err) + assert.Lenf(t, originalFlow.ContinueWith(), 2, "%#ßv", originalFlow.ContinueWith()) + assertContinueWithAddresses(t, originalFlow.ContinueWith(), []string{"foo@ory.sh", "bar@ory.sh"}) + vf := originalFlow.ContinueWith()[0] + assert.IsType(t, &flow.ContinueWithVerificationUI{}, vf) + fView := vf.(*flow.ContinueWithVerificationUI).Flow + + expectedVerificationFlow, err := reg.VerificationFlowPersister().GetVerificationFlow(ctx, fView.ID) var verificationFlow verification.Flow require.NoError(t, reg.Persister().GetConnection(context.Background()).First(&verificationFlow)) @@ -108,8 +110,8 @@ func TestVerifier(t *testing.T) { assert.Contains(t, recipients, "foo@ory.sh") assert.Contains(t, recipients, "bar@ory.sh") - assert.NotContains(t, recipients, "baz@ory.sh") // Email to baz@ory.sh is skipped because it is verified already. + assert.NotContains(t, recipients, "baz@ory.sh") //these addresses will be marked as sent and won't be sent again by the settings hook address1, err := reg.IdentityPool().FindVerifiableAddressByValue(context.Background(), identity.VerifiableAddressTypeEmail, "foo@ory.sh") @@ -119,8 +121,18 @@ func TestVerifier(t *testing.T) { require.NoError(t, err) assert.EqualValues(t, identity.VerifiableAddressStatusSent, address2.Status) + switch k { + case "settings": + originalFlow = &settings.Flow{RequestURL: "http://foo.com/settings?after_verification_return_to=verification_callback"} + case "register": + originalFlow = ®istration.Flow{RequestURL: "http://foo.com/registration?after_verification_return_to=verification_callback"} + default: + t.FailNow() + } require.NoError(t, hf(h, i, originalFlow)) - expectedVerificationFlow, err = verification.NewPostHookFlow(conf, conf.SelfServiceFlowVerificationRequestLifespan(ctx), "", u, s, originalFlow) + + assert.Emptyf(t, originalFlow.ContinueWith(), "%+v", originalFlow.ContinueWith()) + require.NoError(t, err) var verificationFlow2 verification.Flow require.NoError(t, reg.Persister().GetConnection(context.Background()).First(&verificationFlow2)) @@ -131,3 +143,19 @@ func TestVerifier(t *testing.T) { }) } } + +func assertContinueWithAddresses(t *testing.T, cs []flow.ContinueWith, addresses []string) { + t.Helper() + + require.Len(t, cs, len(addresses)) + + e := make([]string, len(cs)) + + for i, c := range cs { + require.IsType(t, &flow.ContinueWithVerificationUI{}, c) + fView := c.(*flow.ContinueWithVerificationUI).Flow + e[i] = fView.VerifiableAddress + } + + require.ElementsMatch(t, addresses, e) +} diff --git a/selfservice/strategy/code/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads.json b/selfservice/strategy/code/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads.json index ec1092ad77a6..37f61ac9e827 100644 --- a/selfservice/strategy/code/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads.json +++ b/selfservice/strategy/code/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads.json @@ -1,17 +1,4 @@ [ - { - "attributes": { - "disabled": false, - "name": "csrf_token", - "node_type": "input", - "required": true, - "type": "hidden" - }, - "group": "default", - "messages": [], - "meta": {}, - "type": "input" - }, { "attributes": { "disabled": false, @@ -49,5 +36,18 @@ } }, "type": "input" + }, + { + "attributes": { + "disabled": false, + "name": "csrf_token", + "node_type": "input", + "required": true, + "type": "hidden" + }, + "group": "default", + "messages": [], + "meta": {}, + "type": "input" } ] diff --git a/selfservice/strategy/code/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads_after_submission.json b/selfservice/strategy/code/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads_after_submission.json index a3f063a7cd66..790389fbb501 100644 --- a/selfservice/strategy/code/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads_after_submission.json +++ b/selfservice/strategy/code/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads_after_submission.json @@ -50,6 +50,19 @@ } } }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, { "type": "input", "group": "code", @@ -68,18 +81,5 @@ "type": "info" } } - }, - { - "type": "input", - "group": "default", - "attributes": { - "name": "csrf_token", - "type": "hidden", - "required": true, - "disabled": false, - "node_type": "input" - }, - "messages": [], - "meta": {} } ] diff --git a/selfservice/strategy/code/strategy_verification.go b/selfservice/strategy/code/strategy_verification.go index 3d8c7491575c..92a0f0e7fcc8 100644 --- a/selfservice/strategy/code/strategy_verification.go +++ b/selfservice/strategy/code/strategy_verification.go @@ -20,7 +20,6 @@ import ( "github.com/ory/kratos/x" "github.com/ory/x/decoderx" "github.com/ory/x/sqlxx" - "github.com/ory/x/urlx" ) func (s *Strategy) VerificationStrategyID() string { @@ -33,16 +32,40 @@ func (s *Strategy) RegisterPublicVerificationRoutes(public *x.RouterPublic) { func (s *Strategy) RegisterAdminVerificationRoutes(admin *x.RouterAdmin) { } +// PopulateVerificationMethod set's the appropriate UI nodes on this flow +// +// If the flow's state is `sent_email`, the `code` input and the success notification is set +// Otherwise, the default email input is added. +// In all cases, the CSRF token is added to the UI, and a submit button is set. func (s *Strategy) PopulateVerificationMethod(r *http.Request, f *verification.Flow) error { - f.UI.SetCSRF(s.deps.GenerateCSRFToken(r)) - f.UI.GetNodes().Upsert( - node.NewInputField("email", nil, node.CodeGroup, node.InputAttributeTypeEmail, node.WithRequiredInputAttribute). - WithMetaLabel(text.NewInfoNodeInputEmail()), - ) - f.UI.GetNodes().Append( + nodes := node.Nodes{} + switch f.State { + case verification.StateEmailSent: + nodes.Upsert( + node. + NewInputField("code", nil, node.CodeGroup, node.InputAttributeTypeText, node.WithRequiredInputAttribute). + WithMetaLabel(text.NewInfoNodeLabelVerifyOTP()), + ) + // Required for the re-send code button + nodes.Append( + node.NewInputField("method", s.VerificationNodeGroup(), node.CodeGroup, node.InputAttributeTypeHidden), + ) + f.UI.Messages.Set(text.NewVerificationEmailWithCodeSent()) + default: + nodes.Upsert( + node.NewInputField("email", nil, node.CodeGroup, node.InputAttributeTypeEmail, node.WithRequiredInputAttribute). + WithMetaLabel(text.NewInfoNodeInputEmail()), + ) + } + nodes.Append( node.NewInputField("method", s.VerificationStrategyID(), node.CodeGroup, node.InputAttributeTypeSubmit). WithMetaLabel(text.NewInfoNodeLabelSubmit()), ) + + f.UI.Nodes = nodes + if f.Type == flow.TypeBrowser { + f.UI.SetCSRF(s.deps.GenerateCSRFToken(r)) + } return nil } @@ -145,40 +168,13 @@ func (s *Strategy) Verify(w http.ResponseWriter, r *http.Request, f *verificatio } } -func (s *Strategy) createVerificationCodeForm(action string, code *string, email *string) *container.Container { - // re-initialize the UI with a "clean" new state - c := &container.Container{ - Method: "POST", - Action: action, - } - - c.Nodes.Append( - node. - NewInputField("code", code, node.CodeGroup, node.InputAttributeTypeText, node.WithRequiredInputAttribute). - WithMetaLabel(text.NewInfoNodeLabelVerifyOTP()), - ) - c.Nodes.Append( - node.NewInputField("method", s.VerificationNodeGroup(), node.CodeGroup, node.InputAttributeTypeHidden), - ) - - c.Nodes.Append( - node.NewInputField("method", s.VerificationStrategyID(), node.CodeGroup, node.InputAttributeTypeSubmit). - WithMetaLabel(text.NewInfoNodeLabelSubmit()), - ) +func (s *Strategy) handleLinkClick(w http.ResponseWriter, r *http.Request, f *verification.Flow, code string) error { - if email != nil { - c.Nodes.Append( - node.NewInputField("email", email, node.CodeGroup, node.InputAttributeTypeSubmit). - WithMetaLabel(text.NewInfoNodeResendOTP()), - ) + // Pre-fill the code + if codeField := f.UI.Nodes.Find("code"); codeField != nil { + codeField.Attributes.SetValue(code) } - return c -} - -func (s *Strategy) handleLinkClick(w http.ResponseWriter, r *http.Request, f *verification.Flow, code string) error { - f.UI = s.createVerificationCodeForm(flow.AppendFlowTo(urlx.AppendPaths(s.deps.Config().SelfPublicURL(r.Context()), verification.RouteSubmitFlow), f.ID).String(), &code, nil) - // In the verification flow, we can't enforce CSRF if the flow is opened from an email, so we initialize the CSRF // token here, so all subsequent interactions are protected csrfToken := s.deps.CSRFHandler().RegenerateToken(w, r) @@ -228,9 +224,16 @@ func (s *Strategy) verificationHandleFormSubmission(w http.ResponseWriter, r *ht f.State = verification.StateEmailSent - f.UI = s.createVerificationCodeForm(flow.AppendFlowTo(urlx.AppendPaths(s.deps.Config().SelfPublicURL(r.Context()), verification.RouteSubmitFlow), f.ID).String(), nil, &body.Email) - f.UI.Messages.Set(text.NewVerificationEmailWithCodeSent()) - f.UI.SetCSRF(s.deps.GenerateCSRFToken(r)) + if err := s.PopulateVerificationMethod(r, f); err != nil { + return s.handleVerificationError(w, r, f, body, err) + } + + if body.Email != "" { + f.UI.Nodes.Append( + node.NewInputField("email", body.Email, node.CodeGroup, node.InputAttributeTypeSubmit). + WithMetaLabel(text.NewInfoNodeResendOTP()), + ) + } if err := s.deps.VerificationFlowPersister().UpdateVerificationFlow(r.Context(), f); err != nil { return s.handleVerificationError(w, r, f, body, err) diff --git a/selfservice/strategy/code/strategy_verification_test.go b/selfservice/strategy/code/strategy_verification_test.go index 92fd727ccb8d..b00bcda5c2bf 100644 --- a/selfservice/strategy/code/strategy_verification_test.go +++ b/selfservice/strategy/code/strategy_verification_test.go @@ -108,14 +108,14 @@ func TestVerification(t *testing.T) { body := expectSuccess(t, nil, false, false, func(v url.Values) { v.Set("email", "test@ory.sh") }) - testhelpers.SnapshotTExcept(t, json.RawMessage(gjson.Get(body, "ui.nodes").String()), []string{"4.attributes.value"}) + testhelpers.SnapshotTExcept(t, json.RawMessage(gjson.Get(body, "ui.nodes").String()), []string{"3.attributes.value"}) }) t.Run("description=should set all the correct verification payloads", func(t *testing.T) { c := testhelpers.NewClientWithCookies(t) rs := testhelpers.GetVerificationFlow(t, c, public) - testhelpers.SnapshotTExcept(t, rs.Ui.Nodes, []string{"0.attributes.value"}) + testhelpers.SnapshotTExcept(t, rs.Ui.Nodes, []string{"2.attributes.value"}) assert.EqualValues(t, public.URL+verification.RouteSubmitFlow+"?flow="+rs.Id, rs.Ui.Action) assert.Empty(t, rs.Ui.Messages) }) diff --git a/spec/api.json b/spec/api.json index 7fca9d69f71d..fb4b214217aa 100755 --- a/spec/api.json +++ b/spec/api.json @@ -417,6 +417,90 @@ "title": "Authenticator Assurance Level (AAL)", "type": "string" }, + "continueWith": { + "discriminator": { + "mapping": { + "set_ory_session_token": "#/components/schemas/continueWithSetOrySessionToken", + "verification_ui": "#/components/schemas/continueWithVerificationUi" + }, + "propertyName": "action" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/continueWithVerificationUi" + }, + { + "$ref": "#/components/schemas/continueWithSetOrySessionToken" + } + ] + }, + "continueWithSetOrySessionToken": { + "description": "Indicates that a session was issued, and the application should use this token for authenticated requests", + "properties": { + "action": { + "description": "Action will always be `set_ory_session_token`\nset_ory_session_token ContinueWithActionSetOrySessionToken\nverification_ui ContinueWithActionVerificationUI", + "enum": [ + "set_ory_session_token", + "verification_ui" + ], + "type": "string", + "x-go-enum-desc": "set_ory_session_token ContinueWithActionSetOrySessionToken\nverification_ui ContinueWithActionVerificationUI" + }, + "ory_session_token": { + "description": "Token is the token of the session", + "type": "string" + } + }, + "required": [ + "action", + "ory_session_token" + ], + "type": "object" + }, + "continueWithVerificationUi": { + "description": "Indicates, that the UI flow could be continued by showing a verification ui", + "properties": { + "action": { + "description": "Action will always be `verification_ui`\nset_ory_session_token ContinueWithActionSetOrySessionToken\nverification_ui ContinueWithActionVerificationUI", + "enum": [ + "set_ory_session_token", + "verification_ui" + ], + "type": "string", + "x-go-enum-desc": "set_ory_session_token ContinueWithActionSetOrySessionToken\nverification_ui ContinueWithActionVerificationUI" + }, + "flow": { + "$ref": "#/components/schemas/continueWithVerificationUiFlow" + } + }, + "required": [ + "action", + "flow" + ], + "type": "object" + }, + "continueWithVerificationUiFlow": { + "properties": { + "id": { + "description": "The ID of the verification flow", + "format": "uuid", + "type": "string" + }, + "url": { + "description": "The URL of the verification flow", + "type": "string" + }, + "verifiable_address": { + "description": "The address that should be verified in this flow", + "type": "string" + } + }, + "required": [ + "id", + "verifiable_address" + ], + "type": "object" + }, "courierMessageStatus": { "description": "A Message's Status", "enum": [ @@ -1806,6 +1890,13 @@ "description": "Active, if set, contains the registration method that is being used. It is initially\nnot set.", "type": "string" }, + "continue_with": { + "description": "Contains a list of actions, that could follow this flow\n\nIt can, for example, contain a reference to the verification flow, created as part of the user's\nregistration.", + "items": { + "$ref": "#/components/schemas/continueWith" + }, + "type": "array" + }, "expires_at": { "description": "ExpiresAt is the time (UTC) when the flow expires. If the user still wishes to update the setting,\na new flow has to be initiated.", "format": "date-time", @@ -1883,6 +1974,13 @@ "successfulNativeRegistration": { "description": "The Response for Registration Flows via API", "properties": { + "continue_with": { + "description": "Contains a list of actions, that could follow this flow\n\nIt can, for example, this will contain a reference to the verification flow, created as part of the user's\nregistration or the token of the session.", + "items": { + "$ref": "#/components/schemas/continueWith" + }, + "type": "array" + }, "identity": { "$ref": "#/components/schemas/identity" }, diff --git a/spec/swagger.json b/spec/swagger.json index 7ace6c059cf9..7b41f7e7b3e2 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -3226,6 +3226,75 @@ "type": "string", "title": "Authenticator Assurance Level (AAL)" }, + "continueWith": { + }, + "continueWithSetOrySessionToken": { + "description": "Indicates that a session was issued, and the application should use this token for authenticated requests", + "type": "object", + "required": [ + "action", + "ory_session_token" + ], + "properties": { + "action": { + "description": "Action will always be `set_ory_session_token`\nset_ory_session_token ContinueWithActionSetOrySessionToken\nverification_ui ContinueWithActionVerificationUI", + "type": "string", + "enum": [ + "set_ory_session_token", + "verification_ui" + ], + "x-go-enum-desc": "set_ory_session_token ContinueWithActionSetOrySessionToken\nverification_ui ContinueWithActionVerificationUI" + }, + "ory_session_token": { + "description": "Token is the token of the session", + "type": "string" + } + } + }, + "continueWithVerificationUi": { + "description": "Indicates, that the UI flow could be continued by showing a verification ui", + "type": "object", + "required": [ + "action", + "flow" + ], + "properties": { + "action": { + "description": "Action will always be `verification_ui`\nset_ory_session_token ContinueWithActionSetOrySessionToken\nverification_ui ContinueWithActionVerificationUI", + "type": "string", + "enum": [ + "set_ory_session_token", + "verification_ui" + ], + "x-go-enum-desc": "set_ory_session_token ContinueWithActionSetOrySessionToken\nverification_ui ContinueWithActionVerificationUI" + }, + "flow": { + "$ref": "#/definitions/continueWithVerificationUiFlow" + } + } + }, + "continueWithVerificationUiFlow": { + "type": "object", + "required": [ + "id", + "verifiable_address" + ], + "properties": { + "id": { + "description": "The ID of the verification flow", + "type": "string", + "format": "uuid" + }, + "url": { + "description": "The URL of the verification flow", + "type": "string" + }, + "verifiable_address": { + "description": "The address that should be verified in this flow", + "type": "string" + } + } + }, "courierMessageStatus": { "description": "A Message's Status", "type": "integer", @@ -4587,6 +4656,13 @@ "description": "Active, if set, contains the registration method that is being used. It is initially\nnot set.", "type": "string" }, + "continue_with": { + "description": "Contains a list of actions, that could follow this flow\n\nIt can, for example, contain a reference to the verification flow, created as part of the user's\nregistration.", + "type": "array", + "items": { + "$ref": "#/definitions/continueWith" + } + }, "expires_at": { "description": "ExpiresAt is the time (UTC) when the flow expires. If the user still wishes to update the setting,\na new flow has to be initiated.", "type": "string", @@ -4652,6 +4728,13 @@ "identity" ], "properties": { + "continue_with": { + "description": "Contains a list of actions, that could follow this flow\n\nIt can, for example, this will contain a reference to the verification flow, created as part of the user's\nregistration or the token of the session.", + "type": "array", + "items": { + "$ref": "#/definitions/continueWith" + } + }, "identity": { "$ref": "#/definitions/identity" }, diff --git a/test/e2e/cypress/integration/profiles/email/registration/success.spec.ts b/test/e2e/cypress/integration/profiles/email/registration/success.spec.ts index c93555b133fc..9235e4572b65 100644 --- a/test/e2e/cypress/integration/profiles/email/registration/success.spec.ts +++ b/test/e2e/cypress/integration/profiles/email/registration/success.spec.ts @@ -25,8 +25,13 @@ context("Registration success with email profile", () => { }) beforeEach(() => { + cy.deleteMail() cy.clearAllCookies() cy.visit(route) + cy.enableVerification() + if (app === "express") { + cy.enableVerificationUIAfterRegistration("password") + } }) it("should sign up and be logged in", () => { @@ -43,6 +48,20 @@ context("Registration success with email profile", () => { cy.get('[type="checkbox"][name="traits.tos"]').click({ force: true }) cy.submitPasswordForm() + + cy.url().should("contain", "verification") + cy.getVerificationCodeFromEmail(email).then((code) => { + cy.get("input[name=code]").type(code) + cy.get("button[name=method][value=code]").click() + }) + + cy.get('[data-testid="ui/message/1080002"]').should( + "have.text", + "You successfully verified your email address.", + ) + + cy.get("[data-testid='node/anchor/continue']").click() + if (app === "express") { cy.get('a[href*="sessions"').click() } @@ -72,6 +91,20 @@ context("Registration success with email profile", () => { cy.get('input[name="traits.website"]').type(website) cy.submitPasswordForm() + + cy.url().should("contain", "verification") + cy.getVerificationCodeFromEmail(email).then((code) => { + cy.get("input[name=code]").type(code) + cy.get("button[name=method][value=code]").click() + }) + + cy.get('[data-testid="ui/message/1080002"]').should( + "have.text", + "You successfully verified your email address.", + ) + + cy.get("[data-testid='node/anchor/continue']").click() + if (app === "express") { cy.get('a[href*="sessions"').click() } @@ -91,6 +124,7 @@ context("Registration success with email profile", () => { }) it("should sign up and be redirected", () => { + cy.disableVerification() cy.browserReturnUrlOry() cy.visit(route + "?return_to=https://www.example.org/") @@ -103,6 +137,7 @@ context("Registration success with email profile", () => { cy.get('input[name="traits.website').type(website) cy.get('input[name="password"]').type(password) cy.submitPasswordForm() + cy.url().should("eq", "https://www.example.org/") }) }) @@ -144,5 +179,29 @@ context("Registration success with email profile", () => { cy.url().should("eq", "https://www.example.org/") }) + + it.only("should not redirect to verification_flow if not configured", () => { + cy.deleteMail() + cy.useConfigProfile("email") + cy.enableVerification() + cy.proxy("express") + cy.visit(express.registration + "?return_to=https://www.example.org/") + + const email = gen.email() + const password = gen.password() + const website = "https://www.example.org/" + + cy.get(`${appPrefix("express")} input[name="traits"]`).should("not.exist") + cy.get('input[name="traits.email"]').type(email) + cy.get('input[name="traits.website').type(website) + cy.get('input[name="password"]').type(password) + + cy.submitPasswordForm() + + // Verify that the verification code is still sent + cy.getVerificationCodeFromEmail(email).should("not.be.undefined") + + cy.url().should("eq", "https://www.example.org/") + }) }) }) diff --git a/test/e2e/cypress/support/commands.ts b/test/e2e/cypress/support/commands.ts index f0b567b9ba4e..d58d43a17af8 100644 --- a/test/e2e/cypress/support/commands.ts +++ b/test/e2e/cypress/support/commands.ts @@ -504,10 +504,8 @@ Cypress.Commands.add( cy.request({ url }) .then(({ body }) => { const form = body.ui - // label should still exist after request, for more detail: #2591 - expect(form.nodes[1].meta).to.not.be.null - expect(form.nodes[1].meta.label).to.not.be.null - expect(form.nodes[1].meta.label.text).to.equal("Email") + expect(form.nodes.some((node) => node.meta?.label?.text === "Email")).to + .be.true return cy.request({ method: form.method, @@ -1348,3 +1346,39 @@ Cypress.Commands.add("getCourierMessages", () => { return res.body }) }) + +Cypress.Commands.add( + "enableVerificationUIAfterRegistration", + (strategy: "password" | "oidc" | "webauthn") => { + cy.updateConfigFile((config) => { + if (!config.selfservice.flows.registration.after[strategy]) { + config.selfservice.flows.registration.after = { + [strategy]: { hooks: [] }, + } + } + + const hooks = + config.selfservice.flows.registration.after[strategy].hooks || [] + config.selfservice.flows.registration.after[strategy].hooks = [ + ...hooks.filter((h) => h.hook !== "show_verification_ui"), + { hook: "show_verification_ui" }, + ] + return config + }) + }, +) + +Cypress.Commands.add("getVerificationCodeFromEmail", (email) => { + return cy + .getMail({ removeMail: true }) + .should((message) => { + expect(message.subject).to.equal("Please verify your email address") + expect(message.toAddresses[0].trim()).to.equal(email) + }) + .then((message) => { + const code = extractRecoveryCode(message.body) + expect(code).to.not.be.undefined + expect(code.length).to.equal(6) + return code + }) +}) diff --git a/test/e2e/cypress/support/index.d.ts b/test/e2e/cypress/support/index.d.ts index b66c89273cd1..dd3bb60fa1a9 100644 --- a/test/e2e/cypress/support/index.d.ts +++ b/test/e2e/cypress/support/index.d.ts @@ -1,6 +1,8 @@ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 +import { Session as KratosSession } from "@ory/kratos-client" + export interface MailMessage { fromAddress: string toAddresses: Array @@ -36,7 +38,7 @@ declare global { expectMethods?: Array< "password" | "webauthn" | "lookup_secret" | "totp" > - }): Chainable + }): Chainable /** * Expect that the browser has no valid Ory Kratos Cookie Session. @@ -53,7 +55,7 @@ declare global { password: string expectSession?: boolean cookieUrl?: string - }): Chainable> + }): Chainable> /** * Sign up a user @@ -91,6 +93,7 @@ declare global { performEmailVerification(opts?: { expect?: { email?: string; redirectTo?: string } strategy?: Strategy + useLinkFromEmail?: boolean }): Chainable /** @@ -111,7 +114,7 @@ declare global { email: string password: string fields: { [key: string]: string } - }): Chainable + }): Chainable /** * Submits a recovery flow via the API @@ -608,7 +611,7 @@ declare global { loginApi(opts: { email: string password: string - }): Chainable<{ session: Session }> + }): Chainable<{ session: KratosSession }> /** * Same as loginApi but uses dark magic to avoid cookie issues. @@ -618,7 +621,7 @@ declare global { loginApiWithoutCookies(opts: { email: string password: string - }): Chainable<{ session: Session }> + }): Chainable<{ session: KratosSession }> /** * Which app to proxy @@ -658,9 +661,24 @@ declare global { value: string, ): Chainable + /** + * Fetches the courier messages from the admin API + */ getCourierMessages(): Chainable< { recipient: string; template_type: string }[] > + + /** + * Enable the verification UI after registration hook + */ + enableVerificationUIAfterRegistration( + strategy: "password" | "oidc" | "webauthn", + ): Chainable + + /** + * Extracts a verification code from the received email + */ + getVerificationCodeFromEmail(email: string): Chainable } } } diff --git a/test/e2e/profiles/email/.kratos.yml b/test/e2e/profiles/email/.kratos.yml index 8470f75ed151..2176f282084a 100644 --- a/test/e2e/profiles/email/.kratos.yml +++ b/test/e2e/profiles/email/.kratos.yml @@ -13,15 +13,14 @@ selfservice: after: password: hooks: - - - hook: session + - hook: session login: ui_url: http://localhost:4455/login error: ui_url: http://localhost:4455/error verification: - ui_url: http://localhost:4455/verify + ui_url: http://localhost:4455/verification recovery: ui_url: http://localhost:4455/recovery diff --git a/test/e2e/profiles/oidc/identity.traits.schema.json b/test/e2e/profiles/oidc/identity.traits.schema.json index e0675e54a72b..10fb49486aed 100644 --- a/test/e2e/profiles/oidc/identity.traits.schema.json +++ b/test/e2e/profiles/oidc/identity.traits.schema.json @@ -20,6 +20,9 @@ "webauthn": { "identifier": true } + }, + "verification": { + "via": "email" } } }, @@ -38,10 +41,7 @@ "type": "boolean" } }, - "required": [ - "email", - "website" - ], + "required": ["email", "website"], "additionalProperties": false } }