From 7aac70c2cc9363250208a1a67302e2aec1a9261f Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:04:57 +0200 Subject: [PATCH 1/6] feat: add redirect to continue_with for SPA flows This patch adds the new `continue_with` action `redirect_browser_to`, which contains the redirect URL the app should redirect to. It is only supported for SPA (not server-side browser apps, not native apps) flows at this point in time. --- .schema/openapi/patches/schema.yaml | 2 + internal/client-go/.openapi-generator/FILES | 2 + internal/client-go/README.md | 1 + internal/client-go/model_continue_with.go | 40 +++++ ...model_continue_with_redirect_browser_to.go | 147 +++++++++++++++++ internal/httpclient/.openapi-generator/FILES | 2 + internal/httpclient/README.md | 1 + internal/httpclient/model_continue_with.go | 40 +++++ ...model_continue_with_redirect_browser_to.go | 147 +++++++++++++++++ selfservice/flow/continue_with.go | 28 ++++ selfservice/flow/login/hook.go | 4 + selfservice/flow/registration/hook.go | 7 +- selfservice/flow/settings/hook.go | 1 + .../strategy/code/strategy_login_test.go | 10 +- .../code/strategy_registration_test.go | 16 +- selfservice/strategy/lookup/login_test.go | 7 + selfservice/strategy/lookup/settings_test.go | 6 + .../strategy/passkey/passkey_login_test.go | 7 + .../passkey/passkey_registration_test.go | 9 ++ .../strategy/passkey/passkey_settings_test.go | 7 + selfservice/strategy/password/login_test.go | 26 +++ .../strategy/password/registration_test.go | 51 +++--- .../strategy/password/settings_test.go | 11 +- ...on=hydrate_the_proper_fields-type=spa.json | 153 ++++++++++++++++++ selfservice/strategy/profile/strategy_test.go | 9 +- selfservice/strategy/totp/login_test.go | 17 +- selfservice/strategy/totp/settings_test.go | 12 ++ selfservice/strategy/webauthn/login_test.go | 7 + .../strategy/webauthn/registration_test.go | 7 + .../strategy/webauthn/settings_test.go | 8 + spec/api.json | 20 +++ spec/swagger.json | 16 ++ 32 files changed, 790 insertions(+), 31 deletions(-) create mode 100644 internal/client-go/model_continue_with_redirect_browser_to.go create mode 100644 internal/httpclient/model_continue_with_redirect_browser_to.go create mode 100644 selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=spa.json diff --git a/.schema/openapi/patches/schema.yaml b/.schema/openapi/patches/schema.yaml index 206aceb2708e..ff661ce4079d 100644 --- a/.schema/openapi/patches/schema.yaml +++ b/.schema/openapi/patches/schema.yaml @@ -43,6 +43,7 @@ set_ory_session_token: "#/components/schemas/continueWithSetOrySessionToken" show_settings_ui: "#/components/schemas/continueWithSettingsUi" show_recovery_ui: "#/components/schemas/continueWithRecoveryUi" + redirect_browser_to: "#/components/schemas/continueWithRedirectBrowserTo" - op: add path: /components/schemas/continueWith/oneOf @@ -51,3 +52,4 @@ - "$ref": "#/components/schemas/continueWithSetOrySessionToken" - "$ref": "#/components/schemas/continueWithSettingsUi" - "$ref": "#/components/schemas/continueWithRecoveryUi" + - "$ref": "#/components/schemas/continueWithRedirectBrowserTo" diff --git a/internal/client-go/.openapi-generator/FILES b/internal/client-go/.openapi-generator/FILES index fdf34c5e1507..8f05b235508f 100644 --- a/internal/client-go/.openapi-generator/FILES +++ b/internal/client-go/.openapi-generator/FILES @@ -15,6 +15,7 @@ docs/ConsistencyRequestParameters.md docs/ContinueWith.md docs/ContinueWithRecoveryUi.md docs/ContinueWithRecoveryUiFlow.md +docs/ContinueWithRedirectBrowserTo.md docs/ContinueWithSetOrySessionToken.md docs/ContinueWithSettingsUi.md docs/ContinueWithSettingsUiFlow.md @@ -139,6 +140,7 @@ model_consistency_request_parameters.go model_continue_with.go model_continue_with_recovery_ui.go model_continue_with_recovery_ui_flow.go +model_continue_with_redirect_browser_to.go model_continue_with_set_ory_session_token.go model_continue_with_settings_ui.go model_continue_with_settings_ui_flow.go diff --git a/internal/client-go/README.md b/internal/client-go/README.md index 04dd61ab7d1e..01f9831e7520 100644 --- a/internal/client-go/README.md +++ b/internal/client-go/README.md @@ -142,6 +142,7 @@ Class | Method | HTTP request | Description - [ContinueWith](docs/ContinueWith.md) - [ContinueWithRecoveryUi](docs/ContinueWithRecoveryUi.md) - [ContinueWithRecoveryUiFlow](docs/ContinueWithRecoveryUiFlow.md) + - [ContinueWithRedirectBrowserTo](docs/ContinueWithRedirectBrowserTo.md) - [ContinueWithSetOrySessionToken](docs/ContinueWithSetOrySessionToken.md) - [ContinueWithSettingsUi](docs/ContinueWithSettingsUi.md) - [ContinueWithSettingsUiFlow](docs/ContinueWithSettingsUiFlow.md) diff --git a/internal/client-go/model_continue_with.go b/internal/client-go/model_continue_with.go index 9e97dbf479e7..6fb1056836e6 100644 --- a/internal/client-go/model_continue_with.go +++ b/internal/client-go/model_continue_with.go @@ -19,6 +19,7 @@ import ( // ContinueWith - struct for ContinueWith type ContinueWith struct { ContinueWithRecoveryUi *ContinueWithRecoveryUi + ContinueWithRedirectBrowserTo *ContinueWithRedirectBrowserTo ContinueWithSetOrySessionToken *ContinueWithSetOrySessionToken ContinueWithSettingsUi *ContinueWithSettingsUi ContinueWithVerificationUi *ContinueWithVerificationUi @@ -31,6 +32,13 @@ func ContinueWithRecoveryUiAsContinueWith(v *ContinueWithRecoveryUi) ContinueWit } } +// ContinueWithRedirectBrowserToAsContinueWith is a convenience function that returns ContinueWithRedirectBrowserTo wrapped in ContinueWith +func ContinueWithRedirectBrowserToAsContinueWith(v *ContinueWithRedirectBrowserTo) ContinueWith { + return ContinueWith{ + ContinueWithRedirectBrowserTo: v, + } +} + // ContinueWithSetOrySessionTokenAsContinueWith is a convenience function that returns ContinueWithSetOrySessionToken wrapped in ContinueWith func ContinueWithSetOrySessionTokenAsContinueWith(v *ContinueWithSetOrySessionToken) ContinueWith { return ContinueWith{ @@ -62,6 +70,18 @@ func (dst *ContinueWith) UnmarshalJSON(data []byte) error { return fmt.Errorf("Failed to unmarshal JSON into map for the discrimintor lookup.") } + // check if the discriminator value is 'redirect_browser_to' + if jsonDict["action"] == "redirect_browser_to" { + // try to unmarshal JSON data into ContinueWithRedirectBrowserTo + err = json.Unmarshal(data, &dst.ContinueWithRedirectBrowserTo) + if err == nil { + return nil // data stored in dst.ContinueWithRedirectBrowserTo, return on the first match + } else { + dst.ContinueWithRedirectBrowserTo = nil + return fmt.Errorf("Failed to unmarshal ContinueWith as ContinueWithRedirectBrowserTo: %s", err.Error()) + } + } + // check if the discriminator value is 'set_ory_session_token' if jsonDict["action"] == "set_ory_session_token" { // try to unmarshal JSON data into ContinueWithSetOrySessionToken @@ -122,6 +142,18 @@ func (dst *ContinueWith) UnmarshalJSON(data []byte) error { } } + // check if the discriminator value is 'continueWithRedirectBrowserTo' + if jsonDict["action"] == "continueWithRedirectBrowserTo" { + // try to unmarshal JSON data into ContinueWithRedirectBrowserTo + err = json.Unmarshal(data, &dst.ContinueWithRedirectBrowserTo) + if err == nil { + return nil // data stored in dst.ContinueWithRedirectBrowserTo, return on the first match + } else { + dst.ContinueWithRedirectBrowserTo = nil + return fmt.Errorf("Failed to unmarshal ContinueWith as ContinueWithRedirectBrowserTo: %s", err.Error()) + } + } + // check if the discriminator value is 'continueWithSetOrySessionToken' if jsonDict["action"] == "continueWithSetOrySessionToken" { // try to unmarshal JSON data into ContinueWithSetOrySessionToken @@ -167,6 +199,10 @@ func (src ContinueWith) MarshalJSON() ([]byte, error) { return json.Marshal(&src.ContinueWithRecoveryUi) } + if src.ContinueWithRedirectBrowserTo != nil { + return json.Marshal(&src.ContinueWithRedirectBrowserTo) + } + if src.ContinueWithSetOrySessionToken != nil { return json.Marshal(&src.ContinueWithSetOrySessionToken) } @@ -191,6 +227,10 @@ func (obj *ContinueWith) GetActualInstance() interface{} { return obj.ContinueWithRecoveryUi } + if obj.ContinueWithRedirectBrowserTo != nil { + return obj.ContinueWithRedirectBrowserTo + } + if obj.ContinueWithSetOrySessionToken != nil { return obj.ContinueWithSetOrySessionToken } diff --git a/internal/client-go/model_continue_with_redirect_browser_to.go b/internal/client-go/model_continue_with_redirect_browser_to.go new file mode 100644 index 000000000000..46344016b779 --- /dev/null +++ b/internal/client-go/model_continue_with_redirect_browser_to.go @@ -0,0 +1,147 @@ +/* + * 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" +) + +// ContinueWithRedirectBrowserTo Indicates, that the UI flow could be continued by showing a recovery ui +type ContinueWithRedirectBrowserTo struct { + // Action will always be `redirect_browser_to` + Action interface{} `json:"action"` + // The URL to redirect the browser to + RedirectBrowserTo *string `json:"redirect_browser_to,omitempty"` +} + +// NewContinueWithRedirectBrowserTo instantiates a new ContinueWithRedirectBrowserTo 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 NewContinueWithRedirectBrowserTo(action interface{}) *ContinueWithRedirectBrowserTo { + this := ContinueWithRedirectBrowserTo{} + this.Action = action + return &this +} + +// NewContinueWithRedirectBrowserToWithDefaults instantiates a new ContinueWithRedirectBrowserTo 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 NewContinueWithRedirectBrowserToWithDefaults() *ContinueWithRedirectBrowserTo { + this := ContinueWithRedirectBrowserTo{} + return &this +} + +// GetAction returns the Action field value +// If the value is explicit nil, the zero value for interface{} will be returned +func (o *ContinueWithRedirectBrowserTo) GetAction() interface{} { + if o == nil { + var ret interface{} + 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. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *ContinueWithRedirectBrowserTo) GetActionOk() (*interface{}, bool) { + if o == nil || o.Action == nil { + return nil, false + } + return &o.Action, true +} + +// SetAction sets field value +func (o *ContinueWithRedirectBrowserTo) SetAction(v interface{}) { + o.Action = v +} + +// GetRedirectBrowserTo returns the RedirectBrowserTo field value if set, zero value otherwise. +func (o *ContinueWithRedirectBrowserTo) GetRedirectBrowserTo() string { + if o == nil || o.RedirectBrowserTo == nil { + var ret string + return ret + } + return *o.RedirectBrowserTo +} + +// GetRedirectBrowserToOk returns a tuple with the RedirectBrowserTo field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *ContinueWithRedirectBrowserTo) GetRedirectBrowserToOk() (*string, bool) { + if o == nil || o.RedirectBrowserTo == nil { + return nil, false + } + return o.RedirectBrowserTo, true +} + +// HasRedirectBrowserTo returns a boolean if a field has been set. +func (o *ContinueWithRedirectBrowserTo) HasRedirectBrowserTo() bool { + if o != nil && o.RedirectBrowserTo != nil { + return true + } + + return false +} + +// SetRedirectBrowserTo gets a reference to the given string and assigns it to the RedirectBrowserTo field. +func (o *ContinueWithRedirectBrowserTo) SetRedirectBrowserTo(v string) { + o.RedirectBrowserTo = &v +} + +func (o ContinueWithRedirectBrowserTo) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.Action != nil { + toSerialize["action"] = o.Action + } + if o.RedirectBrowserTo != nil { + toSerialize["redirect_browser_to"] = o.RedirectBrowserTo + } + return json.Marshal(toSerialize) +} + +type NullableContinueWithRedirectBrowserTo struct { + value *ContinueWithRedirectBrowserTo + isSet bool +} + +func (v NullableContinueWithRedirectBrowserTo) Get() *ContinueWithRedirectBrowserTo { + return v.value +} + +func (v *NullableContinueWithRedirectBrowserTo) Set(val *ContinueWithRedirectBrowserTo) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWithRedirectBrowserTo) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWithRedirectBrowserTo) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWithRedirectBrowserTo(val *ContinueWithRedirectBrowserTo) *NullableContinueWithRedirectBrowserTo { + return &NullableContinueWithRedirectBrowserTo{value: val, isSet: true} +} + +func (v NullableContinueWithRedirectBrowserTo) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWithRedirectBrowserTo) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/httpclient/.openapi-generator/FILES b/internal/httpclient/.openapi-generator/FILES index fdf34c5e1507..8f05b235508f 100644 --- a/internal/httpclient/.openapi-generator/FILES +++ b/internal/httpclient/.openapi-generator/FILES @@ -15,6 +15,7 @@ docs/ConsistencyRequestParameters.md docs/ContinueWith.md docs/ContinueWithRecoveryUi.md docs/ContinueWithRecoveryUiFlow.md +docs/ContinueWithRedirectBrowserTo.md docs/ContinueWithSetOrySessionToken.md docs/ContinueWithSettingsUi.md docs/ContinueWithSettingsUiFlow.md @@ -139,6 +140,7 @@ model_consistency_request_parameters.go model_continue_with.go model_continue_with_recovery_ui.go model_continue_with_recovery_ui_flow.go +model_continue_with_redirect_browser_to.go model_continue_with_set_ory_session_token.go model_continue_with_settings_ui.go model_continue_with_settings_ui_flow.go diff --git a/internal/httpclient/README.md b/internal/httpclient/README.md index 04dd61ab7d1e..01f9831e7520 100644 --- a/internal/httpclient/README.md +++ b/internal/httpclient/README.md @@ -142,6 +142,7 @@ Class | Method | HTTP request | Description - [ContinueWith](docs/ContinueWith.md) - [ContinueWithRecoveryUi](docs/ContinueWithRecoveryUi.md) - [ContinueWithRecoveryUiFlow](docs/ContinueWithRecoveryUiFlow.md) + - [ContinueWithRedirectBrowserTo](docs/ContinueWithRedirectBrowserTo.md) - [ContinueWithSetOrySessionToken](docs/ContinueWithSetOrySessionToken.md) - [ContinueWithSettingsUi](docs/ContinueWithSettingsUi.md) - [ContinueWithSettingsUiFlow](docs/ContinueWithSettingsUiFlow.md) diff --git a/internal/httpclient/model_continue_with.go b/internal/httpclient/model_continue_with.go index 9e97dbf479e7..6fb1056836e6 100644 --- a/internal/httpclient/model_continue_with.go +++ b/internal/httpclient/model_continue_with.go @@ -19,6 +19,7 @@ import ( // ContinueWith - struct for ContinueWith type ContinueWith struct { ContinueWithRecoveryUi *ContinueWithRecoveryUi + ContinueWithRedirectBrowserTo *ContinueWithRedirectBrowserTo ContinueWithSetOrySessionToken *ContinueWithSetOrySessionToken ContinueWithSettingsUi *ContinueWithSettingsUi ContinueWithVerificationUi *ContinueWithVerificationUi @@ -31,6 +32,13 @@ func ContinueWithRecoveryUiAsContinueWith(v *ContinueWithRecoveryUi) ContinueWit } } +// ContinueWithRedirectBrowserToAsContinueWith is a convenience function that returns ContinueWithRedirectBrowserTo wrapped in ContinueWith +func ContinueWithRedirectBrowserToAsContinueWith(v *ContinueWithRedirectBrowserTo) ContinueWith { + return ContinueWith{ + ContinueWithRedirectBrowserTo: v, + } +} + // ContinueWithSetOrySessionTokenAsContinueWith is a convenience function that returns ContinueWithSetOrySessionToken wrapped in ContinueWith func ContinueWithSetOrySessionTokenAsContinueWith(v *ContinueWithSetOrySessionToken) ContinueWith { return ContinueWith{ @@ -62,6 +70,18 @@ func (dst *ContinueWith) UnmarshalJSON(data []byte) error { return fmt.Errorf("Failed to unmarshal JSON into map for the discrimintor lookup.") } + // check if the discriminator value is 'redirect_browser_to' + if jsonDict["action"] == "redirect_browser_to" { + // try to unmarshal JSON data into ContinueWithRedirectBrowserTo + err = json.Unmarshal(data, &dst.ContinueWithRedirectBrowserTo) + if err == nil { + return nil // data stored in dst.ContinueWithRedirectBrowserTo, return on the first match + } else { + dst.ContinueWithRedirectBrowserTo = nil + return fmt.Errorf("Failed to unmarshal ContinueWith as ContinueWithRedirectBrowserTo: %s", err.Error()) + } + } + // check if the discriminator value is 'set_ory_session_token' if jsonDict["action"] == "set_ory_session_token" { // try to unmarshal JSON data into ContinueWithSetOrySessionToken @@ -122,6 +142,18 @@ func (dst *ContinueWith) UnmarshalJSON(data []byte) error { } } + // check if the discriminator value is 'continueWithRedirectBrowserTo' + if jsonDict["action"] == "continueWithRedirectBrowserTo" { + // try to unmarshal JSON data into ContinueWithRedirectBrowserTo + err = json.Unmarshal(data, &dst.ContinueWithRedirectBrowserTo) + if err == nil { + return nil // data stored in dst.ContinueWithRedirectBrowserTo, return on the first match + } else { + dst.ContinueWithRedirectBrowserTo = nil + return fmt.Errorf("Failed to unmarshal ContinueWith as ContinueWithRedirectBrowserTo: %s", err.Error()) + } + } + // check if the discriminator value is 'continueWithSetOrySessionToken' if jsonDict["action"] == "continueWithSetOrySessionToken" { // try to unmarshal JSON data into ContinueWithSetOrySessionToken @@ -167,6 +199,10 @@ func (src ContinueWith) MarshalJSON() ([]byte, error) { return json.Marshal(&src.ContinueWithRecoveryUi) } + if src.ContinueWithRedirectBrowserTo != nil { + return json.Marshal(&src.ContinueWithRedirectBrowserTo) + } + if src.ContinueWithSetOrySessionToken != nil { return json.Marshal(&src.ContinueWithSetOrySessionToken) } @@ -191,6 +227,10 @@ func (obj *ContinueWith) GetActualInstance() interface{} { return obj.ContinueWithRecoveryUi } + if obj.ContinueWithRedirectBrowserTo != nil { + return obj.ContinueWithRedirectBrowserTo + } + if obj.ContinueWithSetOrySessionToken != nil { return obj.ContinueWithSetOrySessionToken } diff --git a/internal/httpclient/model_continue_with_redirect_browser_to.go b/internal/httpclient/model_continue_with_redirect_browser_to.go new file mode 100644 index 000000000000..46344016b779 --- /dev/null +++ b/internal/httpclient/model_continue_with_redirect_browser_to.go @@ -0,0 +1,147 @@ +/* + * 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" +) + +// ContinueWithRedirectBrowserTo Indicates, that the UI flow could be continued by showing a recovery ui +type ContinueWithRedirectBrowserTo struct { + // Action will always be `redirect_browser_to` + Action interface{} `json:"action"` + // The URL to redirect the browser to + RedirectBrowserTo *string `json:"redirect_browser_to,omitempty"` +} + +// NewContinueWithRedirectBrowserTo instantiates a new ContinueWithRedirectBrowserTo 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 NewContinueWithRedirectBrowserTo(action interface{}) *ContinueWithRedirectBrowserTo { + this := ContinueWithRedirectBrowserTo{} + this.Action = action + return &this +} + +// NewContinueWithRedirectBrowserToWithDefaults instantiates a new ContinueWithRedirectBrowserTo 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 NewContinueWithRedirectBrowserToWithDefaults() *ContinueWithRedirectBrowserTo { + this := ContinueWithRedirectBrowserTo{} + return &this +} + +// GetAction returns the Action field value +// If the value is explicit nil, the zero value for interface{} will be returned +func (o *ContinueWithRedirectBrowserTo) GetAction() interface{} { + if o == nil { + var ret interface{} + 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. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *ContinueWithRedirectBrowserTo) GetActionOk() (*interface{}, bool) { + if o == nil || o.Action == nil { + return nil, false + } + return &o.Action, true +} + +// SetAction sets field value +func (o *ContinueWithRedirectBrowserTo) SetAction(v interface{}) { + o.Action = v +} + +// GetRedirectBrowserTo returns the RedirectBrowserTo field value if set, zero value otherwise. +func (o *ContinueWithRedirectBrowserTo) GetRedirectBrowserTo() string { + if o == nil || o.RedirectBrowserTo == nil { + var ret string + return ret + } + return *o.RedirectBrowserTo +} + +// GetRedirectBrowserToOk returns a tuple with the RedirectBrowserTo field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *ContinueWithRedirectBrowserTo) GetRedirectBrowserToOk() (*string, bool) { + if o == nil || o.RedirectBrowserTo == nil { + return nil, false + } + return o.RedirectBrowserTo, true +} + +// HasRedirectBrowserTo returns a boolean if a field has been set. +func (o *ContinueWithRedirectBrowserTo) HasRedirectBrowserTo() bool { + if o != nil && o.RedirectBrowserTo != nil { + return true + } + + return false +} + +// SetRedirectBrowserTo gets a reference to the given string and assigns it to the RedirectBrowserTo field. +func (o *ContinueWithRedirectBrowserTo) SetRedirectBrowserTo(v string) { + o.RedirectBrowserTo = &v +} + +func (o ContinueWithRedirectBrowserTo) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.Action != nil { + toSerialize["action"] = o.Action + } + if o.RedirectBrowserTo != nil { + toSerialize["redirect_browser_to"] = o.RedirectBrowserTo + } + return json.Marshal(toSerialize) +} + +type NullableContinueWithRedirectBrowserTo struct { + value *ContinueWithRedirectBrowserTo + isSet bool +} + +func (v NullableContinueWithRedirectBrowserTo) Get() *ContinueWithRedirectBrowserTo { + return v.value +} + +func (v *NullableContinueWithRedirectBrowserTo) Set(val *ContinueWithRedirectBrowserTo) { + v.value = val + v.isSet = true +} + +func (v NullableContinueWithRedirectBrowserTo) IsSet() bool { + return v.isSet +} + +func (v *NullableContinueWithRedirectBrowserTo) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableContinueWithRedirectBrowserTo(val *ContinueWithRedirectBrowserTo) *NullableContinueWithRedirectBrowserTo { + return &NullableContinueWithRedirectBrowserTo{value: val, isSet: true} +} + +func (v NullableContinueWithRedirectBrowserTo) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableContinueWithRedirectBrowserTo) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/selfservice/flow/continue_with.go b/selfservice/flow/continue_with.go index 7a5f9ce22410..5b56bbf9aab6 100644 --- a/selfservice/flow/continue_with.go +++ b/selfservice/flow/continue_with.go @@ -201,6 +201,34 @@ func NewContinueWithRecoveryUI(f Flow) *ContinueWithRecoveryUI { } } +// swagger:enum ContinueWithActionRedirectTo +type ContinueWithActionRedirectBrowserTo string + +// #nosec G101 -- only a key constant +const ( + ContinueWithActionRedirectBrowserToString ContinueWithActionRedirectBrowserTo = "redirect_browser_to" +) + +// Indicates, that the UI flow could be continued by showing a recovery ui +// +// swagger:model continueWithRedirectBrowserTo +type ContinueWithRedirectBrowserTo struct { + // Action will always be `redirect_browser_to` + // + // required: true + Action ContinueWithActionRedirectBrowserTo `json:"action"` + + // The URL to redirect the browser to + RedirectTo string `json:"redirect_browser_to"` +} + +func NewContinueWithRedirectBrowserTo(redirectTo string) *ContinueWithRedirectBrowserTo { + return &ContinueWithRedirectBrowserTo{ + Action: ContinueWithActionRedirectBrowserToString, + RedirectTo: redirectTo, + } +} + func ErrorWithContinueWith(err *herodot.DefaultError, continueWith ...ContinueWith) *herodot.DefaultError { if err.DetailsField == nil { err.DetailsField = map[string]interface{}{} diff --git a/selfservice/flow/login/hook.go b/selfservice/flow/login/hook.go index f0e06ccfc934..4d3deddf2a0a 100644 --- a/selfservice/flow/login/hook.go +++ b/selfservice/flow/login/hook.go @@ -159,6 +159,10 @@ func (e *HookExecutor) PostLoginHook( "redirect_reason": "login successful", })...) + if f.Type != flow.TypeAPI { + f.AddContinueWith(flow.NewContinueWithRedirectBrowserTo(returnTo.String())) + } + classified := s s = s.Declassified() diff --git a/selfservice/flow/registration/hook.go b/selfservice/flow/registration/hook.go index 6a997009c1c5..e44be2487bbb 100644 --- a/selfservice/flow/registration/hook.go +++ b/selfservice/flow/registration/hook.go @@ -192,12 +192,17 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque if err != nil { return err } + span.SetAttributes(otelx.StringAttrs(map[string]string{ "return_to": returnTo.String(), - "flow_type": string(flow.TypeBrowser), + "flow_type": string(registrationFlow.Type), "redirect_reason": "registration successful", })...) + if registrationFlow.Type == flow.TypeBrowser && x.IsJSONRequest(r) { + registrationFlow.AddContinueWith(flow.NewContinueWithRedirectBrowserTo(returnTo.String())) + } + e.d.Audit(). WithRequest(r). WithField("identity_id", i.ID). diff --git a/selfservice/flow/settings/hook.go b/selfservice/flow/settings/hook.go index b688fd0fc431..88741e766736 100644 --- a/selfservice/flow/settings/hook.go +++ b/selfservice/flow/settings/hook.go @@ -308,6 +308,7 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, } // 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. + ctxUpdate.Flow.AddContinueWith(flow.NewContinueWithRedirectBrowserTo(returnTo.String())) updatedFlow.ContinueWithItems = ctxUpdate.Flow.ContinueWithItems e.d.Writer().Write(w, r, updatedFlow) diff --git a/selfservice/strategy/code/strategy_login_test.go b/selfservice/strategy/code/strategy_login_test.go index 19cac6d38375..55f090c19dd5 100644 --- a/selfservice/strategy/code/strategy_login_test.go +++ b/selfservice/strategy/code/strategy_login_test.go @@ -12,6 +12,8 @@ import ( "net/url" "testing" + "github.com/ory/kratos/selfservice/flow" + "github.com/ory/x/ioutilx" "github.com/ory/x/snapshotx" "github.com/ory/x/sqlcon" @@ -247,9 +249,15 @@ func TestLoginCodeStrategy(t *testing.T) { assert.NotEmpty(t, loginCode) // 3. Submit OTP - submitLogin(ctx, t, s, tc.apiType, func(v *url.Values) { + state := submitLogin(ctx, t, s, tc.apiType, func(v *url.Values) { v.Set("code", loginCode) }, true, nil) + if tc.apiType == ApiTypeSPA { + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(state.body, "continue_with.0.action").String(), "%s", state.body) + assert.Contains(t, gjson.Get(state.body, "continue_with.0.redirect_browser_to").String(), conf.SelfServiceBrowserDefaultReturnTo(ctx).String(), "%s", state.body) + } else { + assert.Empty(t, gjson.Get(state.body, "continue_with").Array(), "%s", state.body) + } }) t.Run("case=new identities automatically have login with code", func(t *testing.T) { diff --git a/selfservice/strategy/code/strategy_registration_test.go b/selfservice/strategy/code/strategy_registration_test.go index 27c645a94190..0b6caaa15da1 100644 --- a/selfservice/strategy/code/strategy_registration_test.go +++ b/selfservice/strategy/code/strategy_registration_test.go @@ -15,6 +15,8 @@ import ( "strings" "testing" + "github.com/ory/kratos/selfservice/flow" + "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" @@ -37,6 +39,7 @@ type state struct { email string testServer *httptest.Server resultIdentity *identity.Identity + body string } func TestRegistrationCodeStrategyDisabled(t *testing.T) { @@ -172,6 +175,7 @@ func TestRegistrationCodeStrategy(t *testing.T) { values.Set("method", "code") body, resp := testhelpers.RegistrationMakeRequest(t, apiType == ApiTypeNative, apiType == ApiTypeSPA, rf, s.client, testhelpers.EncodeFormAsJSON(t, apiType == ApiTypeNative, values)) + s.body = body if submitAssertion != nil { submitAssertion(ctx, t, s, body, resp) @@ -213,6 +217,7 @@ func TestRegistrationCodeStrategy(t *testing.T) { vals(&values) body, resp := testhelpers.RegistrationMakeRequest(t, apiType == ApiTypeNative, apiType == ApiTypeSPA, rf, s.client, testhelpers.EncodeFormAsJSON(t, apiType == ApiTypeNative, values)) + s.body = body if submitAssertion != nil { submitAssertion(ctx, t, s, body, resp) @@ -240,7 +245,7 @@ func TestRegistrationCodeStrategy(t *testing.T) { t.Parallel() ctx := context.Background() - _, reg, public := setup(ctx, t) + conf, reg, public := setup(ctx, t) for _, tc := range []struct { d string @@ -279,6 +284,15 @@ func TestRegistrationCodeStrategy(t *testing.T) { state = submitOTP(ctx, t, reg, state, func(v *url.Values) { v.Set("code", registrationCode) }, tc.apiType, nil) + + if tc.apiType == ApiTypeSPA { + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(state.body, "continue_with.0.action").String(), "%s", state.body) + assert.Contains(t, gjson.Get(state.body, "continue_with.0.redirect_browser_to").String(), conf.SelfServiceBrowserDefaultReturnTo(ctx).String(), "%s", state.body) + } else if tc.apiType == ApiTypeSPA { + assert.Empty(t, gjson.Get(state.body, "continue_with").Array(), "%s", state.body) + } else if tc.apiType == ApiTypeNative { + assert.NotContains(t, gjson.Get(state.body, "continue_with").Raw, string(flow.ContinueWithActionRedirectBrowserToString), "%s", state.body) + } }) t.Run("case=should normalize email address on sign up", func(t *testing.T) { diff --git a/selfservice/strategy/lookup/login_test.go b/selfservice/strategy/lookup/login_test.go index c4896962c660..b3bc454dac70 100644 --- a/selfservice/strategy/lookup/login_test.go +++ b/selfservice/strategy/lookup/login_test.go @@ -14,6 +14,8 @@ import ( "testing" "time" + "github.com/ory/kratos/selfservice/flow" + "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -241,6 +243,7 @@ func TestCompleteLogin(t *testing.T) { // We can still use another key body, res = doAPIFlowWithClient(t, payload("key-2"), id, apiClient, true) check(t, false, body, res, "key-2", 3) + assert.Empty(t, gjson.Get(body, "continue_with").Array(), "%s", body) }) t.Run("type=browser", func(t *testing.T) { @@ -250,6 +253,7 @@ func TestCompleteLogin(t *testing.T) { // We can still use another key body, res = doBrowserFlowWithClient(t, false, payload("key-5"), id, browserClient, true) check(t, true, body, res, "key-5", 3) + assert.Empty(t, gjson.Get(body, "continue_with").Array(), "%s", body) }) t.Run("type=spa", func(t *testing.T) { @@ -259,6 +263,9 @@ func TestCompleteLogin(t *testing.T) { // We can still use another key body, res = doBrowserFlowWithClient(t, true, payload("key-8"), id, browserClient, true) check(t, false, body, res, "key-8", 3) + + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(body, "continue_with.0.action").String(), "%s", body) + assert.Contains(t, gjson.Get(body, "continue_with.0.redirect_browser_to").String(), conf.SelfServiceBrowserDefaultReturnTo(ctx).String(), "%s", body) }) }) diff --git a/selfservice/strategy/lookup/settings_test.go b/selfservice/strategy/lookup/settings_test.go index fce2be4c0974..48a7faf19705 100644 --- a/selfservice/strategy/lookup/settings_test.go +++ b/selfservice/strategy/lookup/settings_test.go @@ -423,8 +423,11 @@ func TestCompleteSettings(t *testing.T) { if spa { assert.Contains(t, res.Request.URL.String(), publicTS.URL+settings.RouteSubmitFlow) + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(actual, "continue_with.0.action").String(), "%s", actual) + assert.Contains(t, gjson.Get(actual, "continue_with.0.redirect_browser_to").String(), uiTS.URL, "%s", actual) } else { assert.Contains(t, res.Request.URL.String(), uiTS.URL) + assert.Empty(t, gjson.Get(actual, "continue_with").Array(), "%s", actual) } assert.EqualValues(t, flow.StateSuccess, json.RawMessage(gjson.Get(actual, "state").String())) @@ -508,8 +511,11 @@ func TestCompleteSettings(t *testing.T) { if spa { assert.Contains(t, res.Request.URL.String(), publicTS.URL+settings.RouteSubmitFlow) + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(actual, "continue_with.0.action").String(), "%s", actual) + assert.Contains(t, gjson.Get(actual, "continue_with.0.redirect_browser_to").String(), uiTS.URL, "%s", actual) } else { assert.Contains(t, res.Request.URL.String(), uiTS.URL) + assert.Empty(t, gjson.Get(actual, "continue_with").Array(), "%s", actual) } assert.EqualValues(t, flow.StateSuccess, json.RawMessage(gjson.Get(actual, "state").String())) diff --git a/selfservice/strategy/passkey/passkey_login_test.go b/selfservice/strategy/passkey/passkey_login_test.go index 2a6c2075557e..67ec6737f3ba 100644 --- a/selfservice/strategy/passkey/passkey_login_test.go +++ b/selfservice/strategy/passkey/passkey_login_test.go @@ -209,7 +209,14 @@ func TestCompleteLogin(t *testing.T) { actualFlow, err := fix.reg.LoginFlowPersister().GetLoginFlow(context.Background(), uuid.FromStringOrNil(f.Id)) require.NoError(t, err) + assert.Empty(t, gjson.GetBytes(actualFlow.InternalContext, flow.PrefixInternalContextKey(identity.CredentialsTypePasskey, passkey.InternalContextKeySessionData))) + if spa { + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(body, "continue_with.0.action").String(), "%s", body) + assert.Contains(t, gjson.Get(body, "continue_with.0.redirect_browser_to").String(), fix.conf.SelfServiceBrowserDefaultReturnTo(ctx).String(), "%s", body) + } else { + assert.Empty(t, gjson.Get(body, "continue_with").Array(), "%s", body) + } } // We test here that login works even if the identity schema contains diff --git a/selfservice/strategy/passkey/passkey_registration_test.go b/selfservice/strategy/passkey/passkey_registration_test.go index d495e8c4dfe4..d7191207cedb 100644 --- a/selfservice/strategy/passkey/passkey_registration_test.go +++ b/selfservice/strategy/passkey/passkey_registration_test.go @@ -8,6 +8,8 @@ import ( "net/url" "testing" + "github.com/ory/kratos/selfservice/flow" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" @@ -327,6 +329,13 @@ func TestRegistration(t *testing.T) { i, _, err := fix.reg.PrivilegedIdentityPool().FindByCredentialsIdentifier(fix.ctx, identity.CredentialsTypePasskey, userID) require.NoError(t, err) assert.Equal(t, email, gjson.GetBytes(i.Traits, "username").String(), "%s", actual) + + if f == "spa" { + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(actual, "continue_with.0.action").String(), "%s", actual) + assert.Contains(t, gjson.Get(actual, "continue_with.0.redirect_browser_to").String(), fix.redirNoSessionTS.URL+"/registration-return-ts", "%s", actual) + } else { + assert.Empty(t, gjson.Get(actual, "continue_with").Array(), "%s", actual) + } }) } }) diff --git a/selfservice/strategy/passkey/passkey_settings_test.go b/selfservice/strategy/passkey/passkey_settings_test.go index ced111071711..842f4d22c10e 100644 --- a/selfservice/strategy/passkey/passkey_settings_test.go +++ b/selfservice/strategy/passkey/passkey_settings_test.go @@ -271,6 +271,13 @@ func TestCompleteSettings(t *testing.T) { flow.PrefixInternalContextKey(identity.CredentialsTypePasskey, passkey.InternalContextKeySessionData))) testhelpers.EnsureAAL(t, browserClient, fix.publicTS, "aal1", string(identity.CredentialsTypePasskey)) + + if spa { + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(body, "continue_with.0.action").String(), "%s", body) + assert.Contains(t, gjson.Get(body, "continue_with.0.redirect_browser_to").String(), fix.uiTS.URL, "%s", body) + } else { + assert.Empty(t, gjson.Get(body, "continue_with").Array(), "%s", body) + } } t.Run("type=browser", func(t *testing.T) { diff --git a/selfservice/strategy/password/login_test.go b/selfservice/strategy/password/login_test.go index 8d8879cce91c..e917b25d8836 100644 --- a/selfservice/strategy/password/login_test.go +++ b/selfservice/strategy/password/login_test.go @@ -741,6 +741,32 @@ func TestCompleteLogin(t *testing.T) { assert.Equal(t, identifier, gjson.Get(body, "identity.traits.subject").String(), "%s", body) }) + t.Run("should succeed and include redirect continue_with in SPA flow", func(t *testing.T) { + identifier, pwd := x.NewUUID().String(), "password" + createIdentity(ctx, reg, t, identifier, pwd) + + browserClient := testhelpers.NewClientWithCookies(t) + f := testhelpers.InitializeLoginFlowViaBrowser(t, browserClient, publicTS, false, true, false, false) + values := url.Values{"method": {"password"}, "identifier": {strings.ToUpper(identifier)}, "password": {pwd}, "csrf_token": {x.FakeCSRFToken}}.Encode() + body, res := testhelpers.LoginMakeRequest(t, false, true, f, browserClient, values) + + assert.EqualValues(t, http.StatusOK, res.StatusCode) + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(body, "continue_with.0.action").String(), "%s", body) + assert.EqualValues(t, conf.SelfServiceBrowserDefaultReturnTo(ctx).String(), gjson.Get(body, "continue_with.0.redirect_browser_to").String(), "%s", body) + }) + + t.Run("should succeed and not have redirect continue_with in api flow", func(t *testing.T) { + identifier, pwd := x.NewUUID().String(), "password" + createIdentity(ctx, reg, t, identifier, pwd) + browserClient := testhelpers.NewClientWithCookies(t) + f := testhelpers.InitializeLoginFlowViaAPI(t, apiClient, publicTS, false) + + body, res := testhelpers.LoginMakeRequest(t, true, true, f, browserClient, fmt.Sprintf(`{"method":"password","identifier":"%s","password":"%s"}`, strings.ToUpper(identifier), pwd)) + + assert.EqualValues(t, http.StatusOK, res.StatusCode, body) + assert.Empty(t, gjson.Get(body, "continue_with").Array(), "%s", body) + }) + t.Run("should login even if old form field name is used", func(t *testing.T) { identifier, pwd := x.NewUUID().String(), "password" createIdentity(ctx, reg, t, identifier, pwd) diff --git a/selfservice/strategy/password/registration_test.go b/selfservice/strategy/password/registration_test.go index 14bf2382b212..d52ca2d77707 100644 --- a/selfservice/strategy/password/registration_test.go +++ b/selfservice/strategy/password/registration_test.go @@ -14,6 +14,8 @@ import ( "testing" "time" + "github.com/ory/kratos/selfservice/flow" + "github.com/ory/kratos/driver" "github.com/ory/kratos/internal/registrationhelpers" @@ -106,7 +108,7 @@ func TestRegistration(t *testing.T) { }) }) - var expectLoginBody = func(t *testing.T, browserRedirTS *httptest.Server, isAPI, isSPA bool, hc *http.Client, values func(url.Values)) string { + var expectRegistrationBody = func(t *testing.T, browserRedirTS *httptest.Server, isAPI, isSPA bool, hc *http.Client, values func(url.Values)) string { if isAPI { return testhelpers.SubmitRegistrationForm(t, isAPI, hc, publicTS, values, isSPA, http.StatusOK, @@ -126,17 +128,17 @@ func TestRegistration(t *testing.T) { isSPA, http.StatusOK, expectReturnTo) } - var expectSuccessfulLogin = func(t *testing.T, isAPI, isSPA bool, hc *http.Client, values func(url.Values)) string { + var expectSuccessfulRegistration = func(t *testing.T, isAPI, isSPA bool, hc *http.Client, values func(url.Values)) string { useReturnToFromTS(redirTS) - return expectLoginBody(t, redirTS, isAPI, isSPA, hc, values) + return expectRegistrationBody(t, redirTS, isAPI, isSPA, hc, values) } - var expectNoLogin = func(t *testing.T, isAPI, isSPA bool, hc *http.Client, values func(url.Values)) string { + var expectNoRegistration = func(t *testing.T, isAPI, isSPA bool, hc *http.Client, values func(url.Values)) string { useReturnToFromTS(redirNoSessionTS) t.Cleanup(func() { useReturnToFromTS(redirTS) }) - return expectLoginBody(t, redirNoSessionTS, isAPI, isSPA, hc, values) + return expectRegistrationBody(t, redirNoSessionTS, isAPI, isSPA, hc, values) } t.Run("case=should reject invalid transient payload", func(t *testing.T) { @@ -178,7 +180,7 @@ func TestRegistration(t *testing.T) { t.Run("type=api", func(t *testing.T) { username := x.NewUUID().String() - body := expectSuccessfulLogin(t, true, false, nil, func(v url.Values) { + body := expectSuccessfulRegistration(t, true, false, nil, func(v url.Values) { setValues(username, v) }) assert.Equal(t, username, gjson.Get(body, "identity.traits.username").String(), "%s", body) @@ -188,7 +190,7 @@ func TestRegistration(t *testing.T) { t.Run("type=spa", func(t *testing.T) { username := x.NewUUID().String() - body := expectSuccessfulLogin(t, false, true, nil, func(v url.Values) { + body := expectSuccessfulRegistration(t, false, true, nil, func(v url.Values) { setValues(username, v) }) assert.Equal(t, username, gjson.Get(body, "identity.traits.username").String(), "%s", body) @@ -198,7 +200,7 @@ func TestRegistration(t *testing.T) { t.Run("type=browser", func(t *testing.T) { username := x.NewUUID().String() - body := expectSuccessfulLogin(t, false, false, nil, func(v url.Values) { + body := expectSuccessfulRegistration(t, false, false, nil, func(v url.Values) { setValues(username, v) }) assert.Equal(t, username, gjson.Get(body, "identity.traits.username").String(), "%s", body) @@ -213,7 +215,7 @@ func TestRegistration(t *testing.T) { }) t.Run("type=api", func(t *testing.T) { - body := expectSuccessfulLogin(t, true, false, nil, func(v url.Values) { + body := expectSuccessfulRegistration(t, true, false, nil, func(v url.Values) { v.Set("traits.username", "registration-identifier-8-api") v.Set("password", x.NewUUID().String()) v.Set("traits.foobar", "bar") @@ -221,10 +223,11 @@ func TestRegistration(t *testing.T) { assert.Equal(t, `registration-identifier-8-api`, gjson.Get(body, "identity.traits.username").String(), "%s", body) assert.NotEmpty(t, gjson.Get(body, "session_token").String(), "%s", body) assert.NotEmpty(t, gjson.Get(body, "session.id").String(), "%s", body) + assert.NotContains(t, gjson.Get(body, "continue_with").Raw, string(flow.ContinueWithActionRedirectBrowserToString), "%s", body) }) t.Run("type=spa", func(t *testing.T) { - body := expectSuccessfulLogin(t, false, true, nil, func(v url.Values) { + body := expectSuccessfulRegistration(t, false, true, nil, func(v url.Values) { v.Set("traits.username", "registration-identifier-8-spa") v.Set("password", x.NewUUID().String()) v.Set("traits.foobar", "bar") @@ -232,15 +235,17 @@ func TestRegistration(t *testing.T) { assert.Equal(t, `registration-identifier-8-spa`, gjson.Get(body, "identity.traits.username").String(), "%s", body) assert.Empty(t, gjson.Get(body, "session_token").String(), "%s", body) assert.NotEmpty(t, gjson.Get(body, "session.id").String(), "%s", body) + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(body, "continue_with.0.action").String(), "%s", body) }) t.Run("type=browser", func(t *testing.T) { - body := expectSuccessfulLogin(t, false, false, nil, func(v url.Values) { + body := expectSuccessfulRegistration(t, false, false, nil, func(v url.Values) { v.Set("traits.username", "registration-identifier-8-browser") v.Set("password", x.NewUUID().String()) v.Set("traits.foobar", "bar") }) assert.Equal(t, `registration-identifier-8-browser`, gjson.Get(body, "identity.traits.username").String(), "%s", body) + assert.Empty(t, gjson.Get(body, "continue_with").Array(), "%s", body) }) }) @@ -249,7 +254,7 @@ func TestRegistration(t *testing.T) { conf.MustSet(ctx, config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil) t.Run("type=api", func(t *testing.T) { - body := expectNoLogin(t, true, false, nil, func(v url.Values) { + body := expectNoRegistration(t, true, false, nil, func(v url.Values) { v.Set("traits.username", "registration-identifier-8-api-nosession") v.Set("password", x.NewUUID().String()) v.Set("traits.foobar", "bar") @@ -260,7 +265,7 @@ func TestRegistration(t *testing.T) { }) t.Run("type=spa", func(t *testing.T) { - expectNoLogin(t, false, true, nil, func(v url.Values) { + expectNoRegistration(t, false, true, nil, func(v url.Values) { v.Set("traits.username", "registration-identifier-8-spa-nosession") v.Set("password", x.NewUUID().String()) v.Set("traits.foobar", "bar") @@ -268,7 +273,7 @@ func TestRegistration(t *testing.T) { }) t.Run("type=browser", func(t *testing.T) { - expectNoLogin(t, false, false, nil, func(v url.Values) { + expectNoRegistration(t, false, false, nil, func(v url.Values) { v.Set("traits.username", "registration-identifier-8-browser-nosession") v.Set("password", x.NewUUID().String()) v.Set("traits.foobar", "bar") @@ -300,7 +305,7 @@ func TestRegistration(t *testing.T) { v.Set("traits.foobar", "bar") } - _ = expectSuccessfulLogin(t, true, false, apiClient, values) + _ = expectSuccessfulRegistration(t, true, false, apiClient, values) body := testhelpers.SubmitRegistrationForm(t, true, apiClient, publicTS, applyTransform(values, transform), false, http.StatusBadRequest, publicTS.URL+registration.RouteSubmitFlow) @@ -314,7 +319,7 @@ func TestRegistration(t *testing.T) { v.Set("traits.foobar", "bar") } - _ = expectSuccessfulLogin(t, false, true, nil, values) + _ = expectSuccessfulRegistration(t, false, true, nil, values) body := registrationhelpers.ExpectValidationError(t, publicTS, conf, "spa", applyTransform(values, transform)) assert.Contains(t, gjson.Get(body, "ui.messages.0.text").String(), "You tried signing in with registration-identifier-8-spa-duplicate-"+suffix+" which is already in use by another account. You can sign in using your password.", "%s", body) }) @@ -326,7 +331,7 @@ func TestRegistration(t *testing.T) { v.Set("traits.foobar", "bar") } - _ = expectSuccessfulLogin(t, false, false, nil, values) + _ = expectSuccessfulRegistration(t, false, false, nil, values) body := registrationhelpers.ExpectValidationError(t, publicTS, conf, "browser", applyTransform(values, transform)) assert.Contains(t, gjson.Get(body, "ui.messages.0.text").String(), "You tried signing in with registration-identifier-8-browser-duplicate-"+suffix+" which is already in use by another account. You can sign in using your password.", "%s", body) }) @@ -541,7 +546,7 @@ func TestRegistration(t *testing.T) { }) t.Run("type=api", func(t *testing.T) { - actual := expectSuccessfulLogin(t, true, false, nil, func(v url.Values) { + actual := expectSuccessfulRegistration(t, true, false, nil, func(v url.Values) { v.Set("traits.username", "registration-identifier-10-api") v.Set("password", x.NewUUID().String()) v.Set("traits.foobar", "bar") @@ -550,7 +555,7 @@ func TestRegistration(t *testing.T) { }) t.Run("type=spa", func(t *testing.T) { - actual := expectSuccessfulLogin(t, false, false, nil, func(v url.Values) { + actual := expectSuccessfulRegistration(t, false, false, nil, func(v url.Values) { v.Set("traits.username", "registration-identifier-10-spa") v.Set("password", x.NewUUID().String()) v.Set("traits.foobar", "bar") @@ -559,7 +564,7 @@ func TestRegistration(t *testing.T) { }) t.Run("type=browser", func(t *testing.T) { - actual := expectSuccessfulLogin(t, false, false, nil, func(v url.Values) { + actual := expectSuccessfulRegistration(t, false, false, nil, func(v url.Values) { v.Set("traits.username", "registration-identifier-10-browser") v.Set("password", x.NewUUID().String()) v.Set("traits.foobar", "bar") @@ -620,7 +625,7 @@ func TestRegistration(t *testing.T) { username := "registration-custom-schema" t.Run("type=api", func(t *testing.T) { - body := expectNoLogin(t, true, false, nil, func(v url.Values) { + body := expectNoRegistration(t, true, false, nil, func(v url.Values) { v.Set("traits.username", username+"-api") v.Set("password", x.NewUUID().String()) v.Set("traits.baz", "bar") @@ -631,7 +636,7 @@ func TestRegistration(t *testing.T) { }) t.Run("type=spa", func(t *testing.T) { - expectNoLogin(t, false, true, nil, func(v url.Values) { + expectNoRegistration(t, false, true, nil, func(v url.Values) { v.Set("traits.username", username+"-spa") v.Set("password", x.NewUUID().String()) v.Set("traits.baz", "bar") @@ -639,7 +644,7 @@ func TestRegistration(t *testing.T) { }) t.Run("type=browser", func(t *testing.T) { - expectNoLogin(t, false, false, nil, func(v url.Values) { + expectNoRegistration(t, false, false, nil, func(v url.Values) { v.Set("traits.username", username+"-browser") v.Set("password", x.NewUUID().String()) v.Set("traits.baz", "bar") diff --git a/selfservice/strategy/password/settings_test.go b/selfservice/strategy/password/settings_test.go index a4ee7e6c7fa0..49912ee95418 100644 --- a/selfservice/strategy/password/settings_test.go +++ b/selfservice/strategy/password/settings_test.go @@ -13,6 +13,8 @@ import ( "strings" "testing" + "github.com/ory/kratos/selfservice/flow" + "github.com/ory/kratos/internal/settingshelpers" "github.com/ory/kratos/text" @@ -82,7 +84,7 @@ func TestSettings(t *testing.T) { testhelpers.StrategyEnable(t, conf, identity.CredentialsTypePassword.String(), true) testhelpers.StrategyEnable(t, conf, settings.StrategyProfile, true) - _ = testhelpers.NewSettingsUIFlowEchoServer(t, reg) + settingsUI := testhelpers.NewSettingsUIFlowEchoServer(t, reg) _ = testhelpers.NewErrorTestServer(t, reg) _ = testhelpers.NewLoginUIWith401Response(t, conf) conf.MustSet(ctx, config.ViperKeySelfServiceSettingsPrivilegedAuthenticationAfter, "1m") @@ -242,15 +244,20 @@ func TestSettings(t *testing.T) { t.Run("type=api", func(t *testing.T) { actual := testhelpers.SubmitSettingsForm(t, true, false, apiUser1, publicTS, payload, http.StatusOK, publicTS.URL+settings.RouteSubmitFlow) check(t, actual) + assert.Empty(t, gjson.Get(actual, "continue_with").Array(), "%s", actual) }) t.Run("type=spa", func(t *testing.T) { actual := testhelpers.SubmitSettingsForm(t, false, true, browserUser1, publicTS, payload, http.StatusOK, publicTS.URL+settings.RouteSubmitFlow) check(t, actual) + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(actual, "continue_with.0.action").String(), "%s", actual) + assert.Contains(t, gjson.Get(actual, "continue_with.0.redirect_browser_to").String(), settingsUI.URL, "%s", actual) }) t.Run("type=browser", func(t *testing.T) { - check(t, testhelpers.SubmitSettingsForm(t, false, false, browserUser1, publicTS, payload, http.StatusOK, conf.SelfServiceFlowSettingsUI(ctx).String())) + actual := testhelpers.SubmitSettingsForm(t, false, false, browserUser1, publicTS, payload, http.StatusOK, conf.SelfServiceFlowSettingsUI(ctx).String()) + check(t, actual) + assert.Empty(t, gjson.Get(actual, "continue_with").Array(), "%s", actual) }) }) diff --git a/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=spa.json b/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=spa.json new file mode 100644 index 000000000000..d6665e756663 --- /dev/null +++ b/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=spa.json @@ -0,0 +1,153 @@ +{ + "method": "POST", + "nodes": [ + { + "attributes": { + "disabled": false, + "name": "csrf_token", + "node_type": "input", + "required": true, + "type": "hidden" + }, + "group": "default", + "messages": [], + "meta": {}, + "type": "input" + }, + { + "attributes": { + "disabled": false, + "name": "traits.email", + "node_type": "input", + "type": "text" + }, + "group": "profile", + "messages": [], + "meta": {}, + "type": "input" + }, + { + "attributes": { + "disabled": false, + "name": "traits.stringy", + "node_type": "input", + "type": "text", + "value": "foobar" + }, + "group": "profile", + "messages": [], + "meta": {}, + "type": "input" + }, + { + "attributes": { + "disabled": false, + "name": "traits.numby", + "node_type": "input", + "type": "number", + "value": 2.5 + }, + "group": "profile", + "messages": [], + "meta": {}, + "type": "input" + }, + { + "attributes": { + "disabled": false, + "name": "traits.booly", + "node_type": "input", + "type": "checkbox", + "value": false + }, + "group": "profile", + "messages": [], + "meta": {}, + "type": "input" + }, + { + "attributes": { + "disabled": false, + "name": "traits.should_big_number", + "node_type": "input", + "type": "number", + "value": 2048 + }, + "group": "profile", + "messages": [], + "meta": {}, + "type": "input" + }, + { + "attributes": { + "disabled": false, + "name": "traits.should_long_string", + "node_type": "input", + "type": "text", + "value": "asdfasdfasdfasdfasfdasdfasdfasdf" + }, + "group": "profile", + "messages": [], + "meta": {}, + "type": "input" + }, + { + "attributes": { + "disabled": false, + "name": "method", + "node_type": "input", + "type": "submit", + "value": "profile" + }, + "group": "profile", + "messages": [], + "meta": { + "label": { + "id": 1070003, + "text": "Save", + "type": "info" + } + }, + "type": "input" + }, + { + "attributes": { + "autocomplete": "new-password", + "disabled": false, + "name": "password", + "node_type": "input", + "required": true, + "type": "password" + }, + "group": "password", + "messages": [], + "meta": { + "label": { + "id": 1070001, + "text": "Password", + "type": "info" + } + }, + "type": "input" + }, + { + "attributes": { + "disabled": false, + "name": "method", + "node_type": "input", + "type": "submit", + "value": "password" + }, + "group": "password", + "messages": [], + "meta": { + "label": { + "id": 1070003, + "text": "Save", + "type": "info" + } + }, + "type": "input" + } + ] +} diff --git a/selfservice/strategy/profile/strategy_test.go b/selfservice/strategy/profile/strategy_test.go index 7d0c831711c3..92351d5b8d72 100644 --- a/selfservice/strategy/profile/strategy_test.go +++ b/selfservice/strategy/profile/strategy_test.go @@ -210,7 +210,7 @@ func TestStrategyTraits(t *testing.T) { run(t, apiIdentity1, pr, settings.RouteInitAPIFlow) }) - t.Run("type=api", func(t *testing.T) { + t.Run("type=spa", func(t *testing.T) { pr, _, err := testhelpers.NewSDKCustomClient(publicTS, browserUser1).FrontendApi.CreateBrowserSettingsFlow(context.Background()).Execute() require.NoError(t, err) run(t, browserIdentity1, pr, settings.RouteInitBrowserFlow) @@ -449,15 +449,20 @@ func TestStrategyTraits(t *testing.T) { t.Run("type=api", func(t *testing.T) { actual := expectSuccess(t, true, false, apiUser1, payload("not-john-doe-api@mail.com")) check(t, actual) + assert.Empty(t, gjson.Get(actual, "continue_with").Array(), "%s", actual) }) t.Run("type=sqa", func(t *testing.T) { actual := expectSuccess(t, false, true, browserUser1, payload("not-john-doe-browser@mail.com")) check(t, actual) + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(actual, "continue_with.0.action").String(), "%s", actual) + assert.Contains(t, gjson.Get(actual, "continue_with.0.redirect_browser_to").String(), ui.URL, "%s", actual) }) t.Run("type=browser", func(t *testing.T) { - check(t, expectSuccess(t, false, false, browserUser1, payload("not-john-doe-browser@mail.com"))) + actual := expectSuccess(t, false, false, browserUser1, payload("not-john-doe-browser@mail.com")) + check(t, actual) + assert.Empty(t, gjson.Get(actual, "continue_with").Array(), "%s", actual) }) }) diff --git a/selfservice/strategy/totp/login_test.go b/selfservice/strategy/totp/login_test.go index 6456ea7cc599..2eb434256ed8 100644 --- a/selfservice/strategy/totp/login_test.go +++ b/selfservice/strategy/totp/login_test.go @@ -13,6 +13,8 @@ import ( "testing" "time" + "github.com/ory/kratos/selfservice/flow" + "github.com/ory/x/assertx" "github.com/gofrs/uuid" @@ -333,23 +335,36 @@ func TestCompleteLogin(t *testing.T) { t.Run("type=api", func(t *testing.T) { body, res := doAPIFlow(t, payload, id) check(t, false, body, res) + assert.Empty(t, gjson.Get(body, "continue_with").Array(), "%s", body) }) t.Run("type=browser", func(t *testing.T) { body, res := doBrowserFlow(t, false, payload, id, "") check(t, true, body, res) + assert.Empty(t, gjson.Get(body, "continue_with").Array(), "%s", body) }) t.Run("type=browser set return_to", func(t *testing.T) { returnTo := "https://www.ory.sh" - _, res := doBrowserFlow(t, false, payload, id, returnTo) + body, res := doBrowserFlow(t, false, payload, id, returnTo) t.Log(res.Request.URL.String()) assert.Contains(t, res.Request.URL.String(), returnTo) + assert.Empty(t, gjson.Get(body, "continue_with").Array(), "%s", body) }) t.Run("type=spa", func(t *testing.T) { body, res := doBrowserFlow(t, true, payload, id, "") check(t, false, body, res) + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(body, "continue_with.0.action").String(), "%s", body) + assert.EqualValues(t, conf.SelfServiceBrowserDefaultReturnTo(ctx).String(), gjson.Get(body, "continue_with.0.redirect_browser_to").String(), "%s", body) + }) + + t.Run("type=spa set return_to", func(t *testing.T) { + returnTo := "https://www.ory.sh" + body, res := doBrowserFlow(t, true, payload, id, returnTo) + check(t, false, body, res) + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(body, "continue_with.0.action").String(), "%s", body) + assert.EqualValues(t, returnTo, gjson.Get(body, "continue_with.0.redirect_browser_to").String(), "%s", body) }) }) diff --git a/selfservice/strategy/totp/settings_test.go b/selfservice/strategy/totp/settings_test.go index 0fd479f1b220..43d41b2f66aa 100644 --- a/selfservice/strategy/totp/settings_test.go +++ b/selfservice/strategy/totp/settings_test.go @@ -241,6 +241,7 @@ func TestCompleteSettings(t *testing.T) { assert.Contains(t, res.Request.URL.String(), publicTS.URL+settings.RouteSubmitFlow) assert.EqualValues(t, flow.StateSuccess, gjson.Get(actual, "state").String(), actual) checkIdentity(t, id) + assert.Empty(t, gjson.Get(actual, "continue_with").Array(), "%s", actual) }) t.Run("type=spa", func(t *testing.T) { @@ -250,6 +251,9 @@ func TestCompleteSettings(t *testing.T) { assert.Contains(t, res.Request.URL.String(), publicTS.URL+settings.RouteSubmitFlow) assert.EqualValues(t, flow.StateSuccess, gjson.Get(actual, "state").String(), actual) checkIdentity(t, id) + + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(actual, "continue_with.0.action").String(), "%s", actual) + assert.Contains(t, gjson.Get(actual, "continue_with.0.redirect_browser_to").String(), uiTS.URL, "%s", actual) }) t.Run("type=browser", func(t *testing.T) { @@ -259,6 +263,7 @@ func TestCompleteSettings(t *testing.T) { assert.Contains(t, res.Request.URL.String(), uiTS.URL) assert.EqualValues(t, flow.StateSuccess, gjson.Get(actual, "state").String(), actual) checkIdentity(t, id) + assert.Empty(t, gjson.Get(actual, "continue_with").Array(), "%s", actual) }) }) @@ -344,6 +349,13 @@ func TestCompleteSettings(t *testing.T) { checkIdentity(t, id, key) testhelpers.EnsureAAL(t, hc, publicTS, "aal2", string(identity.CredentialsTypeTOTP)) + + if isSPA { + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(actual, "continue_with.0.action").String(), "%s", actual) + assert.Contains(t, gjson.Get(actual, "continue_with.0.redirect_browser_to").String(), uiTS.URL, "%s", actual) + } else { + assert.Empty(t, gjson.Get(actual, "continue_with").Array(), "%s", actual) + } } t.Run("type=api", func(t *testing.T) { diff --git a/selfservice/strategy/webauthn/login_test.go b/selfservice/strategy/webauthn/login_test.go index f5d332182163..46db972c4cc7 100644 --- a/selfservice/strategy/webauthn/login_test.go +++ b/selfservice/strategy/webauthn/login_test.go @@ -446,6 +446,13 @@ func TestCompleteLogin(t *testing.T) { actualFlow, err := reg.LoginFlowPersister().GetLoginFlow(context.Background(), uuid.FromStringOrNil(f.Id)) require.NoError(t, err) assert.Empty(t, gjson.GetBytes(actualFlow.InternalContext, flow.PrefixInternalContextKey(identity.CredentialsTypeWebAuthn, webauthn.InternalContextKeySessionData))) + + if spa { + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(body, "continue_with.0.action").String(), "%s", body) + assert.Contains(t, gjson.Get(body, "continue_with.0.redirect_browser_to").String(), conf.SelfServiceBrowserDefaultReturnTo(ctx).String(), "%s", body) + } else { + assert.Empty(t, gjson.Get(body, "continue_with").Array(), "%s", body) + } } t.Run("type=browser", func(t *testing.T) { diff --git a/selfservice/strategy/webauthn/registration_test.go b/selfservice/strategy/webauthn/registration_test.go index c0503b151ed8..973e1ae0ec81 100644 --- a/selfservice/strategy/webauthn/registration_test.go +++ b/selfservice/strategy/webauthn/registration_test.go @@ -367,6 +367,13 @@ func TestRegistration(t *testing.T) { i, _, err := reg.PrivilegedIdentityPool().FindByCredentialsIdentifier(context.Background(), identity.CredentialsTypeWebAuthn, email) require.NoError(t, err) assert.Equal(t, email, gjson.GetBytes(i.Traits, "username").String(), "%s", actual) + + if f == "spa" { + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(actual, "continue_with.0.action").String(), "%s", actual) + assert.Contains(t, gjson.Get(actual, "continue_with.0.redirect_browser_to").String(), redirNoSessionTS.URL+"/registration-return-ts", "%s", actual) + } else { + assert.Empty(t, gjson.Get(actual, "continue_with").Array(), "%s", actual) + } }) } }) diff --git a/selfservice/strategy/webauthn/settings_test.go b/selfservice/strategy/webauthn/settings_test.go index acf4fd357b1d..3b46cc8de752 100644 --- a/selfservice/strategy/webauthn/settings_test.go +++ b/selfservice/strategy/webauthn/settings_test.go @@ -465,6 +465,13 @@ func TestCompleteSettings(t *testing.T) { assert.Contains(t, res.Request.URL.String(), uiTS.URL) } assert.EqualValues(t, flow.StateSuccess, gjson.Get(body, "state").String(), body) + + if spa { + assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(body, "continue_with.0.action").String(), "%s", body) + assert.Contains(t, gjson.Get(body, "continue_with.0.redirect_browser_to").String(), uiTS.URL, "%s", body) + } else { + assert.Empty(t, gjson.Get(body, "continue_with").Array(), "%s", body) + } } actual, err := reg.Persister().GetIdentityConfidential(context.Background(), id.ID) @@ -474,6 +481,7 @@ func TestCompleteSettings(t *testing.T) { // Check not to remove other credentials with webauthn _, ok = actual.GetCredentials(identity.CredentialsTypePassword) assert.True(t, ok) + } t.Run("type=browser", func(t *testing.T) { diff --git a/spec/api.json b/spec/api.json index 48b0d934d382..193b0f808a47 100644 --- a/spec/api.json +++ b/spec/api.json @@ -465,6 +465,7 @@ "continueWith": { "discriminator": { "mapping": { + "redirect_browser_to": "#/components/schemas/continueWithRedirectBrowserTo", "set_ory_session_token": "#/components/schemas/continueWithSetOrySessionToken", "show_recovery_ui": "#/components/schemas/continueWithRecoveryUi", "show_settings_ui": "#/components/schemas/continueWithSettingsUi", @@ -484,6 +485,9 @@ }, { "$ref": "#/components/schemas/continueWithRecoveryUi" + }, + { + "$ref": "#/components/schemas/continueWithRedirectBrowserTo" } ] }, @@ -525,6 +529,22 @@ ], "type": "object" }, + "continueWithRedirectBrowserTo": { + "description": "Indicates, that the UI flow could be continued by showing a recovery ui", + "properties": { + "action": { + "description": "Action will always be `redirect_browser_to`" + }, + "redirect_browser_to": { + "description": "The URL to redirect the browser to", + "type": "string" + } + }, + "required": [ + "action" + ], + "type": "object" + }, "continueWithSetOrySessionToken": { "description": "Indicates that a session was issued, and the application should use this token for authenticated requests", "properties": { diff --git a/spec/swagger.json b/spec/swagger.json index 1d548df9a00f..bf4d3181b9f9 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -3650,6 +3650,22 @@ } } }, + "continueWithRedirectBrowserTo": { + "description": "Indicates, that the UI flow could be continued by showing a recovery ui", + "type": "object", + "required": [ + "action" + ], + "properties": { + "action": { + "description": "Action will always be `redirect_browser_to`" + }, + "redirect_browser_to": { + "description": "The URL to redirect the browser to", + "type": "string" + } + } + }, "continueWithSetOrySessionToken": { "description": "Indicates that a session was issued, and the application should use this token for authenticated requests", "type": "object", From 52222a6d8ef291bfb382956c7141dc190855516d Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:00:49 +0200 Subject: [PATCH 2/6] feat: add browser return_to continue_with action --- .../model_continue_with_recovery_ui_flow.go | 2 +- ...model_continue_with_redirect_browser_to.go | 51 ++++++++----------- .../model_continue_with_settings_ui_flow.go | 37 ++++++++++++++ ...odel_continue_with_verification_ui_flow.go | 2 +- .../model_continue_with_recovery_ui_flow.go | 2 +- ...model_continue_with_redirect_browser_to.go | 51 ++++++++----------- .../model_continue_with_settings_ui_flow.go | 37 ++++++++++++++ ...odel_continue_with_verification_ui_flow.go | 2 +- selfservice/flow/continue_with.go | 23 +++++++-- .../strategy/code/strategy_recovery.go | 5 +- spec/api.json | 18 +++++-- spec/swagger.json | 18 +++++-- 12 files changed, 171 insertions(+), 77 deletions(-) diff --git a/internal/client-go/model_continue_with_recovery_ui_flow.go b/internal/client-go/model_continue_with_recovery_ui_flow.go index 3fde7e717ef2..251725a73c3b 100644 --- a/internal/client-go/model_continue_with_recovery_ui_flow.go +++ b/internal/client-go/model_continue_with_recovery_ui_flow.go @@ -19,7 +19,7 @@ import ( type ContinueWithRecoveryUiFlow struct { // The ID of the recovery flow Id string `json:"id"` - // The URL of the recovery flow + // The URL of the recovery flow If this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows. Url *string `json:"url,omitempty"` } diff --git a/internal/client-go/model_continue_with_redirect_browser_to.go b/internal/client-go/model_continue_with_redirect_browser_to.go index 46344016b779..20c3e4f3c562 100644 --- a/internal/client-go/model_continue_with_redirect_browser_to.go +++ b/internal/client-go/model_continue_with_redirect_browser_to.go @@ -17,19 +17,20 @@ import ( // ContinueWithRedirectBrowserTo Indicates, that the UI flow could be continued by showing a recovery ui type ContinueWithRedirectBrowserTo struct { - // Action will always be `redirect_browser_to` - Action interface{} `json:"action"` + // Action will always be `redirect_browser_to` redirect_browser_to ContinueWithActionRedirectBrowserToString + Action string `json:"action"` // The URL to redirect the browser to - RedirectBrowserTo *string `json:"redirect_browser_to,omitempty"` + RedirectBrowserTo string `json:"redirect_browser_to"` } // NewContinueWithRedirectBrowserTo instantiates a new ContinueWithRedirectBrowserTo 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 NewContinueWithRedirectBrowserTo(action interface{}) *ContinueWithRedirectBrowserTo { +func NewContinueWithRedirectBrowserTo(action string, redirectBrowserTo string) *ContinueWithRedirectBrowserTo { this := ContinueWithRedirectBrowserTo{} this.Action = action + this.RedirectBrowserTo = redirectBrowserTo return &this } @@ -42,10 +43,9 @@ func NewContinueWithRedirectBrowserToWithDefaults() *ContinueWithRedirectBrowser } // GetAction returns the Action field value -// If the value is explicit nil, the zero value for interface{} will be returned -func (o *ContinueWithRedirectBrowserTo) GetAction() interface{} { +func (o *ContinueWithRedirectBrowserTo) GetAction() string { if o == nil { - var ret interface{} + var ret string return ret } @@ -54,57 +54,48 @@ func (o *ContinueWithRedirectBrowserTo) GetAction() interface{} { // GetActionOk returns a tuple with the Action field value // and a boolean to check if the value has been set. -// NOTE: If the value is an explicit nil, `nil, true` will be returned -func (o *ContinueWithRedirectBrowserTo) GetActionOk() (*interface{}, bool) { - if o == nil || o.Action == nil { +func (o *ContinueWithRedirectBrowserTo) GetActionOk() (*string, bool) { + if o == nil { return nil, false } return &o.Action, true } // SetAction sets field value -func (o *ContinueWithRedirectBrowserTo) SetAction(v interface{}) { +func (o *ContinueWithRedirectBrowserTo) SetAction(v string) { o.Action = v } -// GetRedirectBrowserTo returns the RedirectBrowserTo field value if set, zero value otherwise. +// GetRedirectBrowserTo returns the RedirectBrowserTo field value func (o *ContinueWithRedirectBrowserTo) GetRedirectBrowserTo() string { - if o == nil || o.RedirectBrowserTo == nil { + if o == nil { var ret string return ret } - return *o.RedirectBrowserTo + + return o.RedirectBrowserTo } -// GetRedirectBrowserToOk returns a tuple with the RedirectBrowserTo field value if set, nil otherwise +// GetRedirectBrowserToOk returns a tuple with the RedirectBrowserTo field value // and a boolean to check if the value has been set. func (o *ContinueWithRedirectBrowserTo) GetRedirectBrowserToOk() (*string, bool) { - if o == nil || o.RedirectBrowserTo == nil { + if o == nil { return nil, false } - return o.RedirectBrowserTo, true -} - -// HasRedirectBrowserTo returns a boolean if a field has been set. -func (o *ContinueWithRedirectBrowserTo) HasRedirectBrowserTo() bool { - if o != nil && o.RedirectBrowserTo != nil { - return true - } - - return false + return &o.RedirectBrowserTo, true } -// SetRedirectBrowserTo gets a reference to the given string and assigns it to the RedirectBrowserTo field. +// SetRedirectBrowserTo sets field value func (o *ContinueWithRedirectBrowserTo) SetRedirectBrowserTo(v string) { - o.RedirectBrowserTo = &v + o.RedirectBrowserTo = v } func (o ContinueWithRedirectBrowserTo) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} - if o.Action != nil { + if true { toSerialize["action"] = o.Action } - if o.RedirectBrowserTo != nil { + if true { toSerialize["redirect_browser_to"] = o.RedirectBrowserTo } return json.Marshal(toSerialize) diff --git a/internal/client-go/model_continue_with_settings_ui_flow.go b/internal/client-go/model_continue_with_settings_ui_flow.go index 4ccaf74ef1b8..d6e9b9441f99 100644 --- a/internal/client-go/model_continue_with_settings_ui_flow.go +++ b/internal/client-go/model_continue_with_settings_ui_flow.go @@ -19,6 +19,8 @@ import ( type ContinueWithSettingsUiFlow struct { // The ID of the settings flow Id string `json:"id"` + // The URL of the settings flow If this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows. + Url *string `json:"url,omitempty"` } // NewContinueWithSettingsUiFlow instantiates a new ContinueWithSettingsUiFlow object @@ -63,11 +65,46 @@ func (o *ContinueWithSettingsUiFlow) SetId(v string) { o.Id = v } +// GetUrl returns the Url field value if set, zero value otherwise. +func (o *ContinueWithSettingsUiFlow) 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 *ContinueWithSettingsUiFlow) 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 *ContinueWithSettingsUiFlow) 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 *ContinueWithSettingsUiFlow) SetUrl(v string) { + o.Url = &v +} + func (o ContinueWithSettingsUiFlow) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} if true { toSerialize["id"] = o.Id } + if o.Url != nil { + toSerialize["url"] = o.Url + } return json.Marshal(toSerialize) } diff --git a/internal/client-go/model_continue_with_verification_ui_flow.go b/internal/client-go/model_continue_with_verification_ui_flow.go index 8fdd4609cf93..3c73a0761339 100644 --- a/internal/client-go/model_continue_with_verification_ui_flow.go +++ b/internal/client-go/model_continue_with_verification_ui_flow.go @@ -19,7 +19,7 @@ import ( type ContinueWithVerificationUiFlow struct { // The ID of the verification flow Id string `json:"id"` - // The URL of the verification flow + // The URL of the verification flow If this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows. Url *string `json:"url,omitempty"` // The address that should be verified in this flow VerifiableAddress string `json:"verifiable_address"` diff --git a/internal/httpclient/model_continue_with_recovery_ui_flow.go b/internal/httpclient/model_continue_with_recovery_ui_flow.go index 3fde7e717ef2..251725a73c3b 100644 --- a/internal/httpclient/model_continue_with_recovery_ui_flow.go +++ b/internal/httpclient/model_continue_with_recovery_ui_flow.go @@ -19,7 +19,7 @@ import ( type ContinueWithRecoveryUiFlow struct { // The ID of the recovery flow Id string `json:"id"` - // The URL of the recovery flow + // The URL of the recovery flow If this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows. Url *string `json:"url,omitempty"` } diff --git a/internal/httpclient/model_continue_with_redirect_browser_to.go b/internal/httpclient/model_continue_with_redirect_browser_to.go index 46344016b779..20c3e4f3c562 100644 --- a/internal/httpclient/model_continue_with_redirect_browser_to.go +++ b/internal/httpclient/model_continue_with_redirect_browser_to.go @@ -17,19 +17,20 @@ import ( // ContinueWithRedirectBrowserTo Indicates, that the UI flow could be continued by showing a recovery ui type ContinueWithRedirectBrowserTo struct { - // Action will always be `redirect_browser_to` - Action interface{} `json:"action"` + // Action will always be `redirect_browser_to` redirect_browser_to ContinueWithActionRedirectBrowserToString + Action string `json:"action"` // The URL to redirect the browser to - RedirectBrowserTo *string `json:"redirect_browser_to,omitempty"` + RedirectBrowserTo string `json:"redirect_browser_to"` } // NewContinueWithRedirectBrowserTo instantiates a new ContinueWithRedirectBrowserTo 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 NewContinueWithRedirectBrowserTo(action interface{}) *ContinueWithRedirectBrowserTo { +func NewContinueWithRedirectBrowserTo(action string, redirectBrowserTo string) *ContinueWithRedirectBrowserTo { this := ContinueWithRedirectBrowserTo{} this.Action = action + this.RedirectBrowserTo = redirectBrowserTo return &this } @@ -42,10 +43,9 @@ func NewContinueWithRedirectBrowserToWithDefaults() *ContinueWithRedirectBrowser } // GetAction returns the Action field value -// If the value is explicit nil, the zero value for interface{} will be returned -func (o *ContinueWithRedirectBrowserTo) GetAction() interface{} { +func (o *ContinueWithRedirectBrowserTo) GetAction() string { if o == nil { - var ret interface{} + var ret string return ret } @@ -54,57 +54,48 @@ func (o *ContinueWithRedirectBrowserTo) GetAction() interface{} { // GetActionOk returns a tuple with the Action field value // and a boolean to check if the value has been set. -// NOTE: If the value is an explicit nil, `nil, true` will be returned -func (o *ContinueWithRedirectBrowserTo) GetActionOk() (*interface{}, bool) { - if o == nil || o.Action == nil { +func (o *ContinueWithRedirectBrowserTo) GetActionOk() (*string, bool) { + if o == nil { return nil, false } return &o.Action, true } // SetAction sets field value -func (o *ContinueWithRedirectBrowserTo) SetAction(v interface{}) { +func (o *ContinueWithRedirectBrowserTo) SetAction(v string) { o.Action = v } -// GetRedirectBrowserTo returns the RedirectBrowserTo field value if set, zero value otherwise. +// GetRedirectBrowserTo returns the RedirectBrowserTo field value func (o *ContinueWithRedirectBrowserTo) GetRedirectBrowserTo() string { - if o == nil || o.RedirectBrowserTo == nil { + if o == nil { var ret string return ret } - return *o.RedirectBrowserTo + + return o.RedirectBrowserTo } -// GetRedirectBrowserToOk returns a tuple with the RedirectBrowserTo field value if set, nil otherwise +// GetRedirectBrowserToOk returns a tuple with the RedirectBrowserTo field value // and a boolean to check if the value has been set. func (o *ContinueWithRedirectBrowserTo) GetRedirectBrowserToOk() (*string, bool) { - if o == nil || o.RedirectBrowserTo == nil { + if o == nil { return nil, false } - return o.RedirectBrowserTo, true -} - -// HasRedirectBrowserTo returns a boolean if a field has been set. -func (o *ContinueWithRedirectBrowserTo) HasRedirectBrowserTo() bool { - if o != nil && o.RedirectBrowserTo != nil { - return true - } - - return false + return &o.RedirectBrowserTo, true } -// SetRedirectBrowserTo gets a reference to the given string and assigns it to the RedirectBrowserTo field. +// SetRedirectBrowserTo sets field value func (o *ContinueWithRedirectBrowserTo) SetRedirectBrowserTo(v string) { - o.RedirectBrowserTo = &v + o.RedirectBrowserTo = v } func (o ContinueWithRedirectBrowserTo) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} - if o.Action != nil { + if true { toSerialize["action"] = o.Action } - if o.RedirectBrowserTo != nil { + if true { toSerialize["redirect_browser_to"] = o.RedirectBrowserTo } return json.Marshal(toSerialize) diff --git a/internal/httpclient/model_continue_with_settings_ui_flow.go b/internal/httpclient/model_continue_with_settings_ui_flow.go index 4ccaf74ef1b8..d6e9b9441f99 100644 --- a/internal/httpclient/model_continue_with_settings_ui_flow.go +++ b/internal/httpclient/model_continue_with_settings_ui_flow.go @@ -19,6 +19,8 @@ import ( type ContinueWithSettingsUiFlow struct { // The ID of the settings flow Id string `json:"id"` + // The URL of the settings flow If this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows. + Url *string `json:"url,omitempty"` } // NewContinueWithSettingsUiFlow instantiates a new ContinueWithSettingsUiFlow object @@ -63,11 +65,46 @@ func (o *ContinueWithSettingsUiFlow) SetId(v string) { o.Id = v } +// GetUrl returns the Url field value if set, zero value otherwise. +func (o *ContinueWithSettingsUiFlow) 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 *ContinueWithSettingsUiFlow) 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 *ContinueWithSettingsUiFlow) 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 *ContinueWithSettingsUiFlow) SetUrl(v string) { + o.Url = &v +} + func (o ContinueWithSettingsUiFlow) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} if true { toSerialize["id"] = o.Id } + if o.Url != nil { + toSerialize["url"] = o.Url + } return json.Marshal(toSerialize) } diff --git a/internal/httpclient/model_continue_with_verification_ui_flow.go b/internal/httpclient/model_continue_with_verification_ui_flow.go index 8fdd4609cf93..3c73a0761339 100644 --- a/internal/httpclient/model_continue_with_verification_ui_flow.go +++ b/internal/httpclient/model_continue_with_verification_ui_flow.go @@ -19,7 +19,7 @@ import ( type ContinueWithVerificationUiFlow struct { // The ID of the verification flow Id string `json:"id"` - // The URL of the verification flow + // The URL of the verification flow If this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows. Url *string `json:"url,omitempty"` // The address that should be verified in this flow VerifiableAddress string `json:"verifiable_address"` diff --git a/selfservice/flow/continue_with.go b/selfservice/flow/continue_with.go index 5b56bbf9aab6..9bc9e6152d78 100644 --- a/selfservice/flow/continue_with.go +++ b/selfservice/flow/continue_with.go @@ -89,6 +89,8 @@ type ContinueWithVerificationUIFlow struct { // The URL of the verification flow // + // If this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows. + // // required: false URL string `json:"url,omitempty"` } @@ -134,8 +136,11 @@ type ContinueWithSettingsUI struct { // // required: true Action ContinueWithActionShowSettingsUI `json:"action"` + // Flow contains the ID of the verification flow // + // If this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows. + // // required: true Flow ContinueWithSettingsUIFlow `json:"flow"` } @@ -146,13 +151,21 @@ type ContinueWithSettingsUIFlow struct { // // required: true ID uuid.UUID `json:"id"` + + // The URL of the settings flow + // + // If this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows. + // + // required: false + URL string `json:"url,omitempty"` } -func NewContinueWithSettingsUI(f Flow) *ContinueWithSettingsUI { +func NewContinueWithSettingsUI(f Flow, redirectTo string) *ContinueWithSettingsUI { return &ContinueWithSettingsUI{ Action: ContinueWithActionShowSettingsUIString, Flow: ContinueWithSettingsUIFlow{ - ID: f.GetID(), + ID: f.GetID(), + URL: redirectTo, }, } } @@ -188,6 +201,8 @@ type ContinueWithRecoveryUIFlow struct { // The URL of the recovery flow // + // If this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows. + // // required: false URL string `json:"url,omitempty"` } @@ -201,7 +216,7 @@ func NewContinueWithRecoveryUI(f Flow) *ContinueWithRecoveryUI { } } -// swagger:enum ContinueWithActionRedirectTo +// swagger:enum ContinueWithActionRedirectBrowserTo type ContinueWithActionRedirectBrowserTo string // #nosec G101 -- only a key constant @@ -219,6 +234,8 @@ type ContinueWithRedirectBrowserTo struct { Action ContinueWithActionRedirectBrowserTo `json:"action"` // The URL to redirect the browser to + // + // required: true RedirectTo string `json:"redirect_browser_to"` } diff --git a/selfservice/strategy/code/strategy_recovery.go b/selfservice/strategy/code/strategy_recovery.go index 0cbddf393325..305fc73c6327 100644 --- a/selfservice/strategy/code/strategy_recovery.go +++ b/selfservice/strategy/code/strategy_recovery.go @@ -236,14 +236,15 @@ func (s *Strategy) recoveryIssueSession(w http.ResponseWriter, r *http.Request, } if s.deps.Config().UseContinueWithTransitions(ctx) { + redirectTo := sf.AppendTo(s.deps.Config().SelfServiceFlowSettingsUI(r.Context())).String() switch { case f.Type.IsAPI(): fallthrough case x.IsJSONRequest(r): - f.ContinueWith = append(f.ContinueWith, flow.NewContinueWithSettingsUI(sf)) + f.ContinueWith = append(f.ContinueWith, flow.NewContinueWithSettingsUI(sf, redirectTo)) s.deps.Writer().Write(w, r, f) default: - http.Redirect(w, r, sf.AppendTo(s.deps.Config().SelfServiceFlowSettingsUI(r.Context())).String(), http.StatusSeeOther) + http.Redirect(w, r, redirectTo, http.StatusSeeOther) } } else { if x.IsJSONRequest(r) { diff --git a/spec/api.json b/spec/api.json index 193b0f808a47..180dd98b5dd0 100644 --- a/spec/api.json +++ b/spec/api.json @@ -520,7 +520,7 @@ "type": "string" }, "url": { - "description": "The URL of the recovery flow", + "description": "The URL of the recovery flow\n\nIf this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows.", "type": "string" } }, @@ -533,7 +533,12 @@ "description": "Indicates, that the UI flow could be continued by showing a recovery ui", "properties": { "action": { - "description": "Action will always be `redirect_browser_to`" + "description": "Action will always be `redirect_browser_to`\nredirect_browser_to ContinueWithActionRedirectBrowserToString", + "enum": [ + "redirect_browser_to" + ], + "type": "string", + "x-go-enum-desc": "redirect_browser_to ContinueWithActionRedirectBrowserToString" }, "redirect_browser_to": { "description": "The URL to redirect the browser to", @@ -541,7 +546,8 @@ } }, "required": [ - "action" + "action", + "redirect_browser_to" ], "type": "object" }, @@ -594,6 +600,10 @@ "description": "The ID of the settings flow", "format": "uuid", "type": "string" + }, + "url": { + "description": "The URL of the settings flow\n\nIf this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows.", + "type": "string" } }, "required": [ @@ -630,7 +640,7 @@ "type": "string" }, "url": { - "description": "The URL of the verification flow", + "description": "The URL of the verification flow\n\nIf this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows.", "type": "string" }, "verifiable_address": { diff --git a/spec/swagger.json b/spec/swagger.json index bf4d3181b9f9..9498a51dee44 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -3645,7 +3645,7 @@ "format": "uuid" }, "url": { - "description": "The URL of the recovery flow", + "description": "The URL of the recovery flow\n\nIf this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows.", "type": "string" } } @@ -3654,11 +3654,17 @@ "description": "Indicates, that the UI flow could be continued by showing a recovery ui", "type": "object", "required": [ - "action" + "action", + "redirect_browser_to" ], "properties": { "action": { - "description": "Action will always be `redirect_browser_to`" + "description": "Action will always be `redirect_browser_to`\nredirect_browser_to ContinueWithActionRedirectBrowserToString", + "type": "string", + "enum": [ + "redirect_browser_to" + ], + "x-go-enum-desc": "redirect_browser_to ContinueWithActionRedirectBrowserToString" }, "redirect_browser_to": { "description": "The URL to redirect the browser to", @@ -3719,6 +3725,10 @@ "description": "The ID of the settings flow", "type": "string", "format": "uuid" + }, + "url": { + "description": "The URL of the settings flow\n\nIf this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows.", + "type": "string" } } }, @@ -3756,7 +3766,7 @@ "format": "uuid" }, "url": { - "description": "The URL of the verification flow", + "description": "The URL of the verification flow\n\nIf this value is set, redirect the user's browser to this URL. This value is typically unset for native clients / API flows.", "type": "string" }, "verifiable_address": { From 970408d7129725c95d7f857ed30f034eeb22f476 Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Tue, 23 Apr 2024 12:11:37 +0200 Subject: [PATCH 3/6] feat(sdk): add missing profile discriminator to update registration --- .schema/openapi/patches/selfservice.yaml | 4 +- .../model_update_registration_flow_body.go | 44 ++++++++++++++++++- .../model_update_registration_flow_body.go | 44 ++++++++++++++++++- spec/api.json | 6 ++- 4 files changed, 92 insertions(+), 6 deletions(-) diff --git a/.schema/openapi/patches/selfservice.yaml b/.schema/openapi/patches/selfservice.yaml index 81d82247586b..db9d7d3e6720 100644 --- a/.schema/openapi/patches/selfservice.yaml +++ b/.schema/openapi/patches/selfservice.yaml @@ -19,6 +19,7 @@ - "$ref": "#/components/schemas/updateRegistrationFlowWithWebAuthnMethod" - "$ref": "#/components/schemas/updateRegistrationFlowWithCodeMethod" - "$ref": "#/components/schemas/updateRegistrationFlowWithPasskeyMethod" + - "$ref": "#/components/schemas/updateRegistrationFlowWithProfileMethod" - op: add path: /components/schemas/updateRegistrationFlowBody/discriminator value: @@ -28,7 +29,8 @@ oidc: "#/components/schemas/updateRegistrationFlowWithOidcMethod" webauthn: "#/components/schemas/updateRegistrationFlowWithWebAuthnMethod" code: "#/components/schemas/updateRegistrationFlowWithCodeMethod" - passKey: "#/components/schemas/updateRegistrationFlowWithPasskeyMethod" + passkey: "#/components/schemas/updateRegistrationFlowWithPasskeyMethod" + profile: "#/components/schemas/updateRegistrationFlowWithProfileMethod" - op: add path: /components/schemas/registrationFlowState/enum value: diff --git a/internal/client-go/model_update_registration_flow_body.go b/internal/client-go/model_update_registration_flow_body.go index 64374c620f8f..82a578cfc4d3 100644 --- a/internal/client-go/model_update_registration_flow_body.go +++ b/internal/client-go/model_update_registration_flow_body.go @@ -22,6 +22,7 @@ type UpdateRegistrationFlowBody struct { UpdateRegistrationFlowWithOidcMethod *UpdateRegistrationFlowWithOidcMethod UpdateRegistrationFlowWithPasskeyMethod *UpdateRegistrationFlowWithPasskeyMethod UpdateRegistrationFlowWithPasswordMethod *UpdateRegistrationFlowWithPasswordMethod + UpdateRegistrationFlowWithProfileMethod *UpdateRegistrationFlowWithProfileMethod UpdateRegistrationFlowWithWebAuthnMethod *UpdateRegistrationFlowWithWebAuthnMethod } @@ -53,6 +54,13 @@ func UpdateRegistrationFlowWithPasswordMethodAsUpdateRegistrationFlowBody(v *Upd } } +// UpdateRegistrationFlowWithProfileMethodAsUpdateRegistrationFlowBody is a convenience function that returns UpdateRegistrationFlowWithProfileMethod wrapped in UpdateRegistrationFlowBody +func UpdateRegistrationFlowWithProfileMethodAsUpdateRegistrationFlowBody(v *UpdateRegistrationFlowWithProfileMethod) UpdateRegistrationFlowBody { + return UpdateRegistrationFlowBody{ + UpdateRegistrationFlowWithProfileMethod: v, + } +} + // UpdateRegistrationFlowWithWebAuthnMethodAsUpdateRegistrationFlowBody is a convenience function that returns UpdateRegistrationFlowWithWebAuthnMethod wrapped in UpdateRegistrationFlowBody func UpdateRegistrationFlowWithWebAuthnMethodAsUpdateRegistrationFlowBody(v *UpdateRegistrationFlowWithWebAuthnMethod) UpdateRegistrationFlowBody { return UpdateRegistrationFlowBody{ @@ -94,8 +102,8 @@ func (dst *UpdateRegistrationFlowBody) UnmarshalJSON(data []byte) error { } } - // check if the discriminator value is 'passKey' - if jsonDict["method"] == "passKey" { + // check if the discriminator value is 'passkey' + if jsonDict["method"] == "passkey" { // try to unmarshal JSON data into UpdateRegistrationFlowWithPasskeyMethod err = json.Unmarshal(data, &dst.UpdateRegistrationFlowWithPasskeyMethod) if err == nil { @@ -118,6 +126,18 @@ func (dst *UpdateRegistrationFlowBody) UnmarshalJSON(data []byte) error { } } + // check if the discriminator value is 'profile' + if jsonDict["method"] == "profile" { + // try to unmarshal JSON data into UpdateRegistrationFlowWithProfileMethod + err = json.Unmarshal(data, &dst.UpdateRegistrationFlowWithProfileMethod) + if err == nil { + return nil // data stored in dst.UpdateRegistrationFlowWithProfileMethod, return on the first match + } else { + dst.UpdateRegistrationFlowWithProfileMethod = nil + return fmt.Errorf("Failed to unmarshal UpdateRegistrationFlowBody as UpdateRegistrationFlowWithProfileMethod: %s", err.Error()) + } + } + // check if the discriminator value is 'webauthn' if jsonDict["method"] == "webauthn" { // try to unmarshal JSON data into UpdateRegistrationFlowWithWebAuthnMethod @@ -178,6 +198,18 @@ func (dst *UpdateRegistrationFlowBody) UnmarshalJSON(data []byte) error { } } + // check if the discriminator value is 'updateRegistrationFlowWithProfileMethod' + if jsonDict["method"] == "updateRegistrationFlowWithProfileMethod" { + // try to unmarshal JSON data into UpdateRegistrationFlowWithProfileMethod + err = json.Unmarshal(data, &dst.UpdateRegistrationFlowWithProfileMethod) + if err == nil { + return nil // data stored in dst.UpdateRegistrationFlowWithProfileMethod, return on the first match + } else { + dst.UpdateRegistrationFlowWithProfileMethod = nil + return fmt.Errorf("Failed to unmarshal UpdateRegistrationFlowBody as UpdateRegistrationFlowWithProfileMethod: %s", err.Error()) + } + } + // check if the discriminator value is 'updateRegistrationFlowWithWebAuthnMethod' if jsonDict["method"] == "updateRegistrationFlowWithWebAuthnMethod" { // try to unmarshal JSON data into UpdateRegistrationFlowWithWebAuthnMethod @@ -211,6 +243,10 @@ func (src UpdateRegistrationFlowBody) MarshalJSON() ([]byte, error) { return json.Marshal(&src.UpdateRegistrationFlowWithPasswordMethod) } + if src.UpdateRegistrationFlowWithProfileMethod != nil { + return json.Marshal(&src.UpdateRegistrationFlowWithProfileMethod) + } + if src.UpdateRegistrationFlowWithWebAuthnMethod != nil { return json.Marshal(&src.UpdateRegistrationFlowWithWebAuthnMethod) } @@ -239,6 +275,10 @@ func (obj *UpdateRegistrationFlowBody) GetActualInstance() interface{} { return obj.UpdateRegistrationFlowWithPasswordMethod } + if obj.UpdateRegistrationFlowWithProfileMethod != nil { + return obj.UpdateRegistrationFlowWithProfileMethod + } + if obj.UpdateRegistrationFlowWithWebAuthnMethod != nil { return obj.UpdateRegistrationFlowWithWebAuthnMethod } diff --git a/internal/httpclient/model_update_registration_flow_body.go b/internal/httpclient/model_update_registration_flow_body.go index 64374c620f8f..82a578cfc4d3 100644 --- a/internal/httpclient/model_update_registration_flow_body.go +++ b/internal/httpclient/model_update_registration_flow_body.go @@ -22,6 +22,7 @@ type UpdateRegistrationFlowBody struct { UpdateRegistrationFlowWithOidcMethod *UpdateRegistrationFlowWithOidcMethod UpdateRegistrationFlowWithPasskeyMethod *UpdateRegistrationFlowWithPasskeyMethod UpdateRegistrationFlowWithPasswordMethod *UpdateRegistrationFlowWithPasswordMethod + UpdateRegistrationFlowWithProfileMethod *UpdateRegistrationFlowWithProfileMethod UpdateRegistrationFlowWithWebAuthnMethod *UpdateRegistrationFlowWithWebAuthnMethod } @@ -53,6 +54,13 @@ func UpdateRegistrationFlowWithPasswordMethodAsUpdateRegistrationFlowBody(v *Upd } } +// UpdateRegistrationFlowWithProfileMethodAsUpdateRegistrationFlowBody is a convenience function that returns UpdateRegistrationFlowWithProfileMethod wrapped in UpdateRegistrationFlowBody +func UpdateRegistrationFlowWithProfileMethodAsUpdateRegistrationFlowBody(v *UpdateRegistrationFlowWithProfileMethod) UpdateRegistrationFlowBody { + return UpdateRegistrationFlowBody{ + UpdateRegistrationFlowWithProfileMethod: v, + } +} + // UpdateRegistrationFlowWithWebAuthnMethodAsUpdateRegistrationFlowBody is a convenience function that returns UpdateRegistrationFlowWithWebAuthnMethod wrapped in UpdateRegistrationFlowBody func UpdateRegistrationFlowWithWebAuthnMethodAsUpdateRegistrationFlowBody(v *UpdateRegistrationFlowWithWebAuthnMethod) UpdateRegistrationFlowBody { return UpdateRegistrationFlowBody{ @@ -94,8 +102,8 @@ func (dst *UpdateRegistrationFlowBody) UnmarshalJSON(data []byte) error { } } - // check if the discriminator value is 'passKey' - if jsonDict["method"] == "passKey" { + // check if the discriminator value is 'passkey' + if jsonDict["method"] == "passkey" { // try to unmarshal JSON data into UpdateRegistrationFlowWithPasskeyMethod err = json.Unmarshal(data, &dst.UpdateRegistrationFlowWithPasskeyMethod) if err == nil { @@ -118,6 +126,18 @@ func (dst *UpdateRegistrationFlowBody) UnmarshalJSON(data []byte) error { } } + // check if the discriminator value is 'profile' + if jsonDict["method"] == "profile" { + // try to unmarshal JSON data into UpdateRegistrationFlowWithProfileMethod + err = json.Unmarshal(data, &dst.UpdateRegistrationFlowWithProfileMethod) + if err == nil { + return nil // data stored in dst.UpdateRegistrationFlowWithProfileMethod, return on the first match + } else { + dst.UpdateRegistrationFlowWithProfileMethod = nil + return fmt.Errorf("Failed to unmarshal UpdateRegistrationFlowBody as UpdateRegistrationFlowWithProfileMethod: %s", err.Error()) + } + } + // check if the discriminator value is 'webauthn' if jsonDict["method"] == "webauthn" { // try to unmarshal JSON data into UpdateRegistrationFlowWithWebAuthnMethod @@ -178,6 +198,18 @@ func (dst *UpdateRegistrationFlowBody) UnmarshalJSON(data []byte) error { } } + // check if the discriminator value is 'updateRegistrationFlowWithProfileMethod' + if jsonDict["method"] == "updateRegistrationFlowWithProfileMethod" { + // try to unmarshal JSON data into UpdateRegistrationFlowWithProfileMethod + err = json.Unmarshal(data, &dst.UpdateRegistrationFlowWithProfileMethod) + if err == nil { + return nil // data stored in dst.UpdateRegistrationFlowWithProfileMethod, return on the first match + } else { + dst.UpdateRegistrationFlowWithProfileMethod = nil + return fmt.Errorf("Failed to unmarshal UpdateRegistrationFlowBody as UpdateRegistrationFlowWithProfileMethod: %s", err.Error()) + } + } + // check if the discriminator value is 'updateRegistrationFlowWithWebAuthnMethod' if jsonDict["method"] == "updateRegistrationFlowWithWebAuthnMethod" { // try to unmarshal JSON data into UpdateRegistrationFlowWithWebAuthnMethod @@ -211,6 +243,10 @@ func (src UpdateRegistrationFlowBody) MarshalJSON() ([]byte, error) { return json.Marshal(&src.UpdateRegistrationFlowWithPasswordMethod) } + if src.UpdateRegistrationFlowWithProfileMethod != nil { + return json.Marshal(&src.UpdateRegistrationFlowWithProfileMethod) + } + if src.UpdateRegistrationFlowWithWebAuthnMethod != nil { return json.Marshal(&src.UpdateRegistrationFlowWithWebAuthnMethod) } @@ -239,6 +275,10 @@ func (obj *UpdateRegistrationFlowBody) GetActualInstance() interface{} { return obj.UpdateRegistrationFlowWithPasswordMethod } + if obj.UpdateRegistrationFlowWithProfileMethod != nil { + return obj.UpdateRegistrationFlowWithProfileMethod + } + if obj.UpdateRegistrationFlowWithWebAuthnMethod != nil { return obj.UpdateRegistrationFlowWithWebAuthnMethod } diff --git a/spec/api.json b/spec/api.json index 180dd98b5dd0..156702b6716d 100644 --- a/spec/api.json +++ b/spec/api.json @@ -2956,8 +2956,9 @@ "mapping": { "code": "#/components/schemas/updateRegistrationFlowWithCodeMethod", "oidc": "#/components/schemas/updateRegistrationFlowWithOidcMethod", - "passKey": "#/components/schemas/updateRegistrationFlowWithPasskeyMethod", + "passkey": "#/components/schemas/updateRegistrationFlowWithPasskeyMethod", "password": "#/components/schemas/updateRegistrationFlowWithPasswordMethod", + "profile": "#/components/schemas/updateRegistrationFlowWithProfileMethod", "webauthn": "#/components/schemas/updateRegistrationFlowWithWebAuthnMethod" }, "propertyName": "method" @@ -2977,6 +2978,9 @@ }, { "$ref": "#/components/schemas/updateRegistrationFlowWithPasskeyMethod" + }, + { + "$ref": "#/components/schemas/updateRegistrationFlowWithProfileMethod" } ] }, From a0f1e62f51d2d15814863b143be63d0f24e399cd Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:37:28 +0200 Subject: [PATCH 4/6] feat(sdk): avoid eval with javascript triggers Using `OnLoadTrigger` and `OnClickTrigger` one can now map the trigger to the corresponding JavaScript function. For example, trigger `{"on_click_trigger":"oryWebAuthnRegistration"}` should be translated to `window.oryWebAuthnRegistration()`: ``` if (attrs.onClickTrigger) { window[attrs.onClickTrigger]() } ``` --- .../model_ui_node_input_attributes.go | 78 ++++++++++- .../model_ui_node_input_attributes.go | 78 ++++++++++- ...sswordless-case=passkey_button_exists.json | 4 +- ...resh_passwordless_credentials-browser.json | 3 +- ...=refresh_passwordless_credentials-spa.json | 3 +- ...device_is_shown_which_can_be_unlinked.json | 3 +- ...-case=one_activation_element_is_shown.json | 3 +- ...on-case=passkey_button_exists-browser.json | 3 +- ...ration-case=passkey_button_exists-spa.json | 3 +- selfservice/strategy/passkey/passkey_login.go | 125 +++++++++++++++++- .../strategy/passkey/passkey_registration.go | 9 +- .../strategy/passkey/passkey_settings.go | 5 +- ...oad_is_set_when_identity_has_webauthn.json | 40 +++--- ...ebauthn_login_is_invalid-type=browser.json | 3 +- ...if_webauthn_login_is_invalid-type=spa.json | 3 +- ...passwordless_enabled=false#01-browser.json | 40 +++--- ...als-passwordless_enabled=false#01-spa.json | 40 +++--- ...passwordless_enabled=false#02-browser.json | 40 +++--- ...als-passwordless_enabled=false#02-spa.json | 40 +++--- ...ls-passwordless_enabled=false-browser.json | 40 +++--- ...ntials-passwordless_enabled=false-spa.json | 40 +++--- ...-passwordless_enabled=true#01-browser.json | 40 +++--- ...ials-passwordless_enabled=true#01-spa.json | 40 +++--- ...-passwordless_enabled=true#02-browser.json | 40 +++--- ...ials-passwordless_enabled=true#02-spa.json | 40 +++--- ...als-passwordless_enabled=true-browser.json | 40 +++--- ...entials-passwordless_enabled=true-spa.json | 40 +++--- ...device_is_shown_which_can_be_unlinked.json | 28 ++-- ...ast_credential_available-type=browser.json | 3 - ...he_last_credential_available-type=spa.json | 3 - ...-case=one_activation_element_is_shown.json | 28 ++-- ...f_it_is_MFA_at_all_times-type=browser.json | 3 - ...al_if_it_is_MFA_at_all_times-type=spa.json | 3 - ...n-case=webauthn_button_exists-browser.json | 6 +- ...ation-case=webauthn_button_exists-spa.json | 6 +- selfservice/strategy/webauthn/login_test.go | 18 +-- .../strategy/webauthn/registration_test.go | 1 + .../strategy/webauthn/settings_test.go | 17 +-- spec/api.json | 30 ++++- spec/swagger.json | 30 ++++- ui/node/attributes.go | 15 +++ x/webauthnx/js/trigger.go | 22 +++ x/webauthnx/js/trigger_test.go | 14 ++ x/webauthnx/js/webauthn.js | 39 +++++- x/webauthnx/nodes.go | 10 +- 45 files changed, 764 insertions(+), 355 deletions(-) create mode 100644 x/webauthnx/js/trigger.go create mode 100644 x/webauthnx/js/trigger_test.go diff --git a/internal/client-go/model_ui_node_input_attributes.go b/internal/client-go/model_ui_node_input_attributes.go index b373dda7ccfd..7056a308d651 100644 --- a/internal/client-go/model_ui_node_input_attributes.go +++ b/internal/client-go/model_ui_node_input_attributes.go @@ -26,10 +26,14 @@ type UiNodeInputAttributes struct { Name string `json:"name"` // NodeType represents this node's types. It is a mirror of `node.type` and is primarily used to allow compatibility with OpenAPI 3.0. In this struct it technically always is \"input\". text Text input Input img Image a Anchor script Script NodeType string `json:"node_type"` - // OnClick may contain javascript which should be executed on click. This is primarily used for WebAuthn. + // OnClick may contain javascript which should be executed on click. This is primarily used for WebAuthn. Deprecated: Using OnClick requires the use of eval() which is a security risk. Use OnClickTrigger instead. Onclick *string `json:"onclick,omitempty"` - // OnLoad may contain javascript which should be executed on load. This is primarily used for WebAuthn. + // OnClickTrigger may contain a WebAuthn trigger which should be executed on click. The trigger maps to a JavaScript function provided by Ory, which triggers actions such as PassKey registration or login. oryWebAuthnRegistration WebAuthnTriggersWebAuthnRegistration oryWebAuthnLogin WebAuthnTriggersWebAuthnLogin oryPasskeyLogin WebAuthnTriggersPasskeyLogin oryPasskeyLoginAutocompleteInit WebAuthnTriggersPasskeyLoginAutocompleteInit oryPasskeyRegistration WebAuthnTriggersPasskeyRegistration oryPasskeySettingsRegistration WebAuthnTriggersPasskeySettingsRegistration + OnclickTrigger *string `json:"onclickTrigger,omitempty"` + // OnLoad may contain javascript which should be executed on load. This is primarily used for WebAuthn. Deprecated: Using OnLoad requires the use of eval() which is a security risk. Use OnLoadTrigger instead. Onload *string `json:"onload,omitempty"` + // OnLoadTrigger may contain a WebAuthn trigger which should be executed on load. The trigger maps to a JavaScript function provided by Ory, which triggers actions such as PassKey registration or login. oryWebAuthnRegistration WebAuthnTriggersWebAuthnRegistration oryWebAuthnLogin WebAuthnTriggersWebAuthnLogin oryPasskeyLogin WebAuthnTriggersPasskeyLogin oryPasskeyLoginAutocompleteInit WebAuthnTriggersPasskeyLoginAutocompleteInit oryPasskeyRegistration WebAuthnTriggersPasskeyRegistration oryPasskeySettingsRegistration WebAuthnTriggersPasskeySettingsRegistration + OnloadTrigger *string `json:"onloadTrigger,omitempty"` // The input's pattern. Pattern *string `json:"pattern,omitempty"` // Mark this input field as required. @@ -229,6 +233,38 @@ func (o *UiNodeInputAttributes) SetOnclick(v string) { o.Onclick = &v } +// GetOnclickTrigger returns the OnclickTrigger field value if set, zero value otherwise. +func (o *UiNodeInputAttributes) GetOnclickTrigger() string { + if o == nil || o.OnclickTrigger == nil { + var ret string + return ret + } + return *o.OnclickTrigger +} + +// GetOnclickTriggerOk returns a tuple with the OnclickTrigger field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UiNodeInputAttributes) GetOnclickTriggerOk() (*string, bool) { + if o == nil || o.OnclickTrigger == nil { + return nil, false + } + return o.OnclickTrigger, true +} + +// HasOnclickTrigger returns a boolean if a field has been set. +func (o *UiNodeInputAttributes) HasOnclickTrigger() bool { + if o != nil && o.OnclickTrigger != nil { + return true + } + + return false +} + +// SetOnclickTrigger gets a reference to the given string and assigns it to the OnclickTrigger field. +func (o *UiNodeInputAttributes) SetOnclickTrigger(v string) { + o.OnclickTrigger = &v +} + // GetOnload returns the Onload field value if set, zero value otherwise. func (o *UiNodeInputAttributes) GetOnload() string { if o == nil || o.Onload == nil { @@ -261,6 +297,38 @@ func (o *UiNodeInputAttributes) SetOnload(v string) { o.Onload = &v } +// GetOnloadTrigger returns the OnloadTrigger field value if set, zero value otherwise. +func (o *UiNodeInputAttributes) GetOnloadTrigger() string { + if o == nil || o.OnloadTrigger == nil { + var ret string + return ret + } + return *o.OnloadTrigger +} + +// GetOnloadTriggerOk returns a tuple with the OnloadTrigger field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UiNodeInputAttributes) GetOnloadTriggerOk() (*string, bool) { + if o == nil || o.OnloadTrigger == nil { + return nil, false + } + return o.OnloadTrigger, true +} + +// HasOnloadTrigger returns a boolean if a field has been set. +func (o *UiNodeInputAttributes) HasOnloadTrigger() bool { + if o != nil && o.OnloadTrigger != nil { + return true + } + + return false +} + +// SetOnloadTrigger gets a reference to the given string and assigns it to the OnloadTrigger field. +func (o *UiNodeInputAttributes) SetOnloadTrigger(v string) { + o.OnloadTrigger = &v +} + // GetPattern returns the Pattern field value if set, zero value otherwise. func (o *UiNodeInputAttributes) GetPattern() string { if o == nil || o.Pattern == nil { @@ -402,9 +470,15 @@ func (o UiNodeInputAttributes) MarshalJSON() ([]byte, error) { if o.Onclick != nil { toSerialize["onclick"] = o.Onclick } + if o.OnclickTrigger != nil { + toSerialize["onclickTrigger"] = o.OnclickTrigger + } if o.Onload != nil { toSerialize["onload"] = o.Onload } + if o.OnloadTrigger != nil { + toSerialize["onloadTrigger"] = o.OnloadTrigger + } if o.Pattern != nil { toSerialize["pattern"] = o.Pattern } diff --git a/internal/httpclient/model_ui_node_input_attributes.go b/internal/httpclient/model_ui_node_input_attributes.go index b373dda7ccfd..7056a308d651 100644 --- a/internal/httpclient/model_ui_node_input_attributes.go +++ b/internal/httpclient/model_ui_node_input_attributes.go @@ -26,10 +26,14 @@ type UiNodeInputAttributes struct { Name string `json:"name"` // NodeType represents this node's types. It is a mirror of `node.type` and is primarily used to allow compatibility with OpenAPI 3.0. In this struct it technically always is \"input\". text Text input Input img Image a Anchor script Script NodeType string `json:"node_type"` - // OnClick may contain javascript which should be executed on click. This is primarily used for WebAuthn. + // OnClick may contain javascript which should be executed on click. This is primarily used for WebAuthn. Deprecated: Using OnClick requires the use of eval() which is a security risk. Use OnClickTrigger instead. Onclick *string `json:"onclick,omitempty"` - // OnLoad may contain javascript which should be executed on load. This is primarily used for WebAuthn. + // OnClickTrigger may contain a WebAuthn trigger which should be executed on click. The trigger maps to a JavaScript function provided by Ory, which triggers actions such as PassKey registration or login. oryWebAuthnRegistration WebAuthnTriggersWebAuthnRegistration oryWebAuthnLogin WebAuthnTriggersWebAuthnLogin oryPasskeyLogin WebAuthnTriggersPasskeyLogin oryPasskeyLoginAutocompleteInit WebAuthnTriggersPasskeyLoginAutocompleteInit oryPasskeyRegistration WebAuthnTriggersPasskeyRegistration oryPasskeySettingsRegistration WebAuthnTriggersPasskeySettingsRegistration + OnclickTrigger *string `json:"onclickTrigger,omitempty"` + // OnLoad may contain javascript which should be executed on load. This is primarily used for WebAuthn. Deprecated: Using OnLoad requires the use of eval() which is a security risk. Use OnLoadTrigger instead. Onload *string `json:"onload,omitempty"` + // OnLoadTrigger may contain a WebAuthn trigger which should be executed on load. The trigger maps to a JavaScript function provided by Ory, which triggers actions such as PassKey registration or login. oryWebAuthnRegistration WebAuthnTriggersWebAuthnRegistration oryWebAuthnLogin WebAuthnTriggersWebAuthnLogin oryPasskeyLogin WebAuthnTriggersPasskeyLogin oryPasskeyLoginAutocompleteInit WebAuthnTriggersPasskeyLoginAutocompleteInit oryPasskeyRegistration WebAuthnTriggersPasskeyRegistration oryPasskeySettingsRegistration WebAuthnTriggersPasskeySettingsRegistration + OnloadTrigger *string `json:"onloadTrigger,omitempty"` // The input's pattern. Pattern *string `json:"pattern,omitempty"` // Mark this input field as required. @@ -229,6 +233,38 @@ func (o *UiNodeInputAttributes) SetOnclick(v string) { o.Onclick = &v } +// GetOnclickTrigger returns the OnclickTrigger field value if set, zero value otherwise. +func (o *UiNodeInputAttributes) GetOnclickTrigger() string { + if o == nil || o.OnclickTrigger == nil { + var ret string + return ret + } + return *o.OnclickTrigger +} + +// GetOnclickTriggerOk returns a tuple with the OnclickTrigger field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UiNodeInputAttributes) GetOnclickTriggerOk() (*string, bool) { + if o == nil || o.OnclickTrigger == nil { + return nil, false + } + return o.OnclickTrigger, true +} + +// HasOnclickTrigger returns a boolean if a field has been set. +func (o *UiNodeInputAttributes) HasOnclickTrigger() bool { + if o != nil && o.OnclickTrigger != nil { + return true + } + + return false +} + +// SetOnclickTrigger gets a reference to the given string and assigns it to the OnclickTrigger field. +func (o *UiNodeInputAttributes) SetOnclickTrigger(v string) { + o.OnclickTrigger = &v +} + // GetOnload returns the Onload field value if set, zero value otherwise. func (o *UiNodeInputAttributes) GetOnload() string { if o == nil || o.Onload == nil { @@ -261,6 +297,38 @@ func (o *UiNodeInputAttributes) SetOnload(v string) { o.Onload = &v } +// GetOnloadTrigger returns the OnloadTrigger field value if set, zero value otherwise. +func (o *UiNodeInputAttributes) GetOnloadTrigger() string { + if o == nil || o.OnloadTrigger == nil { + var ret string + return ret + } + return *o.OnloadTrigger +} + +// GetOnloadTriggerOk returns a tuple with the OnloadTrigger field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UiNodeInputAttributes) GetOnloadTriggerOk() (*string, bool) { + if o == nil || o.OnloadTrigger == nil { + return nil, false + } + return o.OnloadTrigger, true +} + +// HasOnloadTrigger returns a boolean if a field has been set. +func (o *UiNodeInputAttributes) HasOnloadTrigger() bool { + if o != nil && o.OnloadTrigger != nil { + return true + } + + return false +} + +// SetOnloadTrigger gets a reference to the given string and assigns it to the OnloadTrigger field. +func (o *UiNodeInputAttributes) SetOnloadTrigger(v string) { + o.OnloadTrigger = &v +} + // GetPattern returns the Pattern field value if set, zero value otherwise. func (o *UiNodeInputAttributes) GetPattern() string { if o == nil || o.Pattern == nil { @@ -402,9 +470,15 @@ func (o UiNodeInputAttributes) MarshalJSON() ([]byte, error) { if o.Onclick != nil { toSerialize["onclick"] = o.Onclick } + if o.OnclickTrigger != nil { + toSerialize["onclickTrigger"] = o.OnclickTrigger + } if o.Onload != nil { toSerialize["onload"] = o.Onload } + if o.OnloadTrigger != nil { + toSerialize["onloadTrigger"] = o.OnloadTrigger + } if o.Pattern != nil { toSerialize["pattern"] = o.Pattern } diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json index d2dd6567d240..33c3cd4a7c34 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json @@ -38,7 +38,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -54,7 +54,9 @@ "name": "passkey_login_trigger", "node_type": "input", "onclick": "window.__oryPasskeyLogin()", + "onclick_trigger": "oryPasskeyLogin", "onload": "window.__oryPasskeyLoginAutocompleteInit()", + "onload_trigger": "oryPasskeyLoginAutocompleteInit", "type": "button", "value": "" }, diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json index c331d4f4280f..9b6602d9064f 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json @@ -30,7 +30,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -46,6 +46,7 @@ "name": "passkey_login_trigger", "node_type": "input", "onclick": "window.__oryPasskeyLogin()", + "onclick_trigger": "oryPasskeyLogin", "type": "button", "value": "" }, diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json index c331d4f4280f..9b6602d9064f 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json @@ -30,7 +30,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -46,6 +46,7 @@ "name": "passkey_login_trigger", "node_type": "input", "onclick": "window.__oryPasskeyLogin()", + "onclick_trigger": "oryPasskeyLogin", "type": "button", "value": "" }, diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json b/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json index f9032e39049d..10cdc52924d6 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json @@ -5,6 +5,7 @@ "name": "passkey_register_trigger", "node_type": "input", "onclick": "window.__oryPasskeySettingsRegistration()", + "onclick_trigger": "oryPasskeySettingsRegistration", "type": "button", "value": "" }, @@ -109,7 +110,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json b/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json index 7e5c5b3d082b..ce4393561cfd 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json @@ -5,6 +5,7 @@ "name": "passkey_register_trigger", "node_type": "input", "onclick": "window.__oryPasskeySettingsRegistration()", + "onclick_trigger": "oryPasskeySettingsRegistration", "type": "button", "value": "" }, @@ -61,7 +62,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-browser.json b/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-browser.json index 18e0cda77811..4eb8c3d30eb7 100644 --- a/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-browser.json +++ b/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-browser.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,6 +71,7 @@ "name": "passkey_register_trigger", "node_type": "input", "onclick": "window.__oryPasskeyRegistration()", + "onclick_trigger": "oryPasskeyRegistration", "type": "button" }, "group": "passkey", diff --git a/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-spa.json b/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-spa.json index 18e0cda77811..4eb8c3d30eb7 100644 --- a/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-spa.json +++ b/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-spa.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,6 +71,7 @@ "name": "passkey_register_trigger", "node_type": "input", "onclick": "window.__oryPasskeyRegistration()", + "onclick_trigger": "oryPasskeyRegistration", "type": "button" }, "group": "passkey", diff --git a/selfservice/strategy/passkey/passkey_login.go b/selfservice/strategy/passkey/passkey_login.go index 54b3f475ed38..927944261007 100644 --- a/selfservice/strategy/passkey/passkey_login.go +++ b/selfservice/strategy/passkey/passkey_login.go @@ -9,6 +9,8 @@ import ( "net/http" "strings" + "github.com/ory/kratos/x/webauthnx/js" + "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" "github.com/pkg/errors" @@ -103,6 +105,119 @@ func (s *Strategy) populateLoginMethodForPasskeys(r *http.Request, loginFlow *lo Type: node.InputAttributeTypeHidden, }}) + loginFlow.UI.Nodes.Append(node.NewInputField( + node.PasskeyLoginTrigger, + "", + node.PasskeyGroup, + node.InputAttributeTypeButton, + node.WithInputAttributes(func(attr *node.InputAttributes) { + attr.OnClick = "window.__oryPasskeyLogin()" // this function is defined in webauthn.js + attr.OnLoad = "window.__oryPasskeyLoginAutocompleteInit()" // same here + }), + ).WithMetaLabel(text.NewInfoSelfServiceLoginPasskey())) + + return nil +} + +func (s *Strategy) populateLoginMethodForRefresh(r *http.Request, loginFlow *login.Flow) error { + ctx := r.Context() + + identifier, id, _ := flowhelpers.GuessForcedLoginIdentifier(r, s.d, loginFlow, s.ID()) + if identifier == "" { + return nil + } + + id, err := s.d.PrivilegedIdentityPool().GetIdentityConfidential(r.Context(), id.ID) + if err != nil { + return err + } + + cred, ok := id.GetCredentials(s.ID()) + if !ok { + // Identity has no passkey + return nil + } + + var conf identity.CredentialsWebAuthnConfig + if err := json.Unmarshal(cred.Config, &conf); err != nil { + return errors.WithStack(err) + } + + webAuthCreds := conf.Credentials.ToWebAuthn() + if len(webAuthCreds) == 0 { + // Identity has no webauthn + return nil + } + + passkeyIdentifier := s.PasskeyDisplayNameFromIdentity(ctx, id) + + webAuthn, err := webauthn.New(s.d.Config().PasskeyConfig(ctx)) + if err != nil { + return errors.WithStack(err) + } + option, sessionData, err := webAuthn.BeginLogin(&webauthnx.User{ + Name: passkeyIdentifier, + ID: conf.UserHandle, + Credentials: webAuthCreds, + Config: webAuthn.Config, + }) + if err != nil { + return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to initiate passkey login.").WithDebug(err.Error())) + } + + loginFlow.InternalContext, err = sjson.SetBytes( + loginFlow.InternalContext, + flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData), + sessionData, + ) + if err != nil { + return errors.WithStack(err) + } + + injectWebAuthnOptions, err := json.Marshal(option) + if err != nil { + return errors.WithStack(err) + } + + loginFlow.UI.Nodes.Upsert(&node.Node{ + Type: node.Input, + Group: node.PasskeyGroup, + Meta: &node.Meta{}, + Attributes: &node.InputAttributes{ + Name: node.PasskeyChallenge, + Type: node.InputAttributeTypeHidden, + FieldValue: string(injectWebAuthnOptions), + }}) + + loginFlow.UI.Nodes.Append(webauthnx.NewWebAuthnScript(s.d.Config().SelfPublicURL(ctx))) + + loginFlow.UI.Nodes.Upsert(&node.Node{ + Type: node.Input, + Group: node.PasskeyGroup, + Meta: &node.Meta{}, + Attributes: &node.InputAttributes{ + Name: node.PasskeyLogin, + Type: node.InputAttributeTypeHidden, + }}) + + loginFlow.UI.Nodes.Append(node.NewInputField( + node.PasskeyLoginTrigger, + "", + node.PasskeyGroup, + node.InputAttributeTypeButton, + node.WithInputAttributes(func(attr *node.InputAttributes) { + attr.OnClick = "window.__oryPasskeyLogin()" // this function is defined in webauthn.js + }), + ).WithMetaLabel(text.NewInfoSelfServiceLoginPasskey())) + + loginFlow.UI.SetCSRF(s.d.GenerateCSRFToken(r)) + loginFlow.UI.SetNode(node.NewInputField( + "identifier", + passkeyIdentifier, + node.DefaultGroup, + node.InputAttributeTypeHidden, + )) + return nil } @@ -362,7 +477,8 @@ func (s *Strategy) PopulateLoginMethodRefresh(r *http.Request, f *login.Flow) er node.PasskeyGroup, node.InputAttributeTypeButton, node.WithInputAttributes(func(attr *node.InputAttributes) { - attr.OnClick = "window.__oryPasskeyLogin()" // this function is defined in webauthn.js + attr.OnClick = js.WebAuthnTriggersPasskeyLogin.String() + "()" // this function is defined in webauthn.js + attr.OnClickTrigger = js.WebAuthnTriggersPasskeyLogin }), ).WithMetaLabel(text.NewInfoSelfServiceLoginPasskey())) @@ -392,8 +508,11 @@ func (s *Strategy) PopulateLoginMethodFirstFactor(r *http.Request, sr *login.Flo node.PasskeyGroup, node.InputAttributeTypeButton, node.WithInputAttributes(func(attr *node.InputAttributes) { - attr.OnClick = "window.__oryPasskeyLogin()" // this function is defined in webauthn.js - attr.OnLoad = "window.__oryPasskeyLoginAutocompleteInit()" // same here + attr.OnClick = js.WebAuthnTriggersPasskeyLogin.String() + "()" // this function is defined in webauthn.js + attr.OnClickTrigger = js.WebAuthnTriggersPasskeyLogin + + attr.OnLoad = js.WebAuthnTriggersPasskeyLoginAutocompleteInit.String() + "()" // same here + attr.OnLoadTrigger = js.WebAuthnTriggersPasskeyLoginAutocompleteInit }), ).WithMetaLabel(text.NewInfoSelfServiceLoginPasskey())) diff --git a/selfservice/strategy/passkey/passkey_registration.go b/selfservice/strategy/passkey/passkey_registration.go index 88efd420d725..9be753f70c40 100644 --- a/selfservice/strategy/passkey/passkey_registration.go +++ b/selfservice/strategy/passkey/passkey_registration.go @@ -11,6 +11,8 @@ import ( "net/url" "strings" + "github.com/ory/kratos/x/webauthnx/js" + "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" "github.com/pkg/errors" @@ -280,9 +282,10 @@ func (s *Strategy) PopulateRegistrationMethod(r *http.Request, regFlow *registra Group: node.PasskeyGroup, Meta: &node.Meta{Label: text.NewInfoSelfServiceRegistrationRegisterPasskey()}, Attributes: &node.InputAttributes{ - Name: node.PasskeyRegisterTrigger, - Type: node.InputAttributeTypeButton, - OnClick: "window.__oryPasskeyRegistration()", // defined in webauthn.js + Name: node.PasskeyRegisterTrigger, + Type: node.InputAttributeTypeButton, + OnClick: js.WebAuthnTriggersPasskeyRegistration.String() + "()", // defined in webauthn.js + OnClickTrigger: js.WebAuthnTriggersPasskeyRegistration, }}) // Passkey nodes end diff --git a/selfservice/strategy/passkey/passkey_settings.go b/selfservice/strategy/passkey/passkey_settings.go index 548a261e442b..04beff02ab09 100644 --- a/selfservice/strategy/passkey/passkey_settings.go +++ b/selfservice/strategy/passkey/passkey_settings.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "github.com/ory/kratos/x/webauthnx/js" + "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" "github.com/gofrs/uuid" @@ -114,7 +116,8 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity node.PasskeyGroup, node.InputAttributeTypeButton, node.WithInputAttributes(func(a *node.InputAttributes) { - a.OnClick = "window.__oryPasskeySettingsRegistration()" + a.OnClick = js.WebAuthnTriggersPasskeySettingsRegistration.String() + "()" + a.OnClickTrigger = js.WebAuthnTriggersPasskeySettingsRegistration }), ).WithMetaLabel(text.NewInfoSelfServiceSettingsRegisterPasskey())) diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json index ca960c98d683..472dc71f4672 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json @@ -24,25 +24,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -61,7 +42,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -70,5 +51,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json index f4be195cdecf..03a66e9c5616 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json @@ -37,7 +37,7 @@ "async": true, "referrerpolicy": "no-referrer", "crossorigin": "anonymous", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "type": "text/javascript", "node_type": "script" }, @@ -51,6 +51,7 @@ "name": "webauthn_login_trigger", "type": "button", "disabled": false, + "onclick_trigger": "oryWebAuthnLogin", "node_type": "input" }, "messages": [], diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json index f4be195cdecf..03a66e9c5616 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json @@ -37,7 +37,7 @@ "async": true, "referrerpolicy": "no-referrer", "crossorigin": "anonymous", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "type": "text/javascript", "node_type": "script" }, @@ -51,6 +51,7 @@ "name": "webauthn_login_trigger", "type": "button", "disabled": false, + "onclick_trigger": "oryWebAuthnLogin", "node_type": "input" }, "messages": [], diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-browser.json index 581bff275b17..c359007414d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-browser.json @@ -25,25 +25,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -62,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,5 +52,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-spa.json index 581bff275b17..c359007414d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-spa.json @@ -25,25 +25,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -62,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,5 +52,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-browser.json index 581bff275b17..c359007414d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-browser.json @@ -25,25 +25,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -62,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,5 +52,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-spa.json index 581bff275b17..c359007414d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-spa.json @@ -25,25 +25,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -62,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,5 +52,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-browser.json index 581bff275b17..c359007414d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-browser.json @@ -25,25 +25,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -62,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,5 +52,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-spa.json index 581bff275b17..c359007414d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-spa.json @@ -25,25 +25,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -62,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,5 +52,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-browser.json index 581bff275b17..c359007414d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-browser.json @@ -25,25 +25,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -62,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,5 +52,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-spa.json index 581bff275b17..c359007414d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-spa.json @@ -25,25 +25,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -62,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,5 +52,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-browser.json index 581bff275b17..c359007414d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-browser.json @@ -25,25 +25,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -62,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,5 +52,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-spa.json index 581bff275b17..c359007414d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-spa.json @@ -25,25 +25,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -62,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,5 +52,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-browser.json index 581bff275b17..c359007414d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-browser.json @@ -25,25 +25,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -62,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,5 +52,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-spa.json index 581bff275b17..c359007414d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-spa.json @@ -25,25 +25,6 @@ "meta": {}, "type": "input" }, - { - "attributes": { - "disabled": false, - "name": "webauthn_login_trigger", - "node_type": "input", - "type": "button", - "value": "" - }, - "group": "webauthn", - "messages": [], - "meta": { - "label": { - "id": 1010008, - "text": "Use security key", - "type": "info" - } - }, - "type": "input" - }, { "attributes": { "disabled": false, @@ -62,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" @@ -71,5 +52,24 @@ "messages": [], "meta": {}, "type": "script" + }, + { + "attributes": { + "disabled": false, + "name": "webauthn_login_trigger", + "node_type": "input", + "onclick_trigger": "oryWebAuthnLogin", + "type": "button" + }, + "group": "webauthn", + "messages": [], + "meta": { + "label": { + "id": 1010008, + "text": "Use security key", + "type": "info" + } + }, + "type": "input" } ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json index 0b1702c09413..02acdfb345d5 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json @@ -82,33 +82,33 @@ { "attributes": { "disabled": false, - "name": "webauthn_register_trigger", + "name": "webauthn_register", "node_type": "input", - "type": "button", + "type": "hidden", "value": "" }, "group": "webauthn", "messages": [], - "meta": { - "label": { - "id": 1050012, - "text": "Add security key", - "type": "info" - } - }, + "meta": {}, "type": "input" }, { "attributes": { "disabled": false, - "name": "webauthn_register", + "name": "webauthn_register_trigger", "node_type": "input", - "type": "hidden", - "value": "" + "onclick_trigger": "oryWebAuthnRegistration", + "type": "button" }, "group": "webauthn", "messages": [], - "meta": {}, + "meta": { + "label": { + "id": 1050012, + "text": "Add security key", + "type": "info" + } + }, "type": "input" }, { @@ -116,7 +116,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=fails_to_remove_security_key_if_it_is_passwordless_and_the_last_credential_available-type=browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=fails_to_remove_security_key_if_it_is_passwordless_and_the_last_credential_available-type=browser.json index 515658a3d64f..9bd36e752fd0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=fails_to_remove_security_key_if_it_is_passwordless_and_the_last_credential_available-type=browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=fails_to_remove_security_key_if_it_is_passwordless_and_the_last_credential_available-type=browser.json @@ -5,9 +5,6 @@ "webauthn_register_displayname": [ "" ], - "webauthn_register_trigger": [ - "" - ], "webauthn_remove": [ "666f6f666f6f" ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=fails_to_remove_security_key_if_it_is_passwordless_and_the_last_credential_available-type=spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=fails_to_remove_security_key_if_it_is_passwordless_and_the_last_credential_available-type=spa.json index 515658a3d64f..9bd36e752fd0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=fails_to_remove_security_key_if_it_is_passwordless_and_the_last_credential_available-type=spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=fails_to_remove_security_key_if_it_is_passwordless_and_the_last_credential_available-type=spa.json @@ -5,9 +5,6 @@ "webauthn_register_displayname": [ "" ], - "webauthn_register_trigger": [ - "" - ], "webauthn_remove": [ "666f6f666f6f" ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json index b21fa4833028..8fddd27469f3 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json @@ -34,33 +34,33 @@ { "attributes": { "disabled": false, - "name": "webauthn_register_trigger", + "name": "webauthn_register", "node_type": "input", - "type": "button", + "type": "hidden", "value": "" }, "group": "webauthn", "messages": [], - "meta": { - "label": { - "id": 1050012, - "text": "Add security key", - "type": "info" - } - }, + "meta": {}, "type": "input" }, { "attributes": { "disabled": false, - "name": "webauthn_register", + "name": "webauthn_register_trigger", "node_type": "input", - "type": "hidden", - "value": "" + "onclick_trigger": "oryWebAuthnRegistration", + "type": "button" }, "group": "webauthn", "messages": [], - "meta": {}, + "meta": { + "label": { + "id": 1050012, + "text": "Add security key", + "type": "info" + } + }, "type": "input" }, { @@ -68,7 +68,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=possible_to_remove_webauthn_credential_if_it_is_MFA_at_all_times-type=browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=possible_to_remove_webauthn_credential_if_it_is_MFA_at_all_times-type=browser.json index 515658a3d64f..9bd36e752fd0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=possible_to_remove_webauthn_credential_if_it_is_MFA_at_all_times-type=browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=possible_to_remove_webauthn_credential_if_it_is_MFA_at_all_times-type=browser.json @@ -5,9 +5,6 @@ "webauthn_register_displayname": [ "" ], - "webauthn_register_trigger": [ - "" - ], "webauthn_remove": [ "666f6f666f6f" ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=possible_to_remove_webauthn_credential_if_it_is_MFA_at_all_times-type=spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=possible_to_remove_webauthn_credential_if_it_is_MFA_at_all_times-type=spa.json index 515658a3d64f..9bd36e752fd0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=possible_to_remove_webauthn_credential_if_it_is_MFA_at_all_times-type=spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=possible_to_remove_webauthn_credential_if_it_is_MFA_at_all_times-type=spa.json @@ -5,9 +5,6 @@ "webauthn_register_displayname": [ "" ], - "webauthn_register_trigger": [ - "" - ], "webauthn_remove": [ "666f6f666f6f" ] diff --git a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json index 14a920d0a18d..edcf3a92b509 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json @@ -75,8 +75,8 @@ "disabled": false, "name": "webauthn_register_trigger", "node_type": "input", - "type": "button", - "value": "" + "onclick_trigger": "oryWebAuthnRegistration", + "type": "button" }, "group": "webauthn", "messages": [], @@ -94,7 +94,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json index 14a920d0a18d..edcf3a92b509 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json @@ -75,8 +75,8 @@ "disabled": false, "name": "webauthn_register_trigger", "node_type": "input", - "type": "button", - "value": "" + "onclick_trigger": "oryWebAuthnRegistration", + "type": "button" }, "group": "webauthn", "messages": [], @@ -94,7 +94,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==", + "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/login_test.go b/selfservice/strategy/webauthn/login_test.go index 46db972c4cc7..cfb2b18455ac 100644 --- a/selfservice/strategy/webauthn/login_test.go +++ b/selfservice/strategy/webauthn/login_test.go @@ -170,9 +170,10 @@ func TestCompleteLogin(t *testing.T) { }, testhelpers.InitFlowWithRefresh()) snapshotx.SnapshotTExcept(t, f.Ui.Nodes, []string{ "0.attributes.value", - "2.attributes.onclick", - "4.attributes.nonce", - "4.attributes.src", + "3.attributes.nonce", + "3.attributes.src", + "4.attributes.value", + "4.attributes.onclick", }) nodes, err := json.Marshal(f.Ui.Nodes) require.NoError(t, err) @@ -475,12 +476,13 @@ func TestCompleteLogin(t *testing.T) { testhelpers.SnapshotTExcept(t, f.Ui.Nodes, []string{ "0.attributes.value", "1.attributes.value", - "2.attributes.onclick", - "2.attributes.onload", - "4.attributes.src", - "4.attributes.nonce", + "3.attributes.src", + "3.attributes.nonce", + "4.attributes.onclick", + "4.attributes.onload", + "4.attributes.value", }) - ensureReplacement(t, "2", f.Ui, "allowCredentials") + ensureReplacement(t, "4", f.Ui, "allowCredentials") }) t.Run("case=webauthn payload is not set when identity has no webauthn", func(t *testing.T) { diff --git a/selfservice/strategy/webauthn/registration_test.go b/selfservice/strategy/webauthn/registration_test.go index 973e1ae0ec81..8dd3e38bd036 100644 --- a/selfservice/strategy/webauthn/registration_test.go +++ b/selfservice/strategy/webauthn/registration_test.go @@ -145,6 +145,7 @@ func TestRegistration(t *testing.T) { testhelpers.SnapshotTExcept(t, f.Ui.Nodes, []string{ "2.attributes.value", "5.attributes.onclick", + "5.attributes.value", "6.attributes.nonce", "6.attributes.src", }) diff --git a/selfservice/strategy/webauthn/settings_test.go b/selfservice/strategy/webauthn/settings_test.go index 3b46cc8de752..9a34159c9a3d 100644 --- a/selfservice/strategy/webauthn/settings_test.go +++ b/selfservice/strategy/webauthn/settings_test.go @@ -143,11 +143,12 @@ func TestCompleteSettings(t *testing.T) { testhelpers.SnapshotTExcept(t, f.Ui.Nodes, []string{ "0.attributes.value", - "4.attributes.onclick", + "5.attributes.onclick", + "5.attributes.value", "6.attributes.src", "6.attributes.nonce", }) - ensureReplacement(t, "4", f.Ui, "Ory Corp") + ensureReplacement(t, "5", f.Ui, "Ory Corp") }) t.Run("case=one activation element is shown", func(t *testing.T) { @@ -159,12 +160,13 @@ func TestCompleteSettings(t *testing.T) { testhelpers.SnapshotTExcept(t, f.Ui.Nodes, []string{ "0.attributes.value", - "2.attributes.onload", - "2.attributes.onclick", + "3.attributes.onload", + "3.attributes.onclick", + "3.attributes.value", "4.attributes.src", "4.attributes.nonce", }) - ensureReplacement(t, "2", f.Ui, "Ory Corp") + ensureReplacement(t, "3", f.Ui, "Ory Corp") }) t.Run("case=webauthn only works for browsers", func(t *testing.T) { @@ -375,7 +377,7 @@ func TestCompleteSettings(t *testing.T) { body, res := doBrowserFlow(t, spa, func(v url.Values) { // The remove key should be empty - snapshotx.SnapshotTExcept(t, v, []string{"csrf_token"}) + snapshotx.SnapshotTExcept(t, v, []string{"csrf_token", "webauthn_register_trigger"}) v.Set(node.WebAuthnRemove, "666f6f666f6f") }, id) @@ -416,7 +418,7 @@ func TestCompleteSettings(t *testing.T) { body, res := doBrowserFlow(t, spa, func(v url.Values) { // The remove key should be set - snapshotx.SnapshotTExcept(t, v, []string{"csrf_token"}) + snapshotx.SnapshotTExcept(t, v, []string{"csrf_token", "webauthn_register_trigger"}) v.Set(node.WebAuthnRemove, "666f6f666f6f") }, id) @@ -481,7 +483,6 @@ func TestCompleteSettings(t *testing.T) { // Check not to remove other credentials with webauthn _, ok = actual.GetCredentials(identity.CredentialsTypePassword) assert.True(t, ok) - } t.Run("type=browser", func(t *testing.T) { diff --git a/spec/api.json b/spec/api.json index 156702b6716d..3e45e5768030 100644 --- a/spec/api.json +++ b/spec/api.json @@ -2389,13 +2389,39 @@ "x-go-enum-desc": "text Text\ninput Input\nimg Image\na Anchor\nscript Script" }, "onclick": { - "description": "OnClick may contain javascript which should be executed on click. This is primarily\nused for WebAuthn.", + "description": "OnClick may contain javascript which should be executed on click. This is primarily\nused for WebAuthn.\n\nDeprecated: Using OnClick requires the use of eval() which is a security risk. Use OnClickTrigger instead.", "type": "string" }, + "onclickTrigger": { + "description": "OnClickTrigger may contain a WebAuthn trigger which should be executed on click.\n\nThe trigger maps to a JavaScript function provided by Ory, which triggers actions such as PassKey registration or login.\noryWebAuthnRegistration WebAuthnTriggersWebAuthnRegistration\noryWebAuthnLogin WebAuthnTriggersWebAuthnLogin\noryPasskeyLogin WebAuthnTriggersPasskeyLogin\noryPasskeyLoginAutocompleteInit WebAuthnTriggersPasskeyLoginAutocompleteInit\noryPasskeyRegistration WebAuthnTriggersPasskeyRegistration\noryPasskeySettingsRegistration WebAuthnTriggersPasskeySettingsRegistration", + "enum": [ + "oryWebAuthnRegistration", + "oryWebAuthnLogin", + "oryPasskeyLogin", + "oryPasskeyLoginAutocompleteInit", + "oryPasskeyRegistration", + "oryPasskeySettingsRegistration" + ], + "type": "string", + "x-go-enum-desc": "oryWebAuthnRegistration WebAuthnTriggersWebAuthnRegistration\noryWebAuthnLogin WebAuthnTriggersWebAuthnLogin\noryPasskeyLogin WebAuthnTriggersPasskeyLogin\noryPasskeyLoginAutocompleteInit WebAuthnTriggersPasskeyLoginAutocompleteInit\noryPasskeyRegistration WebAuthnTriggersPasskeyRegistration\noryPasskeySettingsRegistration WebAuthnTriggersPasskeySettingsRegistration" + }, "onload": { - "description": "OnLoad may contain javascript which should be executed on load. This is primarily\nused for WebAuthn.", + "description": "OnLoad may contain javascript which should be executed on load. This is primarily\nused for WebAuthn.\n\nDeprecated: Using OnLoad requires the use of eval() which is a security risk. Use OnLoadTrigger instead.", "type": "string" }, + "onloadTrigger": { + "description": "OnLoadTrigger may contain a WebAuthn trigger which should be executed on load.\n\nThe trigger maps to a JavaScript function provided by Ory, which triggers actions such as PassKey registration or login.\noryWebAuthnRegistration WebAuthnTriggersWebAuthnRegistration\noryWebAuthnLogin WebAuthnTriggersWebAuthnLogin\noryPasskeyLogin WebAuthnTriggersPasskeyLogin\noryPasskeyLoginAutocompleteInit WebAuthnTriggersPasskeyLoginAutocompleteInit\noryPasskeyRegistration WebAuthnTriggersPasskeyRegistration\noryPasskeySettingsRegistration WebAuthnTriggersPasskeySettingsRegistration", + "enum": [ + "oryWebAuthnRegistration", + "oryWebAuthnLogin", + "oryPasskeyLogin", + "oryPasskeyLoginAutocompleteInit", + "oryPasskeyRegistration", + "oryPasskeySettingsRegistration" + ], + "type": "string", + "x-go-enum-desc": "oryWebAuthnRegistration WebAuthnTriggersWebAuthnRegistration\noryWebAuthnLogin WebAuthnTriggersWebAuthnLogin\noryPasskeyLogin WebAuthnTriggersPasskeyLogin\noryPasskeyLoginAutocompleteInit WebAuthnTriggersPasskeyLoginAutocompleteInit\noryPasskeyRegistration WebAuthnTriggersPasskeyRegistration\noryPasskeySettingsRegistration WebAuthnTriggersPasskeySettingsRegistration" + }, "pattern": { "description": "The input's pattern.", "type": "string" diff --git a/spec/swagger.json b/spec/swagger.json index 9498a51dee44..860b1f416181 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -5461,13 +5461,39 @@ "x-go-enum-desc": "text Text\ninput Input\nimg Image\na Anchor\nscript Script" }, "onclick": { - "description": "OnClick may contain javascript which should be executed on click. This is primarily\nused for WebAuthn.", + "description": "OnClick may contain javascript which should be executed on click. This is primarily\nused for WebAuthn.\n\nDeprecated: Using OnClick requires the use of eval() which is a security risk. Use OnClickTrigger instead.", "type": "string" }, + "onclickTrigger": { + "description": "OnClickTrigger may contain a WebAuthn trigger which should be executed on click.\n\nThe trigger maps to a JavaScript function provided by Ory, which triggers actions such as PassKey registration or login.\noryWebAuthnRegistration WebAuthnTriggersWebAuthnRegistration\noryWebAuthnLogin WebAuthnTriggersWebAuthnLogin\noryPasskeyLogin WebAuthnTriggersPasskeyLogin\noryPasskeyLoginAutocompleteInit WebAuthnTriggersPasskeyLoginAutocompleteInit\noryPasskeyRegistration WebAuthnTriggersPasskeyRegistration\noryPasskeySettingsRegistration WebAuthnTriggersPasskeySettingsRegistration", + "type": "string", + "enum": [ + "oryWebAuthnRegistration", + "oryWebAuthnLogin", + "oryPasskeyLogin", + "oryPasskeyLoginAutocompleteInit", + "oryPasskeyRegistration", + "oryPasskeySettingsRegistration" + ], + "x-go-enum-desc": "oryWebAuthnRegistration WebAuthnTriggersWebAuthnRegistration\noryWebAuthnLogin WebAuthnTriggersWebAuthnLogin\noryPasskeyLogin WebAuthnTriggersPasskeyLogin\noryPasskeyLoginAutocompleteInit WebAuthnTriggersPasskeyLoginAutocompleteInit\noryPasskeyRegistration WebAuthnTriggersPasskeyRegistration\noryPasskeySettingsRegistration WebAuthnTriggersPasskeySettingsRegistration" + }, "onload": { - "description": "OnLoad may contain javascript which should be executed on load. This is primarily\nused for WebAuthn.", + "description": "OnLoad may contain javascript which should be executed on load. This is primarily\nused for WebAuthn.\n\nDeprecated: Using OnLoad requires the use of eval() which is a security risk. Use OnLoadTrigger instead.", "type": "string" }, + "onloadTrigger": { + "description": "OnLoadTrigger may contain a WebAuthn trigger which should be executed on load.\n\nThe trigger maps to a JavaScript function provided by Ory, which triggers actions such as PassKey registration or login.\noryWebAuthnRegistration WebAuthnTriggersWebAuthnRegistration\noryWebAuthnLogin WebAuthnTriggersWebAuthnLogin\noryPasskeyLogin WebAuthnTriggersPasskeyLogin\noryPasskeyLoginAutocompleteInit WebAuthnTriggersPasskeyLoginAutocompleteInit\noryPasskeyRegistration WebAuthnTriggersPasskeyRegistration\noryPasskeySettingsRegistration WebAuthnTriggersPasskeySettingsRegistration", + "type": "string", + "enum": [ + "oryWebAuthnRegistration", + "oryWebAuthnLogin", + "oryPasskeyLogin", + "oryPasskeyLoginAutocompleteInit", + "oryPasskeyRegistration", + "oryPasskeySettingsRegistration" + ], + "x-go-enum-desc": "oryWebAuthnRegistration WebAuthnTriggersWebAuthnRegistration\noryWebAuthnLogin WebAuthnTriggersWebAuthnLogin\noryPasskeyLogin WebAuthnTriggersPasskeyLogin\noryPasskeyLoginAutocompleteInit WebAuthnTriggersPasskeyLoginAutocompleteInit\noryPasskeyRegistration WebAuthnTriggersPasskeyRegistration\noryPasskeySettingsRegistration WebAuthnTriggersPasskeySettingsRegistration" + }, "pattern": { "description": "The input's pattern.", "type": "string" diff --git a/ui/node/attributes.go b/ui/node/attributes.go index 762df9fd46c7..2c8045ecb743 100644 --- a/ui/node/attributes.go +++ b/ui/node/attributes.go @@ -6,6 +6,7 @@ package node import ( "fmt" "github.com/ory/kratos/text" + "github.com/ory/kratos/x/webauthnx/js" ) const ( @@ -97,12 +98,26 @@ type InputAttributes struct { // OnClick may contain javascript which should be executed on click. This is primarily // used for WebAuthn. + // + // Deprecated: Using OnClick requires the use of eval() which is a security risk. Use OnClickTrigger instead. OnClick string `json:"onclick,omitempty"` + // OnClickTrigger may contain a WebAuthn trigger which should be executed on click. + // + // The trigger maps to a JavaScript function provided by Ory, which triggers actions such as PassKey registration or login. + OnClickTrigger js.WebAuthnTriggers `json:"onclickTrigger,omitempty"` + // OnLoad may contain javascript which should be executed on load. This is primarily // used for WebAuthn. + // + // Deprecated: Using OnLoad requires the use of eval() which is a security risk. Use OnLoadTrigger instead. OnLoad string `json:"onload,omitempty"` + // OnLoadTrigger may contain a WebAuthn trigger which should be executed on load. + // + // The trigger maps to a JavaScript function provided by Ory, which triggers actions such as PassKey registration or login. + OnLoadTrigger js.WebAuthnTriggers `json:"onloadTrigger,omitempty"` + // NodeType represents this node's types. It is a mirror of `node.type` and // is primarily used to allow compatibility with OpenAPI 3.0. In this struct it technically always is "input". // diff --git a/x/webauthnx/js/trigger.go b/x/webauthnx/js/trigger.go new file mode 100644 index 000000000000..7b236191ce8e --- /dev/null +++ b/x/webauthnx/js/trigger.go @@ -0,0 +1,22 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package js + +import "fmt" + +// swagger:enum WebAuthnTriggers +type WebAuthnTriggers string + +const ( + WebAuthnTriggersWebAuthnRegistration WebAuthnTriggers = "oryWebAuthnRegistration" + WebAuthnTriggersWebAuthnLogin WebAuthnTriggers = "oryWebAuthnLogin" + WebAuthnTriggersPasskeyLogin WebAuthnTriggers = "oryPasskeyLogin" + WebAuthnTriggersPasskeyLoginAutocompleteInit WebAuthnTriggers = "oryPasskeyLoginAutocompleteInit" + WebAuthnTriggersPasskeyRegistration WebAuthnTriggers = "oryPasskeyRegistration" + WebAuthnTriggersPasskeySettingsRegistration WebAuthnTriggers = "oryPasskeySettingsRegistration" +) + +func (r WebAuthnTriggers) String() string { + return fmt.Sprintf("window.%s", string(r)) +} diff --git a/x/webauthnx/js/trigger_test.go b/x/webauthnx/js/trigger_test.go new file mode 100644 index 000000000000..97f9dc00ee77 --- /dev/null +++ b/x/webauthnx/js/trigger_test.go @@ -0,0 +1,14 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package js + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestToString(t *testing.T) { + assert.Equal(t, "window.oryWebAuthnRegistration", WebAuthnTriggersWebAuthnRegistration.String()) +} diff --git a/x/webauthnx/js/webauthn.js b/x/webauthnx/js/webauthn.js index 61a7cb8f976d..638bd4ece082 100644 --- a/x/webauthnx/js/webauthn.js +++ b/x/webauthnx/js/webauthn.js @@ -32,7 +32,7 @@ } function __oryWebAuthnLogin( - opt, + options, resultQuerySelector = '*[name="webauthn_login"]', triggerQuerySelector = '*[name="webauthn_login_trigger"]', ) { @@ -40,6 +40,12 @@ alert("This browser does not support WebAuthn!") } + const triggerEl = document.querySelector(triggerQuerySelector) + let opt = options + if (!opt) { + opt = JSON.parse(triggerEl.value) + } + opt.publicKey.challenge = __oryWebAuthnBufferDecode(opt.publicKey.challenge) opt.publicKey.allowCredentials = opt.publicKey.allowCredentials.map( function (value) { @@ -71,7 +77,7 @@ }, }) - document.querySelector(triggerQuerySelector).closest("form").submit() + triggerEl.closest("form").submit() }) .catch((err) => { alert(err) @@ -79,7 +85,7 @@ } function __oryWebAuthnRegistration( - opt, + options, resultQuerySelector = '*[name="webauthn_register"]', triggerQuerySelector = '*[name="webauthn_register_trigger"]', ) { @@ -87,6 +93,12 @@ alert("This browser does not support WebAuthn!") } + const triggerEl = document.querySelector(triggerQuerySelector) + let opt = options + if (!opt) { + opt = JSON.parse(triggerEl.value) + } + opt.publicKey.user.id = __oryWebAuthnBufferDecode(opt.publicKey.user.id) opt.publicKey.challenge = __oryWebAuthnBufferDecode(opt.publicKey.challenge) @@ -118,14 +130,14 @@ }, }) - document.querySelector(triggerQuerySelector).closest("form").submit() + triggerEl.closest("form").submit() }) .catch((err) => { alert(err) }) } - window.__oryPasskeyLoginAutocompleteInit = async function () { + async function __oryPasskeyLoginAutocompleteInit () { const dataEl = document.getElementsByName("passkey_challenge")[0] const resultEl = document.getElementsByName("passkey_login")[0] const identifierEl = document.getElementsByName("identifier")[0] @@ -195,7 +207,7 @@ }) } - window.__oryPasskeyLogin = function () { + function __oryPasskeyLogin () { const dataEl = document.getElementsByName("passkey_challenge")[0] const resultEl = document.getElementsByName("passkey_login")[0] @@ -262,7 +274,7 @@ }) } - window.__oryPasskeyRegistration = function () { + function __oryPasskeyRegistration () { const dataEl = document.getElementsByName("passkey_create_data")[0] const resultEl = document.getElementsByName("passkey_register")[0] @@ -373,8 +385,21 @@ }) } + // Deprecated naming with underscores - kept for support with Ory Elements v0 window.__oryWebAuthnLogin = __oryWebAuthnLogin window.__oryWebAuthnRegistration = __oryWebAuthnRegistration window.__oryPasskeySettingsRegistration = __oryPasskeySettingsRegistration + window.__oryPasskeyLogin = __oryPasskeyLogin + window.__oryPasskeyRegistration = __oryPasskeyRegistration + window.__oryPasskeyLoginAutocompleteInit = __oryPasskeyLoginAutocompleteInit + + // Current naming - use with Ory Elements v1 + window.oryWebAuthnLogin = __oryWebAuthnLogin + window.oryWebAuthnRegistration = __oryWebAuthnRegistration + window.oryPasskeySettingsRegistration = __oryPasskeySettingsRegistration + window.oryPasskeyLogin = __oryPasskeyLogin + window.oryPasskeyRegistration = __oryPasskeyRegistration + window.oryPasskeyLoginAutocompleteInit = __oryPasskeyLoginAutocompleteInit + window.__oryWebAuthnInitialized = true })() diff --git a/x/webauthnx/nodes.go b/x/webauthnx/nodes.go index 76fac1c397cd..85ecf621cfda 100644 --- a/x/webauthnx/nodes.go +++ b/x/webauthnx/nodes.go @@ -10,6 +10,8 @@ import ( "fmt" "net/url" + "github.com/ory/kratos/x/webauthnx/js" + "github.com/ory/x/stringsx" "github.com/ory/x/urlx" @@ -21,7 +23,9 @@ import ( func NewWebAuthnConnectionTrigger(options string) *node.Node { return node.NewInputField(node.WebAuthnRegisterTrigger, "", node.WebAuthnGroup, node.InputAttributeTypeButton, node.WithInputAttributes(func(a *node.InputAttributes) { - a.OnClick = "window.__oryWebAuthnRegistration(" + options + ")" + a.OnClick = fmt.Sprintf("%s(%s)", js.WebAuthnTriggersWebAuthnRegistration, options) + a.OnClickTrigger = js.WebAuthnTriggersWebAuthnRegistration + a.FieldValue = options })) } @@ -44,7 +48,9 @@ func NewWebAuthnConnectionInput() *node.Node { func NewWebAuthnLoginTrigger(options string) *node.Node { return node.NewInputField(node.WebAuthnLoginTrigger, "", node.WebAuthnGroup, node.InputAttributeTypeButton, node.WithInputAttributes(func(a *node.InputAttributes) { - a.OnClick = "window.__oryWebAuthnLogin(" + options + ")" + a.OnClick = fmt.Sprintf("%s(%s)", js.WebAuthnTriggersWebAuthnLogin, options) + a.FieldValue = options + a.OnClickTrigger = js.WebAuthnTriggersWebAuthnLogin })) } From fd5a4da59630e8b776c4192381fecd643517a05e Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:49:23 +0200 Subject: [PATCH 5/6] fix: replace submit with continue button for recovery and verification and add maxlength --- .../model_ui_node_input_attributes.go | 37 +++++++++++++++++++ .../model_ui_node_input_attributes.go | 37 +++++++++++++++++++ ...=fails_if_active_strategy_is_disabled.json | 4 +- ...=fails_if_active_strategy_is_disabled.json | 4 +- ...=fails_if_active_strategy_is_disabled.json | 4 +- ...=fails_if_active_strategy_is_disabled.json | 4 +- ...ail_field_when_creating_recovery_code.json | 4 +- ...set_all_the_correct_recovery_payloads.json | 4 +- ...ct_recovery_payloads_after_submission.json | 4 +- ...he_correct_recovery_payloads-type=api.json | 4 +- ...orrect_recovery_payloads-type=browser.json | 4 +- ...he_correct_recovery_payloads-type=spa.json | 4 +- ...ry_payloads_after_submission-type=api.json | 4 +- ...ayloads_after_submission-type=browser.json | 4 +- ...ry_payloads_after_submission-type=spa.json | 4 +- ...all_the_correct_verification_payloads.json | 4 +- ...erification_payloads_after_submission.json | 4 +- selfservice/strategy/code/strategy.go | 9 +++-- .../strategy/code/strategy_recovery.go | 5 ++- .../strategy/code/strategy_recovery_admin.go | 7 +++- ...set_all_the_correct_recovery_payloads.json | 4 +- ...ct_recovery_payloads_after_submission.json | 4 +- ...all_the_correct_verification_payloads.json | 4 +- ...erification_payloads_after_submission.json | 4 +- .../strategy/link/strategy_recovery.go | 2 +- .../strategy/link/strategy_verification.go | 2 +- ...sswordless-case=passkey_button_exists.json | 8 ++-- ...resh_passwordless_credentials-browser.json | 4 +- ...=refresh_passwordless_credentials-spa.json | 4 +- ...device_is_shown_which_can_be_unlinked.json | 4 +- ...-case=one_activation_element_is_shown.json | 4 +- ...on-case=passkey_button_exists-browser.json | 4 +- ...ration-case=passkey_button_exists-spa.json | 4 +- ...oad_is_set_when_identity_has_webauthn.json | 2 +- ...ebauthn_login_is_invalid-type=browser.json | 2 +- ...if_webauthn_login_is_invalid-type=spa.json | 2 +- ...passwordless_enabled=false#01-browser.json | 2 +- ...als-passwordless_enabled=false#01-spa.json | 2 +- ...passwordless_enabled=false#02-browser.json | 2 +- ...als-passwordless_enabled=false#02-spa.json | 2 +- ...ls-passwordless_enabled=false-browser.json | 2 +- ...ntials-passwordless_enabled=false-spa.json | 2 +- ...-passwordless_enabled=true#01-browser.json | 2 +- ...ials-passwordless_enabled=true#01-spa.json | 2 +- ...-passwordless_enabled=true#02-browser.json | 2 +- ...ials-passwordless_enabled=true#02-spa.json | 2 +- ...als-passwordless_enabled=true-browser.json | 2 +- ...entials-passwordless_enabled=true-spa.json | 2 +- ...device_is_shown_which_can_be_unlinked.json | 2 +- ...-case=one_activation_element_is_shown.json | 2 +- ...n-case=webauthn_button_exists-browser.json | 2 +- ...ation-case=webauthn_button_exists-spa.json | 2 +- spec/api.json | 5 +++ spec/swagger.json | 5 +++ ui/node/attributes.go | 3 ++ 55 files changed, 176 insertions(+), 82 deletions(-) diff --git a/internal/client-go/model_ui_node_input_attributes.go b/internal/client-go/model_ui_node_input_attributes.go index 7056a308d651..f8deff5d5417 100644 --- a/internal/client-go/model_ui_node_input_attributes.go +++ b/internal/client-go/model_ui_node_input_attributes.go @@ -22,6 +22,8 @@ type UiNodeInputAttributes struct { // Sets the input's disabled field to true or false. Disabled bool `json:"disabled"` Label *UiText `json:"label,omitempty"` + // MaxLength may contain the input's maximum length. + Maxlength *int64 `json:"maxlength,omitempty"` // The input's element name. Name string `json:"name"` // NodeType represents this node's types. It is a mirror of `node.type` and is primarily used to allow compatibility with OpenAPI 3.0. In this struct it technically always is \"input\". text Text input Input img Image a Anchor script Script @@ -153,6 +155,38 @@ func (o *UiNodeInputAttributes) SetLabel(v UiText) { o.Label = &v } +// GetMaxlength returns the Maxlength field value if set, zero value otherwise. +func (o *UiNodeInputAttributes) GetMaxlength() int64 { + if o == nil || o.Maxlength == nil { + var ret int64 + return ret + } + return *o.Maxlength +} + +// GetMaxlengthOk returns a tuple with the Maxlength field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UiNodeInputAttributes) GetMaxlengthOk() (*int64, bool) { + if o == nil || o.Maxlength == nil { + return nil, false + } + return o.Maxlength, true +} + +// HasMaxlength returns a boolean if a field has been set. +func (o *UiNodeInputAttributes) HasMaxlength() bool { + if o != nil && o.Maxlength != nil { + return true + } + + return false +} + +// SetMaxlength gets a reference to the given int64 and assigns it to the Maxlength field. +func (o *UiNodeInputAttributes) SetMaxlength(v int64) { + o.Maxlength = &v +} + // GetName returns the Name field value func (o *UiNodeInputAttributes) GetName() string { if o == nil { @@ -461,6 +495,9 @@ func (o UiNodeInputAttributes) MarshalJSON() ([]byte, error) { if o.Label != nil { toSerialize["label"] = o.Label } + if o.Maxlength != nil { + toSerialize["maxlength"] = o.Maxlength + } if true { toSerialize["name"] = o.Name } diff --git a/internal/httpclient/model_ui_node_input_attributes.go b/internal/httpclient/model_ui_node_input_attributes.go index 7056a308d651..f8deff5d5417 100644 --- a/internal/httpclient/model_ui_node_input_attributes.go +++ b/internal/httpclient/model_ui_node_input_attributes.go @@ -22,6 +22,8 @@ type UiNodeInputAttributes struct { // Sets the input's disabled field to true or false. Disabled bool `json:"disabled"` Label *UiText `json:"label,omitempty"` + // MaxLength may contain the input's maximum length. + Maxlength *int64 `json:"maxlength,omitempty"` // The input's element name. Name string `json:"name"` // NodeType represents this node's types. It is a mirror of `node.type` and is primarily used to allow compatibility with OpenAPI 3.0. In this struct it technically always is \"input\". text Text input Input img Image a Anchor script Script @@ -153,6 +155,38 @@ func (o *UiNodeInputAttributes) SetLabel(v UiText) { o.Label = &v } +// GetMaxlength returns the Maxlength field value if set, zero value otherwise. +func (o *UiNodeInputAttributes) GetMaxlength() int64 { + if o == nil || o.Maxlength == nil { + var ret int64 + return ret + } + return *o.Maxlength +} + +// GetMaxlengthOk returns a tuple with the Maxlength field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UiNodeInputAttributes) GetMaxlengthOk() (*int64, bool) { + if o == nil || o.Maxlength == nil { + return nil, false + } + return o.Maxlength, true +} + +// HasMaxlength returns a boolean if a field has been set. +func (o *UiNodeInputAttributes) HasMaxlength() bool { + if o != nil && o.Maxlength != nil { + return true + } + + return false +} + +// SetMaxlength gets a reference to the given int64 and assigns it to the Maxlength field. +func (o *UiNodeInputAttributes) SetMaxlength(v int64) { + o.Maxlength = &v +} + // GetName returns the Name field value func (o *UiNodeInputAttributes) GetName() string { if o == nil { @@ -461,6 +495,9 @@ func (o UiNodeInputAttributes) MarshalJSON() ([]byte, error) { if o.Label != nil { toSerialize["label"] = o.Label } + if o.Maxlength != nil { + toSerialize["maxlength"] = o.Maxlength + } if true { toSerialize["name"] = o.Name } diff --git a/selfservice/flow/recovery/.snapshots/TestHandleError-flow=api-case=fails_if_active_strategy_is_disabled.json b/selfservice/flow/recovery/.snapshots/TestHandleError-flow=api-case=fails_if_active_strategy_is_disabled.json index f4c0270da2dc..17eb6e965bcb 100644 --- a/selfservice/flow/recovery/.snapshots/TestHandleError-flow=api-case=fails_if_active_strategy_is_disabled.json +++ b/selfservice/flow/recovery/.snapshots/TestHandleError-flow=api-case=fails_if_active_strategy_is_disabled.json @@ -50,8 +50,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } } diff --git a/selfservice/flow/recovery/.snapshots/TestHandleError-flow=spa-case=fails_if_active_strategy_is_disabled.json b/selfservice/flow/recovery/.snapshots/TestHandleError-flow=spa-case=fails_if_active_strategy_is_disabled.json index 56782eed4571..a9ad1e527fb4 100644 --- a/selfservice/flow/recovery/.snapshots/TestHandleError-flow=spa-case=fails_if_active_strategy_is_disabled.json +++ b/selfservice/flow/recovery/.snapshots/TestHandleError-flow=spa-case=fails_if_active_strategy_is_disabled.json @@ -50,8 +50,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } } diff --git a/selfservice/flow/recovery/.snapshots/TestHandleError_WithContinueWith-flow=api-case=fails_if_active_strategy_is_disabled.json b/selfservice/flow/recovery/.snapshots/TestHandleError_WithContinueWith-flow=api-case=fails_if_active_strategy_is_disabled.json index f4c0270da2dc..17eb6e965bcb 100644 --- a/selfservice/flow/recovery/.snapshots/TestHandleError_WithContinueWith-flow=api-case=fails_if_active_strategy_is_disabled.json +++ b/selfservice/flow/recovery/.snapshots/TestHandleError_WithContinueWith-flow=api-case=fails_if_active_strategy_is_disabled.json @@ -50,8 +50,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } } diff --git a/selfservice/flow/recovery/.snapshots/TestHandleError_WithContinueWith-flow=spa-case=fails_if_active_strategy_is_disabled.json b/selfservice/flow/recovery/.snapshots/TestHandleError_WithContinueWith-flow=spa-case=fails_if_active_strategy_is_disabled.json index 56782eed4571..a9ad1e527fb4 100644 --- a/selfservice/flow/recovery/.snapshots/TestHandleError_WithContinueWith-flow=spa-case=fails_if_active_strategy_is_disabled.json +++ b/selfservice/flow/recovery/.snapshots/TestHandleError_WithContinueWith-flow=spa-case=fails_if_active_strategy_is_disabled.json @@ -50,8 +50,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } } diff --git a/selfservice/strategy/code/.snapshots/TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json b/selfservice/strategy/code/.snapshots/TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json index a9f46bedb5c4..7030380e7fc2 100644 --- a/selfservice/strategy/code/.snapshots/TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json +++ b/selfservice/strategy/code/.snapshots/TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json @@ -31,8 +31,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } } diff --git a/selfservice/strategy/code/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads.json b/selfservice/strategy/code/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads.json index ec1092ad77a6..195ca691e981 100644 --- a/selfservice/strategy/code/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads.json +++ b/selfservice/strategy/code/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads.json @@ -43,8 +43,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } }, diff --git a/selfservice/strategy/code/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json b/selfservice/strategy/code/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json index dbf1dcd2cbb7..8d24938c9ae3 100644 --- a/selfservice/strategy/code/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json +++ b/selfservice/strategy/code/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json @@ -58,8 +58,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } } diff --git a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=api.json b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=api.json index ec1092ad77a6..195ca691e981 100644 --- a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=api.json +++ b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=api.json @@ -43,8 +43,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } }, diff --git a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=browser.json b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=browser.json index ec1092ad77a6..195ca691e981 100644 --- a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=browser.json +++ b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=browser.json @@ -43,8 +43,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } }, diff --git a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=spa.json b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=spa.json index ec1092ad77a6..195ca691e981 100644 --- a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=spa.json +++ b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=spa.json @@ -43,8 +43,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } }, diff --git a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json index dbf1dcd2cbb7..8d24938c9ae3 100644 --- a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json +++ b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json @@ -58,8 +58,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } } diff --git a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json index dbf1dcd2cbb7..8d24938c9ae3 100644 --- a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json +++ b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json @@ -58,8 +58,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } } diff --git a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json index dbf1dcd2cbb7..8d24938c9ae3 100644 --- a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json +++ b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json @@ -58,8 +58,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } } 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 37f61ac9e827..01def57fd58f 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 @@ -30,8 +30,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } }, 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 42456da54dc5..7e7096cd7358 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 @@ -44,8 +44,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } } diff --git a/selfservice/strategy/code/strategy.go b/selfservice/strategy/code/strategy.go index 80147786477a..351949f7cd95 100644 --- a/selfservice/strategy/code/strategy.go +++ b/selfservice/strategy/code/strategy.go @@ -193,7 +193,7 @@ func (s *Strategy) populateChooseMethodFlow(r *http.Request, f flow.Flow) error node.NewInputField("email", nil, node.CodeGroup, node.InputAttributeTypeEmail, node.WithRequiredInputAttribute). WithMetaLabel(text.NewInfoNodeInputEmail()), ) - codeMetaLabel = text.NewInfoNodeLabelSubmit() + codeMetaLabel = text.NewInfoNodeLabelContinue() case *login.Flow: ds, err := s.deps.Config().DefaultIdentityTraitsSchemaURL(ctx) if err != nil { @@ -363,13 +363,16 @@ func (s *Strategy) populateEmailSentFlow(ctx context.Context, f flow.Flow) error ) // code input field - freshNodes.Upsert(node.NewInputField("code", nil, node.CodeGroup, node.InputAttributeTypeText, node.WithRequiredInputAttribute). + freshNodes.Upsert(node.NewInputField("code", nil, node.CodeGroup, node.InputAttributeTypeText, node.WithRequiredInputAttribute, node.WithInputAttributes(func(a *node.InputAttributes) { + a.Pattern = "[0-9]+" + a.MaxLength = CodeLength + })). WithMetaLabel(codeMetaLabel)) // code submit button freshNodes. Append(node.NewInputField("method", s.ID(), node.CodeGroup, node.InputAttributeTypeSubmit). - WithMetaLabel(text.NewInfoNodeLabelSubmit())) + WithMetaLabel(text.NewInfoNodeLabelContinue())) if resendNode != nil { freshNodes.Append(resendNode) diff --git a/selfservice/strategy/code/strategy_recovery.go b/selfservice/strategy/code/strategy_recovery.go index 305fc73c6327..6dfe93b44446 100644 --- a/selfservice/strategy/code/strategy_recovery.go +++ b/selfservice/strategy/code/strategy_recovery.go @@ -43,7 +43,7 @@ func (s *Strategy) PopulateRecoveryMethod(r *http.Request, f *recovery.Flow) err f.UI. GetNodes(). Append(node.NewInputField("method", s.RecoveryStrategyID(), node.CodeGroup, node.InputAttributeTypeSubmit). - WithMetaLabel(text.NewInfoNodeLabelSubmit())) + WithMetaLabel(text.NewInfoNodeLabelContinue())) return nil } @@ -409,6 +409,7 @@ func (s *Strategy) recoveryHandleFormSubmission(w http.ResponseWriter, r *http.R f.UI.Nodes.Append(node.NewInputField("code", nil, node.CodeGroup, node.InputAttributeTypeText, node.WithInputAttributes(func(a *node.InputAttributes) { a.Required = true a.Pattern = "[0-9]+" + a.MaxLength = CodeLength })). WithMetaLabel(text.NewInfoNodeLabelRecoveryCode()), ) @@ -417,7 +418,7 @@ func (s *Strategy) recoveryHandleFormSubmission(w http.ResponseWriter, r *http.R f.UI. GetNodes(). Append(node.NewInputField("method", s.RecoveryStrategyID(), node.CodeGroup, node.InputAttributeTypeSubmit). - WithMetaLabel(text.NewInfoNodeLabelSubmit())) + WithMetaLabel(text.NewInfoNodeLabelContinue())) f.UI.Nodes.Append(node.NewInputField("email", body.Email, node.CodeGroup, node.InputAttributeTypeSubmit). WithMetaLabel(text.NewInfoNodeResendOTP()), diff --git a/selfservice/strategy/code/strategy_recovery_admin.go b/selfservice/strategy/code/strategy_recovery_admin.go index 8964682afe30..d9792a0f0fba 100644 --- a/selfservice/strategy/code/strategy_recovery_admin.go +++ b/selfservice/strategy/code/strategy_recovery_admin.go @@ -162,13 +162,16 @@ func (s *Strategy) createRecoveryCodeForIdentity(w http.ResponseWriter, r *http. recoveryFlow.DangerousSkipCSRFCheck = true recoveryFlow.State = flow.StateEmailSent recoveryFlow.UI.Nodes = node.Nodes{} - recoveryFlow.UI.Nodes.Append(node.NewInputField("code", nil, node.CodeGroup, node.InputAttributeTypeText, node.WithRequiredInputAttribute). + recoveryFlow.UI.Nodes.Append(node.NewInputField("code", nil, node.CodeGroup, node.InputAttributeTypeText, node.WithRequiredInputAttribute, node.WithInputAttributes(func(a *node.InputAttributes) { + a.Pattern = "[0-9]+" + a.MaxLength = CodeLength + })). WithMetaLabel(text.NewInfoNodeLabelRecoveryCode()), ) recoveryFlow.UI.Nodes. Append(node.NewInputField("method", s.RecoveryStrategyID(), node.CodeGroup, node.InputAttributeTypeSubmit). - WithMetaLabel(text.NewInfoNodeLabelSubmit())) + WithMetaLabel(text.NewInfoNodeLabelContinue())) if err := s.deps.RecoveryFlowPersister().CreateRecoveryFlow(ctx, recoveryFlow); err != nil { s.deps.Writer().WriteError(w, r, err) diff --git a/selfservice/strategy/link/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads.json b/selfservice/strategy/link/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads.json index 3bb3cbbf3ef6..5ac9946936c8 100644 --- a/selfservice/strategy/link/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads.json +++ b/selfservice/strategy/link/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads.json @@ -43,8 +43,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } }, diff --git a/selfservice/strategy/link/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json b/selfservice/strategy/link/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json index 498575cfee1b..1a8d048fe37d 100644 --- a/selfservice/strategy/link/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json +++ b/selfservice/strategy/link/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json @@ -45,8 +45,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } } diff --git a/selfservice/strategy/link/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads.json b/selfservice/strategy/link/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads.json index 3bb3cbbf3ef6..5ac9946936c8 100644 --- a/selfservice/strategy/link/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads.json +++ b/selfservice/strategy/link/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads.json @@ -43,8 +43,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } }, diff --git a/selfservice/strategy/link/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads_after_submission.json b/selfservice/strategy/link/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads_after_submission.json index 498575cfee1b..1a8d048fe37d 100644 --- a/selfservice/strategy/link/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads_after_submission.json +++ b/selfservice/strategy/link/.snapshots/TestVerification-description=should_set_all_the_correct_verification_payloads_after_submission.json @@ -45,8 +45,8 @@ "messages": [], "meta": { "label": { - "id": 1070005, - "text": "Submit", + "id": 1070009, + "text": "Continue", "type": "info" } } diff --git a/selfservice/strategy/link/strategy_recovery.go b/selfservice/strategy/link/strategy_recovery.go index c8be6025f840..202d58f714c0 100644 --- a/selfservice/strategy/link/strategy_recovery.go +++ b/selfservice/strategy/link/strategy_recovery.go @@ -56,7 +56,7 @@ func (s *Strategy) PopulateRecoveryMethod(r *http.Request, f *recovery.Flow) err // v0.5: form.Field{Name: "email", Type: "email", Required: true}, node.NewInputField("email", nil, node.LinkGroup, node.InputAttributeTypeEmail, node.WithRequiredInputAttribute).WithMetaLabel(text.NewInfoNodeInputEmail()), ) - f.UI.GetNodes().Append(node.NewInputField("method", s.RecoveryStrategyID(), node.LinkGroup, node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoNodeLabelSubmit())) + f.UI.GetNodes().Append(node.NewInputField("method", s.RecoveryStrategyID(), node.LinkGroup, node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoNodeLabelContinue())) return nil } diff --git a/selfservice/strategy/link/strategy_verification.go b/selfservice/strategy/link/strategy_verification.go index a2a72ea9a277..61f95da52fef 100644 --- a/selfservice/strategy/link/strategy_verification.go +++ b/selfservice/strategy/link/strategy_verification.go @@ -44,7 +44,7 @@ func (s *Strategy) PopulateVerificationMethod(r *http.Request, f *verification.F // v0.5: form.Field{Name: "email", Type: "email", Required: true} node.NewInputField("email", nil, node.LinkGroup, node.InputAttributeTypeEmail, node.WithRequiredInputAttribute).WithMetaLabel(text.NewInfoNodeInputEmail()), ) - f.UI.GetNodes().Append(node.NewInputField("method", s.VerificationStrategyID(), node.LinkGroup, node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoNodeLabelSubmit())) + f.UI.GetNodes().Append(node.NewInputField("method", s.VerificationStrategyID(), node.LinkGroup, node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoNodeLabelContinue())) return nil } diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json index 33c3cd4a7c34..0635ea89a614 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json @@ -53,10 +53,10 @@ "disabled": false, "name": "passkey_login_trigger", "node_type": "input", - "onclick": "window.__oryPasskeyLogin()", - "onclick_trigger": "oryPasskeyLogin", - "onload": "window.__oryPasskeyLoginAutocompleteInit()", - "onload_trigger": "oryPasskeyLoginAutocompleteInit", + "onclick": "window.oryPasskeyLogin()", + "onclickTrigger": "oryPasskeyLogin", + "onload": "window.oryPasskeyLoginAutocompleteInit()", + "onloadTrigger": "oryPasskeyLoginAutocompleteInit", "type": "button", "value": "" }, diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json index 9b6602d9064f..c9ece0d3c08e 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json @@ -45,8 +45,8 @@ "disabled": false, "name": "passkey_login_trigger", "node_type": "input", - "onclick": "window.__oryPasskeyLogin()", - "onclick_trigger": "oryPasskeyLogin", + "onclick": "window.oryPasskeyLogin()", + "onclickTrigger": "oryPasskeyLogin", "type": "button", "value": "" }, diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json index 9b6602d9064f..c9ece0d3c08e 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json @@ -45,8 +45,8 @@ "disabled": false, "name": "passkey_login_trigger", "node_type": "input", - "onclick": "window.__oryPasskeyLogin()", - "onclick_trigger": "oryPasskeyLogin", + "onclick": "window.oryPasskeyLogin()", + "onclickTrigger": "oryPasskeyLogin", "type": "button", "value": "" }, diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json b/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json index 10cdc52924d6..b7e2168a1591 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json @@ -4,8 +4,8 @@ "disabled": false, "name": "passkey_register_trigger", "node_type": "input", - "onclick": "window.__oryPasskeySettingsRegistration()", - "onclick_trigger": "oryPasskeySettingsRegistration", + "onclick": "window.oryPasskeySettingsRegistration()", + "onclickTrigger": "oryPasskeySettingsRegistration", "type": "button", "value": "" }, diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json b/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json index ce4393561cfd..88861c80cdcb 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json @@ -4,8 +4,8 @@ "disabled": false, "name": "passkey_register_trigger", "node_type": "input", - "onclick": "window.__oryPasskeySettingsRegistration()", - "onclick_trigger": "oryPasskeySettingsRegistration", + "onclick": "window.oryPasskeySettingsRegistration()", + "onclickTrigger": "oryPasskeySettingsRegistration", "type": "button", "value": "" }, diff --git a/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-browser.json b/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-browser.json index 4eb8c3d30eb7..e232b6edde24 100644 --- a/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-browser.json +++ b/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-browser.json @@ -70,8 +70,8 @@ "disabled": false, "name": "passkey_register_trigger", "node_type": "input", - "onclick": "window.__oryPasskeyRegistration()", - "onclick_trigger": "oryPasskeyRegistration", + "onclick": "window.oryPasskeyRegistration()", + "onclickTrigger": "oryPasskeyRegistration", "type": "button" }, "group": "passkey", diff --git a/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-spa.json b/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-spa.json index 4eb8c3d30eb7..e232b6edde24 100644 --- a/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-spa.json +++ b/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-spa.json @@ -70,8 +70,8 @@ "disabled": false, "name": "passkey_register_trigger", "node_type": "input", - "onclick": "window.__oryPasskeyRegistration()", - "onclick_trigger": "oryPasskeyRegistration", + "onclick": "window.oryPasskeyRegistration()", + "onclickTrigger": "oryPasskeyRegistration", "type": "button" }, "group": "passkey", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json index 472dc71f4672..71ffeaabc0d0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json @@ -57,7 +57,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json index 03a66e9c5616..77f06d2f6c1e 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json @@ -51,7 +51,7 @@ "name": "webauthn_login_trigger", "type": "button", "disabled": false, - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "node_type": "input" }, "messages": [], diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json index 03a66e9c5616..77f06d2f6c1e 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json @@ -51,7 +51,7 @@ "name": "webauthn_login_trigger", "type": "button", "disabled": false, - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "node_type": "input" }, "messages": [], diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-browser.json index c359007414d0..c87a991f8f6d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-browser.json @@ -58,7 +58,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-spa.json index c359007414d0..c87a991f8f6d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-spa.json @@ -58,7 +58,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-browser.json index c359007414d0..c87a991f8f6d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-browser.json @@ -58,7 +58,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-spa.json index c359007414d0..c87a991f8f6d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-spa.json @@ -58,7 +58,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-browser.json index c359007414d0..c87a991f8f6d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-browser.json @@ -58,7 +58,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-spa.json index c359007414d0..c87a991f8f6d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-spa.json @@ -58,7 +58,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-browser.json index c359007414d0..c87a991f8f6d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-browser.json @@ -58,7 +58,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-spa.json index c359007414d0..c87a991f8f6d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-spa.json @@ -58,7 +58,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-browser.json index c359007414d0..c87a991f8f6d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-browser.json @@ -58,7 +58,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-spa.json index c359007414d0..c87a991f8f6d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-spa.json @@ -58,7 +58,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-browser.json index c359007414d0..c87a991f8f6d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-browser.json @@ -58,7 +58,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-spa.json index c359007414d0..c87a991f8f6d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-spa.json @@ -58,7 +58,7 @@ "disabled": false, "name": "webauthn_login_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnLogin", + "onclickTrigger": "oryWebAuthnLogin", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json index 02acdfb345d5..7fab410d6716 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json @@ -97,7 +97,7 @@ "disabled": false, "name": "webauthn_register_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnRegistration", + "onclickTrigger": "oryWebAuthnRegistration", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json index 8fddd27469f3..68bfeefda8cc 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json @@ -49,7 +49,7 @@ "disabled": false, "name": "webauthn_register_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnRegistration", + "onclickTrigger": "oryWebAuthnRegistration", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json index edcf3a92b509..91b3ff4cfcbc 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json @@ -75,7 +75,7 @@ "disabled": false, "name": "webauthn_register_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnRegistration", + "onclickTrigger": "oryWebAuthnRegistration", "type": "button" }, "group": "webauthn", diff --git a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json index edcf3a92b509..91b3ff4cfcbc 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json @@ -75,7 +75,7 @@ "disabled": false, "name": "webauthn_register_trigger", "node_type": "input", - "onclick_trigger": "oryWebAuthnRegistration", + "onclickTrigger": "oryWebAuthnRegistration", "type": "button" }, "group": "webauthn", diff --git a/spec/api.json b/spec/api.json index 3e45e5768030..d0a21811f54d 100644 --- a/spec/api.json +++ b/spec/api.json @@ -2372,6 +2372,11 @@ "label": { "$ref": "#/components/schemas/uiText" }, + "maxlength": { + "description": "MaxLength may contain the input's maximum length.", + "format": "int64", + "type": "integer" + }, "name": { "description": "The input's element name.", "type": "string" diff --git a/spec/swagger.json b/spec/swagger.json index 860b1f416181..c00f074072bb 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -5444,6 +5444,11 @@ "label": { "$ref": "#/definitions/uiText" }, + "maxlength": { + "description": "MaxLength may contain the input's maximum length.", + "type": "integer", + "format": "int64" + }, "name": { "description": "The input's element name.", "type": "string" diff --git a/ui/node/attributes.go b/ui/node/attributes.go index 2c8045ecb743..7db1927c3499 100644 --- a/ui/node/attributes.go +++ b/ui/node/attributes.go @@ -118,6 +118,9 @@ type InputAttributes struct { // The trigger maps to a JavaScript function provided by Ory, which triggers actions such as PassKey registration or login. OnLoadTrigger js.WebAuthnTriggers `json:"onloadTrigger,omitempty"` + // MaxLength may contain the input's maximum length. + MaxLength int `json:"maxlength,omitempty"` + // NodeType represents this node's types. It is a mirror of `node.type` and // is primarily used to allow compatibility with OpenAPI 3.0. In this struct it technically always is "input". // From b8192fc088d041de4d09cb31868ff20221378d13 Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Fri, 26 Apr 2024 11:43:41 +0200 Subject: [PATCH 6/6] feat: set maxlength for totp input --- ...not_contain_email_field_when_creating_recovery_code.json | 2 ++ ..._all_the_correct_recovery_payloads_after_submission.json | 1 + ...correct_recovery_payloads_after_submission-type=api.json | 1 + ...ect_recovery_payloads_after_submission-type=browser.json | 1 + ...correct_recovery_payloads_after_submission-type=spa.json | 1 + ..._the_correct_verification_payloads_after_submission.json | 2 ++ ...eLogin-flow=passwordless-case=passkey_button_exists.json | 2 +- ...fresh-case=refresh_passwordless_credentials-browser.json | 2 +- ...w=refresh-case=refresh_passwordless_credentials-spa.json | 2 +- ...ttings-case=a_device_is_shown_which_can_be_unlinked.json | 2 +- ...mpleteSettings-case=one_activation_element_is_shown.json | 2 +- ...TestRegistration-case=passkey_button_exists-browser.json | 2 +- .../TestRegistration-case=passkey_button_exists-spa.json | 2 +- ...gin-case=totp_payload_is_set_when_identity_has_totp.json | 1 + selfservice/strategy/totp/generator.go | 3 ++- selfservice/strategy/totp/login.go | 2 +- ...=webauthn_payload_is_set_when_identity_has_webauthn.json | 2 +- ...ould_fail_if_webauthn_login_is_invalid-type=browser.json | 2 +- ...e=should_fail_if_webauthn_login_is_invalid-type=spa.json | 2 +- ...0_credentials-passwordless_enabled=false#01-browser.json | 2 +- ...fa_v0_credentials-passwordless_enabled=false#01-spa.json | 2 +- ...0_credentials-passwordless_enabled=false#02-browser.json | 2 +- ...fa_v0_credentials-passwordless_enabled=false#02-spa.json | 2 +- ...a_v0_credentials-passwordless_enabled=false-browser.json | 2 +- ...e=mfa_v0_credentials-passwordless_enabled=false-spa.json | 2 +- ...v0_credentials-passwordless_enabled=true#01-browser.json | 2 +- ...mfa_v0_credentials-passwordless_enabled=true#01-spa.json | 2 +- ...v0_credentials-passwordless_enabled=true#02-browser.json | 2 +- ...mfa_v0_credentials-passwordless_enabled=true#02-spa.json | 2 +- ...fa_v0_credentials-passwordless_enabled=true-browser.json | 2 +- ...se=mfa_v0_credentials-passwordless_enabled=true-spa.json | 2 +- ...ttings-case=a_device_is_shown_which_can_be_unlinked.json | 2 +- ...mpleteSettings-case=one_activation_element_is_shown.json | 2 +- ...estRegistration-case=webauthn_button_exists-browser.json | 2 +- .../TestRegistration-case=webauthn_button_exists-spa.json | 2 +- ui/node/attributes_input.go | 6 ++++++ 36 files changed, 44 insertions(+), 28 deletions(-) diff --git a/selfservice/strategy/code/.snapshots/TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json b/selfservice/strategy/code/.snapshots/TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json index 7030380e7fc2..736578d0e543 100644 --- a/selfservice/strategy/code/.snapshots/TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json +++ b/selfservice/strategy/code/.snapshots/TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json @@ -6,7 +6,9 @@ "name": "code", "type": "text", "required": true, + "pattern": "[0-9]+", "disabled": false, + "maxlength": 6, "node_type": "input" }, "messages": [], diff --git a/selfservice/strategy/code/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json b/selfservice/strategy/code/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json index 8d24938c9ae3..a5ab6784616a 100644 --- a/selfservice/strategy/code/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json +++ b/selfservice/strategy/code/.snapshots/TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json @@ -21,6 +21,7 @@ "required": true, "pattern": "[0-9]+", "disabled": false, + "maxlength": 6, "node_type": "input" }, "messages": [], diff --git a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json index 8d24938c9ae3..a5ab6784616a 100644 --- a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json +++ b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json @@ -21,6 +21,7 @@ "required": true, "pattern": "[0-9]+", "disabled": false, + "maxlength": 6, "node_type": "input" }, "messages": [], diff --git a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json index 8d24938c9ae3..a5ab6784616a 100644 --- a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json +++ b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json @@ -21,6 +21,7 @@ "required": true, "pattern": "[0-9]+", "disabled": false, + "maxlength": 6, "node_type": "input" }, "messages": [], diff --git a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json index 8d24938c9ae3..a5ab6784616a 100644 --- a/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json +++ b/selfservice/strategy/code/.snapshots/TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json @@ -21,6 +21,7 @@ "required": true, "pattern": "[0-9]+", "disabled": false, + "maxlength": 6, "node_type": "input" }, "messages": [], 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 7e7096cd7358..fde2aae2986f 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 @@ -19,7 +19,9 @@ "name": "code", "type": "text", "required": true, + "pattern": "[0-9]+", "disabled": false, + "maxlength": 6, "node_type": "input" }, "messages": [], diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json index 0635ea89a614..e99dd52e418e 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json @@ -38,7 +38,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json index c9ece0d3c08e..1e026fb9979a 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json @@ -30,7 +30,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json index c9ece0d3c08e..1e026fb9979a 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json @@ -30,7 +30,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json b/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json index b7e2168a1591..a0383567eda4 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json @@ -110,7 +110,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json b/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json index 88861c80cdcb..8d91edf04ce5 100644 --- a/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json +++ b/selfservice/strategy/passkey/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json @@ -62,7 +62,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-browser.json b/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-browser.json index e232b6edde24..e4c5160c9697 100644 --- a/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-browser.json +++ b/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-browser.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-spa.json b/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-spa.json index e232b6edde24..e4c5160c9697 100644 --- a/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-spa.json +++ b/selfservice/strategy/passkey/.snapshots/TestRegistration-case=passkey_button_exists-spa.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/totp/.snapshots/TestCompleteLogin-case=totp_payload_is_set_when_identity_has_totp.json b/selfservice/strategy/totp/.snapshots/TestCompleteLogin-case=totp_payload_is_set_when_identity_has_totp.json index afae3de49f05..23611d1c2255 100644 --- a/selfservice/strategy/totp/.snapshots/TestCompleteLogin-case=totp_payload_is_set_when_identity_has_totp.json +++ b/selfservice/strategy/totp/.snapshots/TestCompleteLogin-case=totp_payload_is_set_when_identity_has_totp.json @@ -15,6 +15,7 @@ { "attributes": { "disabled": false, + "maxlength": 6, "name": "totp_code", "node_type": "input", "required": true, diff --git a/selfservice/strategy/totp/generator.go b/selfservice/strategy/totp/generator.go index fe79d8991d0d..9846506f1671 100644 --- a/selfservice/strategy/totp/generator.go +++ b/selfservice/strategy/totp/generator.go @@ -25,6 +25,7 @@ import ( // So we need 160/8 = 20 key length. stdtotp.Generate uses the key // length for reading from crypto.Rand. const secretSize = 160 / 8 +const digits = otp.DigitsSix func NewKey(ctx context.Context, accountName string, d interface { config.Provider @@ -33,7 +34,7 @@ func NewKey(ctx context.Context, accountName string, d interface { Issuer: d.Config().TOTPIssuer(ctx), AccountName: accountName, SecretSize: secretSize, - Digits: otp.DigitsSix, + Digits: digits, Period: 30, }) if err != nil { diff --git a/selfservice/strategy/totp/login.go b/selfservice/strategy/totp/login.go index 2aaface8dc5c..7b4564cb165d 100644 --- a/selfservice/strategy/totp/login.go +++ b/selfservice/strategy/totp/login.go @@ -50,7 +50,7 @@ func (s *Strategy) PopulateLoginMethod(r *http.Request, requestedAAL identity.Au } sr.UI.SetCSRF(s.d.GenerateCSRFToken(r)) - sr.UI.SetNode(node.NewInputField("totp_code", "", node.TOTPGroup, node.InputAttributeTypeText, node.WithRequiredInputAttribute).WithMetaLabel(text.NewInfoLoginTOTPLabel())) + sr.UI.SetNode(node.NewInputField("totp_code", "", node.TOTPGroup, node.InputAttributeTypeText, node.WithRequiredInputAttribute, node.WithMaxLengthInputAttribute(int(digits))).WithMetaLabel(text.NewInfoLoginTOTPLabel())) sr.UI.GetNodes().Append(node.NewInputField("method", s.ID(), node.TOTPGroup, node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoLoginTOTP())) return nil diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json index 71ffeaabc0d0..71fbb382f0de 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json @@ -42,7 +42,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json index 77f06d2f6c1e..399562e7015d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json @@ -37,7 +37,7 @@ "async": true, "referrerpolicy": "no-referrer", "crossorigin": "anonymous", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "type": "text/javascript", "node_type": "script" }, diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json index 77f06d2f6c1e..399562e7015d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json @@ -37,7 +37,7 @@ "async": true, "referrerpolicy": "no-referrer", "crossorigin": "anonymous", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "type": "text/javascript", "node_type": "script" }, diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-browser.json index c87a991f8f6d..6b4d9b33d63d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-browser.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-spa.json index c87a991f8f6d..6b4d9b33d63d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#01-spa.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-browser.json index c87a991f8f6d..6b4d9b33d63d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-browser.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-spa.json index c87a991f8f6d..6b4d9b33d63d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false#02-spa.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-browser.json index c87a991f8f6d..6b4d9b33d63d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-browser.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-spa.json index c87a991f8f6d..6b4d9b33d63d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=false-spa.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-browser.json index c87a991f8f6d..6b4d9b33d63d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-browser.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-spa.json index c87a991f8f6d..6b4d9b33d63d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#01-spa.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-browser.json index c87a991f8f6d..6b4d9b33d63d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-browser.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-spa.json index c87a991f8f6d..6b4d9b33d63d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true#02-spa.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-browser.json index c87a991f8f6d..6b4d9b33d63d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-browser.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-spa.json index c87a991f8f6d..6b4d9b33d63d 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=refresh-case=passwordless-case=mfa_v0_credentials-passwordless_enabled=true-spa.json @@ -43,7 +43,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json index 7fab410d6716..f0edfe3c5966 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json @@ -116,7 +116,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json index 68bfeefda8cc..c15a847d4703 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteSettings-case=one_activation_element_is_shown.json @@ -68,7 +68,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json index 91b3ff4cfcbc..20e3d3566fb0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json @@ -94,7 +94,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json index 91b3ff4cfcbc..20e3d3566fb0 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json @@ -94,7 +94,7 @@ "async": true, "crossorigin": "anonymous", "id": "webauthn_script", - "integrity": "sha512-pRsQlAeFkuxTY2n9F69ZNQ7ah9WGFsvR63UGhDE2kt1X2n7vKsa4Xgl7293HRlZ33AD/+KR8eRNFMvjaau6GwQ==", + "integrity": "sha512-MDzBlwh32rr+eus2Yf1BetIj94m+ULLbewYDulbZjczycs81klNed+qQWG2yi2N03KV5uZlRJJtWdV2x9JNHzQ==", "node_type": "script", "referrerpolicy": "no-referrer", "type": "text/javascript" diff --git a/ui/node/attributes_input.go b/ui/node/attributes_input.go index b63ac9365a51..176c1a25b42e 100644 --- a/ui/node/attributes_input.go +++ b/ui/node/attributes_input.go @@ -38,6 +38,12 @@ func WithRequiredInputAttribute(a *InputAttributes) { a.Required = true } +func WithMaxLengthInputAttribute(maxLength int) func(a *InputAttributes) { + return func(a *InputAttributes) { + a.MaxLength = maxLength + } +} + func WithInputAttributes(f func(a *InputAttributes)) func(a *InputAttributes) { return func(a *InputAttributes) { f(a)