diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000000..fb457f39d53a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v16.19.0 diff --git a/go.mod b/go.mod index d59352f3a4d5..e345c9c069c0 100644 --- a/go.mod +++ b/go.mod @@ -76,7 +76,7 @@ require ( github.com/ory/jsonschema/v3 v3.0.7 github.com/ory/mail/v3 v3.0.0 github.com/ory/nosurf v1.2.7 - github.com/ory/x v0.0.534 + github.com/ory/x v0.0.537 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 @@ -152,6 +152,7 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fullstorydev/grpcurl v1.8.1 // indirect github.com/fxamacker/cbor/v2 v2.4.0 // indirect + github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.21.4 // indirect diff --git a/go.sum b/go.sum index 0ff117e1fd77..76c38e92d427 100644 --- a/go.sum +++ b/go.sum @@ -377,6 +377,8 @@ github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49P github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE= +github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -1111,6 +1113,12 @@ github.com/ory/sessions v1.2.2-0.20220110165800-b09c17334dc2/go.mod h1:dk2InVEVJ github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= github.com/ory/x v0.0.534 h1:hc49pmcOuHdJ6rbHVGtJJ4/LU88dzDCtEQKfgeo/ecU= github.com/ory/x v0.0.534/go.mod h1:CQopDsCC9t0tQsddE9UlyRFVEFd2xjKBVcw4nLMMMS0= +github.com/ory/x v0.0.536-0.20230216201722-1405c99b3827 h1:Ns8MzgUYejLA+r6tA/ShKMYwzXH+fptDsqhZTi/7LXg= +github.com/ory/x v0.0.536-0.20230216201722-1405c99b3827/go.mod h1:CQopDsCC9t0tQsddE9UlyRFVEFd2xjKBVcw4nLMMMS0= +github.com/ory/x v0.0.537-0.20230217104449-3134761394fe h1:ZMbhSfdN+hHuGcMneBquvh+5Z267XMIbyQWS0teHTMM= +github.com/ory/x v0.0.537-0.20230217104449-3134761394fe/go.mod h1:CQopDsCC9t0tQsddE9UlyRFVEFd2xjKBVcw4nLMMMS0= +github.com/ory/x v0.0.537 h1:FB8Tioza6pihvy/RsVNzX08Qg3/VpIhI9vBnEQ4iFmQ= +github.com/ory/x v0.0.537/go.mod h1:CQopDsCC9t0tQsddE9UlyRFVEFd2xjKBVcw4nLMMMS0= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= diff --git a/internal/client-go/model_registration_flow.go b/internal/client-go/model_registration_flow.go index 313bd5e32d2c..0cf620898a8c 100644 --- a/internal/client-go/model_registration_flow.go +++ b/internal/client-go/model_registration_flow.go @@ -31,6 +31,8 @@ type RegistrationFlow struct { RequestUrl string `json:"request_url"` // ReturnTo contains the requested return_to URL. ReturnTo *string `json:"return_to,omitempty"` + // TransientPayload is used to pass data from the registration to a webhook + TransientPayload map[string]interface{} `json:"transient_payload,omitempty"` // The flow type can either be `api` or `browser`. Type string `json:"type"` Ui UiContainer `json:"ui"` @@ -294,6 +296,38 @@ func (o *RegistrationFlow) SetReturnTo(v string) { o.ReturnTo = &v } +// GetTransientPayload returns the TransientPayload field value if set, zero value otherwise. +func (o *RegistrationFlow) GetTransientPayload() map[string]interface{} { + if o == nil || o.TransientPayload == nil { + var ret map[string]interface{} + return ret + } + return o.TransientPayload +} + +// GetTransientPayloadOk returns a tuple with the TransientPayload field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *RegistrationFlow) GetTransientPayloadOk() (map[string]interface{}, bool) { + if o == nil || o.TransientPayload == nil { + return nil, false + } + return o.TransientPayload, true +} + +// HasTransientPayload returns a boolean if a field has been set. +func (o *RegistrationFlow) HasTransientPayload() bool { + if o != nil && o.TransientPayload != nil { + return true + } + + return false +} + +// SetTransientPayload gets a reference to the given map[string]interface{} and assigns it to the TransientPayload field. +func (o *RegistrationFlow) SetTransientPayload(v map[string]interface{}) { + o.TransientPayload = v +} + // GetType returns the Type field value func (o *RegistrationFlow) GetType() string { if o == nil { @@ -368,6 +402,9 @@ func (o RegistrationFlow) MarshalJSON() ([]byte, error) { if o.ReturnTo != nil { toSerialize["return_to"] = o.ReturnTo } + if o.TransientPayload != nil { + toSerialize["transient_payload"] = o.TransientPayload + } if true { toSerialize["type"] = o.Type } diff --git a/internal/client-go/model_update_registration_flow_with_oidc_method.go b/internal/client-go/model_update_registration_flow_with_oidc_method.go index a5d56eb9ea8b..d5594e8be641 100644 --- a/internal/client-go/model_update_registration_flow_with_oidc_method.go +++ b/internal/client-go/model_update_registration_flow_with_oidc_method.go @@ -25,6 +25,8 @@ type UpdateRegistrationFlowWithOidcMethod struct { Provider string `json:"provider"` // The identity traits Traits map[string]interface{} `json:"traits,omitempty"` + // Transient data to pass along to any webhooks + TransientPayload map[string]interface{} `json:"transient_payload,omitempty"` } // NewUpdateRegistrationFlowWithOidcMethod instantiates a new UpdateRegistrationFlowWithOidcMethod object @@ -158,6 +160,38 @@ func (o *UpdateRegistrationFlowWithOidcMethod) SetTraits(v map[string]interface{ o.Traits = v } +// GetTransientPayload returns the TransientPayload field value if set, zero value otherwise. +func (o *UpdateRegistrationFlowWithOidcMethod) GetTransientPayload() map[string]interface{} { + if o == nil || o.TransientPayload == nil { + var ret map[string]interface{} + return ret + } + return o.TransientPayload +} + +// GetTransientPayloadOk returns a tuple with the TransientPayload field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UpdateRegistrationFlowWithOidcMethod) GetTransientPayloadOk() (map[string]interface{}, bool) { + if o == nil || o.TransientPayload == nil { + return nil, false + } + return o.TransientPayload, true +} + +// HasTransientPayload returns a boolean if a field has been set. +func (o *UpdateRegistrationFlowWithOidcMethod) HasTransientPayload() bool { + if o != nil && o.TransientPayload != nil { + return true + } + + return false +} + +// SetTransientPayload gets a reference to the given map[string]interface{} and assigns it to the TransientPayload field. +func (o *UpdateRegistrationFlowWithOidcMethod) SetTransientPayload(v map[string]interface{}) { + o.TransientPayload = v +} + func (o UpdateRegistrationFlowWithOidcMethod) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} if o.CsrfToken != nil { @@ -172,6 +206,9 @@ func (o UpdateRegistrationFlowWithOidcMethod) MarshalJSON() ([]byte, error) { if o.Traits != nil { toSerialize["traits"] = o.Traits } + if o.TransientPayload != nil { + toSerialize["transient_payload"] = o.TransientPayload + } return json.Marshal(toSerialize) } diff --git a/internal/client-go/model_update_registration_flow_with_password_method.go b/internal/client-go/model_update_registration_flow_with_password_method.go index 94c081ce9f1f..3a86a3002c88 100644 --- a/internal/client-go/model_update_registration_flow_with_password_method.go +++ b/internal/client-go/model_update_registration_flow_with_password_method.go @@ -25,6 +25,8 @@ type UpdateRegistrationFlowWithPasswordMethod struct { Password string `json:"password"` // The identity's traits Traits map[string]interface{} `json:"traits"` + // Transient data to pass along to any webhooks + TransientPayload map[string]interface{} `json:"transient_payload,omitempty"` } // NewUpdateRegistrationFlowWithPasswordMethod instantiates a new UpdateRegistrationFlowWithPasswordMethod object @@ -151,6 +153,38 @@ func (o *UpdateRegistrationFlowWithPasswordMethod) SetTraits(v map[string]interf o.Traits = v } +// GetTransientPayload returns the TransientPayload field value if set, zero value otherwise. +func (o *UpdateRegistrationFlowWithPasswordMethod) GetTransientPayload() map[string]interface{} { + if o == nil || o.TransientPayload == nil { + var ret map[string]interface{} + return ret + } + return o.TransientPayload +} + +// GetTransientPayloadOk returns a tuple with the TransientPayload field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UpdateRegistrationFlowWithPasswordMethod) GetTransientPayloadOk() (map[string]interface{}, bool) { + if o == nil || o.TransientPayload == nil { + return nil, false + } + return o.TransientPayload, true +} + +// HasTransientPayload returns a boolean if a field has been set. +func (o *UpdateRegistrationFlowWithPasswordMethod) HasTransientPayload() bool { + if o != nil && o.TransientPayload != nil { + return true + } + + return false +} + +// SetTransientPayload gets a reference to the given map[string]interface{} and assigns it to the TransientPayload field. +func (o *UpdateRegistrationFlowWithPasswordMethod) SetTransientPayload(v map[string]interface{}) { + o.TransientPayload = v +} + func (o UpdateRegistrationFlowWithPasswordMethod) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} if o.CsrfToken != nil { @@ -165,6 +199,9 @@ func (o UpdateRegistrationFlowWithPasswordMethod) MarshalJSON() ([]byte, error) if true { toSerialize["traits"] = o.Traits } + if o.TransientPayload != nil { + toSerialize["transient_payload"] = o.TransientPayload + } return json.Marshal(toSerialize) } diff --git a/internal/client-go/model_update_registration_flow_with_web_authn_method.go b/internal/client-go/model_update_registration_flow_with_web_authn_method.go index a8f428769283..1249f645c0a1 100644 --- a/internal/client-go/model_update_registration_flow_with_web_authn_method.go +++ b/internal/client-go/model_update_registration_flow_with_web_authn_method.go @@ -23,6 +23,8 @@ type UpdateRegistrationFlowWithWebAuthnMethod struct { Method string `json:"method"` // The identity's traits Traits map[string]interface{} `json:"traits"` + // Transient data to pass along to any webhooks + TransientPayload map[string]interface{} `json:"transient_payload,omitempty"` // Register a WebAuthn Security Key It is expected that the JSON returned by the WebAuthn registration process is included here. WebauthnRegister *string `json:"webauthn_register,omitempty"` // Name of the WebAuthn Security Key to be Added A human-readable name for the security key which will be added. @@ -128,6 +130,38 @@ func (o *UpdateRegistrationFlowWithWebAuthnMethod) SetTraits(v map[string]interf o.Traits = v } +// GetTransientPayload returns the TransientPayload field value if set, zero value otherwise. +func (o *UpdateRegistrationFlowWithWebAuthnMethod) GetTransientPayload() map[string]interface{} { + if o == nil || o.TransientPayload == nil { + var ret map[string]interface{} + return ret + } + return o.TransientPayload +} + +// GetTransientPayloadOk returns a tuple with the TransientPayload field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UpdateRegistrationFlowWithWebAuthnMethod) GetTransientPayloadOk() (map[string]interface{}, bool) { + if o == nil || o.TransientPayload == nil { + return nil, false + } + return o.TransientPayload, true +} + +// HasTransientPayload returns a boolean if a field has been set. +func (o *UpdateRegistrationFlowWithWebAuthnMethod) HasTransientPayload() bool { + if o != nil && o.TransientPayload != nil { + return true + } + + return false +} + +// SetTransientPayload gets a reference to the given map[string]interface{} and assigns it to the TransientPayload field. +func (o *UpdateRegistrationFlowWithWebAuthnMethod) SetTransientPayload(v map[string]interface{}) { + o.TransientPayload = v +} + // GetWebauthnRegister returns the WebauthnRegister field value if set, zero value otherwise. func (o *UpdateRegistrationFlowWithWebAuthnMethod) GetWebauthnRegister() string { if o == nil || o.WebauthnRegister == nil { @@ -203,6 +237,9 @@ func (o UpdateRegistrationFlowWithWebAuthnMethod) MarshalJSON() ([]byte, error) if true { toSerialize["traits"] = o.Traits } + if o.TransientPayload != nil { + toSerialize["transient_payload"] = o.TransientPayload + } if o.WebauthnRegister != nil { toSerialize["webauthn_register"] = o.WebauthnRegister } diff --git a/internal/httpclient/model_registration_flow.go b/internal/httpclient/model_registration_flow.go index 313bd5e32d2c..0cf620898a8c 100644 --- a/internal/httpclient/model_registration_flow.go +++ b/internal/httpclient/model_registration_flow.go @@ -31,6 +31,8 @@ type RegistrationFlow struct { RequestUrl string `json:"request_url"` // ReturnTo contains the requested return_to URL. ReturnTo *string `json:"return_to,omitempty"` + // TransientPayload is used to pass data from the registration to a webhook + TransientPayload map[string]interface{} `json:"transient_payload,omitempty"` // The flow type can either be `api` or `browser`. Type string `json:"type"` Ui UiContainer `json:"ui"` @@ -294,6 +296,38 @@ func (o *RegistrationFlow) SetReturnTo(v string) { o.ReturnTo = &v } +// GetTransientPayload returns the TransientPayload field value if set, zero value otherwise. +func (o *RegistrationFlow) GetTransientPayload() map[string]interface{} { + if o == nil || o.TransientPayload == nil { + var ret map[string]interface{} + return ret + } + return o.TransientPayload +} + +// GetTransientPayloadOk returns a tuple with the TransientPayload field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *RegistrationFlow) GetTransientPayloadOk() (map[string]interface{}, bool) { + if o == nil || o.TransientPayload == nil { + return nil, false + } + return o.TransientPayload, true +} + +// HasTransientPayload returns a boolean if a field has been set. +func (o *RegistrationFlow) HasTransientPayload() bool { + if o != nil && o.TransientPayload != nil { + return true + } + + return false +} + +// SetTransientPayload gets a reference to the given map[string]interface{} and assigns it to the TransientPayload field. +func (o *RegistrationFlow) SetTransientPayload(v map[string]interface{}) { + o.TransientPayload = v +} + // GetType returns the Type field value func (o *RegistrationFlow) GetType() string { if o == nil { @@ -368,6 +402,9 @@ func (o RegistrationFlow) MarshalJSON() ([]byte, error) { if o.ReturnTo != nil { toSerialize["return_to"] = o.ReturnTo } + if o.TransientPayload != nil { + toSerialize["transient_payload"] = o.TransientPayload + } if true { toSerialize["type"] = o.Type } diff --git a/internal/httpclient/model_update_registration_flow_with_oidc_method.go b/internal/httpclient/model_update_registration_flow_with_oidc_method.go index a5d56eb9ea8b..d5594e8be641 100644 --- a/internal/httpclient/model_update_registration_flow_with_oidc_method.go +++ b/internal/httpclient/model_update_registration_flow_with_oidc_method.go @@ -25,6 +25,8 @@ type UpdateRegistrationFlowWithOidcMethod struct { Provider string `json:"provider"` // The identity traits Traits map[string]interface{} `json:"traits,omitempty"` + // Transient data to pass along to any webhooks + TransientPayload map[string]interface{} `json:"transient_payload,omitempty"` } // NewUpdateRegistrationFlowWithOidcMethod instantiates a new UpdateRegistrationFlowWithOidcMethod object @@ -158,6 +160,38 @@ func (o *UpdateRegistrationFlowWithOidcMethod) SetTraits(v map[string]interface{ o.Traits = v } +// GetTransientPayload returns the TransientPayload field value if set, zero value otherwise. +func (o *UpdateRegistrationFlowWithOidcMethod) GetTransientPayload() map[string]interface{} { + if o == nil || o.TransientPayload == nil { + var ret map[string]interface{} + return ret + } + return o.TransientPayload +} + +// GetTransientPayloadOk returns a tuple with the TransientPayload field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UpdateRegistrationFlowWithOidcMethod) GetTransientPayloadOk() (map[string]interface{}, bool) { + if o == nil || o.TransientPayload == nil { + return nil, false + } + return o.TransientPayload, true +} + +// HasTransientPayload returns a boolean if a field has been set. +func (o *UpdateRegistrationFlowWithOidcMethod) HasTransientPayload() bool { + if o != nil && o.TransientPayload != nil { + return true + } + + return false +} + +// SetTransientPayload gets a reference to the given map[string]interface{} and assigns it to the TransientPayload field. +func (o *UpdateRegistrationFlowWithOidcMethod) SetTransientPayload(v map[string]interface{}) { + o.TransientPayload = v +} + func (o UpdateRegistrationFlowWithOidcMethod) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} if o.CsrfToken != nil { @@ -172,6 +206,9 @@ func (o UpdateRegistrationFlowWithOidcMethod) MarshalJSON() ([]byte, error) { if o.Traits != nil { toSerialize["traits"] = o.Traits } + if o.TransientPayload != nil { + toSerialize["transient_payload"] = o.TransientPayload + } return json.Marshal(toSerialize) } diff --git a/internal/httpclient/model_update_registration_flow_with_password_method.go b/internal/httpclient/model_update_registration_flow_with_password_method.go index 94c081ce9f1f..3a86a3002c88 100644 --- a/internal/httpclient/model_update_registration_flow_with_password_method.go +++ b/internal/httpclient/model_update_registration_flow_with_password_method.go @@ -25,6 +25,8 @@ type UpdateRegistrationFlowWithPasswordMethod struct { Password string `json:"password"` // The identity's traits Traits map[string]interface{} `json:"traits"` + // Transient data to pass along to any webhooks + TransientPayload map[string]interface{} `json:"transient_payload,omitempty"` } // NewUpdateRegistrationFlowWithPasswordMethod instantiates a new UpdateRegistrationFlowWithPasswordMethod object @@ -151,6 +153,38 @@ func (o *UpdateRegistrationFlowWithPasswordMethod) SetTraits(v map[string]interf o.Traits = v } +// GetTransientPayload returns the TransientPayload field value if set, zero value otherwise. +func (o *UpdateRegistrationFlowWithPasswordMethod) GetTransientPayload() map[string]interface{} { + if o == nil || o.TransientPayload == nil { + var ret map[string]interface{} + return ret + } + return o.TransientPayload +} + +// GetTransientPayloadOk returns a tuple with the TransientPayload field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UpdateRegistrationFlowWithPasswordMethod) GetTransientPayloadOk() (map[string]interface{}, bool) { + if o == nil || o.TransientPayload == nil { + return nil, false + } + return o.TransientPayload, true +} + +// HasTransientPayload returns a boolean if a field has been set. +func (o *UpdateRegistrationFlowWithPasswordMethod) HasTransientPayload() bool { + if o != nil && o.TransientPayload != nil { + return true + } + + return false +} + +// SetTransientPayload gets a reference to the given map[string]interface{} and assigns it to the TransientPayload field. +func (o *UpdateRegistrationFlowWithPasswordMethod) SetTransientPayload(v map[string]interface{}) { + o.TransientPayload = v +} + func (o UpdateRegistrationFlowWithPasswordMethod) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} if o.CsrfToken != nil { @@ -165,6 +199,9 @@ func (o UpdateRegistrationFlowWithPasswordMethod) MarshalJSON() ([]byte, error) if true { toSerialize["traits"] = o.Traits } + if o.TransientPayload != nil { + toSerialize["transient_payload"] = o.TransientPayload + } return json.Marshal(toSerialize) } diff --git a/internal/httpclient/model_update_registration_flow_with_web_authn_method.go b/internal/httpclient/model_update_registration_flow_with_web_authn_method.go index a8f428769283..1249f645c0a1 100644 --- a/internal/httpclient/model_update_registration_flow_with_web_authn_method.go +++ b/internal/httpclient/model_update_registration_flow_with_web_authn_method.go @@ -23,6 +23,8 @@ type UpdateRegistrationFlowWithWebAuthnMethod struct { Method string `json:"method"` // The identity's traits Traits map[string]interface{} `json:"traits"` + // Transient data to pass along to any webhooks + TransientPayload map[string]interface{} `json:"transient_payload,omitempty"` // Register a WebAuthn Security Key It is expected that the JSON returned by the WebAuthn registration process is included here. WebauthnRegister *string `json:"webauthn_register,omitempty"` // Name of the WebAuthn Security Key to be Added A human-readable name for the security key which will be added. @@ -128,6 +130,38 @@ func (o *UpdateRegistrationFlowWithWebAuthnMethod) SetTraits(v map[string]interf o.Traits = v } +// GetTransientPayload returns the TransientPayload field value if set, zero value otherwise. +func (o *UpdateRegistrationFlowWithWebAuthnMethod) GetTransientPayload() map[string]interface{} { + if o == nil || o.TransientPayload == nil { + var ret map[string]interface{} + return ret + } + return o.TransientPayload +} + +// GetTransientPayloadOk returns a tuple with the TransientPayload field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UpdateRegistrationFlowWithWebAuthnMethod) GetTransientPayloadOk() (map[string]interface{}, bool) { + if o == nil || o.TransientPayload == nil { + return nil, false + } + return o.TransientPayload, true +} + +// HasTransientPayload returns a boolean if a field has been set. +func (o *UpdateRegistrationFlowWithWebAuthnMethod) HasTransientPayload() bool { + if o != nil && o.TransientPayload != nil { + return true + } + + return false +} + +// SetTransientPayload gets a reference to the given map[string]interface{} and assigns it to the TransientPayload field. +func (o *UpdateRegistrationFlowWithWebAuthnMethod) SetTransientPayload(v map[string]interface{}) { + o.TransientPayload = v +} + // GetWebauthnRegister returns the WebauthnRegister field value if set, zero value otherwise. func (o *UpdateRegistrationFlowWithWebAuthnMethod) GetWebauthnRegister() string { if o == nil || o.WebauthnRegister == nil { @@ -203,6 +237,9 @@ func (o UpdateRegistrationFlowWithWebAuthnMethod) MarshalJSON() ([]byte, error) if true { toSerialize["traits"] = o.Traits } + if o.TransientPayload != nil { + toSerialize["transient_payload"] = o.TransientPayload + } if o.WebauthnRegister != nil { toSerialize["webauthn_register"] = o.WebauthnRegister } diff --git a/selfservice/flow/registration/flow.go b/selfservice/flow/registration/flow.go index f83620360020..3be4dcbe6e4e 100644 --- a/selfservice/flow/registration/flow.go +++ b/selfservice/flow/registration/flow.go @@ -98,6 +98,9 @@ type Flow struct { // CSRFToken contains the anti-csrf token associated with this flow. Only set for browser flows. CSRFToken string `json:"-" db:"csrf_token"` NID uuid.UUID `json:"-" faker:"-" db:"nid"` + + // TransientPayload is used to pass data from the registration to a webhook + TransientPayload json.RawMessage `json:"transient_payload,omitempty" faker:"-" db:"-"` } func NewFlow(conf *config.Config, exp time.Duration, csrf string, r *http.Request, ft flow.Type) (*Flow, error) { diff --git a/selfservice/hook/stub/test_body.jsonnet b/selfservice/hook/stub/test_body.jsonnet index 0b2639368474..409fa13695ca 100644 --- a/selfservice/hook/stub/test_body.jsonnet +++ b/selfservice/hook/stub/test_body.jsonnet @@ -1,8 +1,9 @@ -function(ctx) { +function(ctx) std.prune({ flow_id: ctx.flow.id, identity_id: if std.objectHas(ctx, "identity") then ctx.identity.id, headers: ctx.request_headers, url: ctx.request_url, method: ctx.request_method, cookies: ctx.request_cookies, -} + transient_payload: if std.objectHas(ctx.flow, "transient_payload") then ctx.flow.transient_payload, +}) diff --git a/selfservice/hook/web_hook_integration_test.go b/selfservice/hook/web_hook_integration_test.go index 80df8d4e265d..c47a9d697e74 100644 --- a/selfservice/hook/web_hook_integration_test.go +++ b/selfservice/hook/web_hook_integration_test.go @@ -119,7 +119,6 @@ func TestWebHooks(t *testing.T) { h, _ := json.Marshal(req.Header) return fmt.Sprintf(`{ "flow_id": "%s", - "identity_id": null, "headers": %s, "method": "%s", "url": "%s", @@ -147,6 +146,23 @@ func TestWebHooks(t *testing.T) { }`, f.GetID(), s.Identity.ID, string(h), req.Method, "http://www.ory.sh/some_end_point") } + bodyWithFlowAndIdentityAndTransientPayload := func(req *http.Request, f flow.Flow, s *session.Session, tp json.RawMessage) string { + h, _ := json.Marshal(req.Header) + return fmt.Sprintf(`{ + "flow_id": "%s", + "identity_id": "%s", + "headers": %s, + "method": "%s", + "url": "%s", + "cookies": { + "Some-Cookie-1": "Some-Cookie-Value", + "Some-Cookie-2": "Some-other-Cookie-Value", + "Some-Cookie-3": "Third-Cookie-Value" + }, + "transient_payload": %s + }`, f.GetID(), s.Identity.ID, string(h), req.Method, "http://www.ory.sh/some_end_point", string(tp)) + } + for _, tc := range []struct { uc string callWebHook func(wh *hook.WebHook, req *http.Request, f flow.Flow, s *session.Session) error @@ -184,13 +200,28 @@ func TestWebHooks(t *testing.T) { }, }, { - uc: "Post Registration Hook", - createFlow: func() flow.Flow { return ®istration.Flow{ID: x.NewUUID()} }, + uc: "Post Registration Hook", + createFlow: func() flow.Flow { + return ®istration.Flow{ + ID: x.NewUUID(), + TransientPayload: json.RawMessage(`{ + "stuff": { + "name": "fubar", + "numbers": [42, 12345, 3.1415] + } + }`), + } + }, callWebHook: func(wh *hook.WebHook, req *http.Request, f flow.Flow, s *session.Session) error { return wh.ExecutePostRegistrationPostPersistHook(nil, req, f.(*registration.Flow), s) }, expectedBody: func(req *http.Request, f flow.Flow, s *session.Session) string { - return bodyWithFlowAndIdentity(req, f, s) + return bodyWithFlowAndIdentityAndTransientPayload(req, f, s, json.RawMessage(`{ + "stuff": { + "name": "fubar", + "numbers": [42, 12345, 3.1415] + } + }`)) }, }, { diff --git a/selfservice/strategy/oidc/.schema/link.schema.json b/selfservice/strategy/oidc/.schema/link.schema.json index bb740bf7d22a..0a3172c7a260 100644 --- a/selfservice/strategy/oidc/.schema/link.schema.json +++ b/selfservice/strategy/oidc/.schema/link.schema.json @@ -12,6 +12,10 @@ }, "traits": { "description": "DO NOT DELETE THIS FIELD. This field will be overwritten in login.go's and registration.go's decoder() method. Do not add anything to this field as it has no effect." + }, + "transient_payload": { + "type": "object", + "additionalProperties": true } } } diff --git a/selfservice/strategy/oidc/strategy.go b/selfservice/strategy/oidc/strategy.go index 3caaba8a0d09..fe5fa41c2a41 100644 --- a/selfservice/strategy/oidc/strategy.go +++ b/selfservice/strategy/oidc/strategy.go @@ -118,9 +118,10 @@ type Strategy struct { } type authCodeContainer struct { - FlowID string `json:"flow_id"` - State string `json:"state"` - Traits json.RawMessage `json:"traits"` + FlowID string `json:"flow_id"` + State string `json:"state"` + Traits json.RawMessage `json:"traits"` + TransientPayload json.RawMessage `json:"transient_payload"` } func generateState(flowID string) string { @@ -367,6 +368,7 @@ func (s *Strategy) handleCallback(w http.ResponseWriter, r *http.Request, ps htt } return case *registration.Flow: + a.TransientPayload = cntnr.TransientPayload if ff, err := s.processRegistration(w, r, a, token, claims, provider, cntnr); err != nil { if ff != nil { s.forwardError(w, r, ff, err) diff --git a/selfservice/strategy/oidc/strategy_registration.go b/selfservice/strategy/oidc/strategy_registration.go index 7f2fde690124..5981d2501bb7 100644 --- a/selfservice/strategy/oidc/strategy_registration.go +++ b/selfservice/strategy/oidc/strategy_registration.go @@ -76,6 +76,11 @@ type UpdateRegistrationFlowWithOidcMethod struct { // // required: true Method string `json:"method"` + + // Transient data to pass along to any webhooks + // + // required: false + TransientPayload json.RawMessage `json:"transient_payload,omitempty"` } func (s *Strategy) newLinkDecoder(p interface{}, r *http.Request) error { @@ -113,6 +118,8 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat return s.handleError(w, r, f, "", nil, err) } + f.TransientPayload = p.TransientPayload + var pid = p.Provider // this can come from both url query and post body if pid == "" { return errors.WithStack(flow.ErrStrategyNotResponsible) @@ -144,9 +151,10 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat state := generateState(f.ID.String()) if err := s.d.ContinuityManager().Pause(r.Context(), w, r, sessionName, continuity.WithPayload(&authCodeContainer{ - State: state, - FlowID: f.ID.String(), - Traits: p.Traits, + State: state, + FlowID: f.ID.String(), + Traits: p.Traits, + TransientPayload: f.TransientPayload, }), continuity.WithLifespan(time.Minute*30)); err != nil { return s.handleError(w, r, f, pid, nil, err) diff --git a/selfservice/strategy/password/.schema/registration.schema.json b/selfservice/strategy/password/.schema/registration.schema.json index 91f0f2823952..9a4a11b01ea6 100644 --- a/selfservice/strategy/password/.schema/registration.schema.json +++ b/selfservice/strategy/password/.schema/registration.schema.json @@ -15,6 +15,10 @@ }, "method": { "type": "string" + }, + "transient_payload": { + "type": "object", + "additionalProperties": true } }, "if": { diff --git a/selfservice/strategy/password/registration.go b/selfservice/strategy/password/registration.go index b3ead97b434a..57ebca990923 100644 --- a/selfservice/strategy/password/registration.go +++ b/selfservice/strategy/password/registration.go @@ -46,6 +46,11 @@ type UpdateRegistrationFlowWithPasswordMethod struct { // // required: true Method string `json:"method"` + + // Transient data to pass along to any webhooks + // + // required: false + TransientPayload json.RawMessage `json:"transient_payload,omitempty"` } func (s *Strategy) RegisterRegistrationRoutes(_ *x.RouterPublic) { @@ -82,6 +87,8 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat return s.handleRegistrationError(w, r, f, &p, err) } + f.TransientPayload = p.TransientPayload + if err := flow.EnsureCSRF(s.d, r, f.Type, s.d.Config().DisableAPIFlowEnforcement(r.Context()), s.d.GenerateCSRFToken, p.CSRFToken); err != nil { return s.handleRegistrationError(w, r, f, &p, err) } diff --git a/selfservice/strategy/password/registration_test.go b/selfservice/strategy/password/registration_test.go index 8f9b0eefe922..1059d14f9cdf 100644 --- a/selfservice/strategy/password/registration_test.go +++ b/selfservice/strategy/password/registration_test.go @@ -136,6 +136,72 @@ func TestRegistration(t *testing.T) { return expectLoginBody(t, redirNoSessionTS, isAPI, isSPA, hc, values) } + t.Run("case=should reject invalid transient payload", func(t *testing.T) { + testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/registration.schema.json") + conf.MustSet(ctx, config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), []config.SelfServiceHook{{Name: "session"}}) + t.Cleanup(func() { + conf.MustSet(ctx, config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil) + }) + + for _, f := range flows { + t.Run("type="+f, func(t *testing.T) { + username := x.NewUUID().String() + body := registrationhelpers.ExpectValidationError(t, publicTS, conf, f, func(v url.Values) { + v.Set("traits.username", username) + v.Set("password", x.NewUUID().String()) + v.Set("traits.foobar", "bar") + v.Set("transient_payload", "42") + }) + assert.Equal(t, "bar", gjson.Get(body, "ui.nodes.#(attributes.name==traits.foobar).attributes.value").String(), "%s", body) + assert.Equal(t, username, gjson.Get(body, "ui.nodes.#(attributes.name==traits.username).attributes.value").String(), "%s", body) + assert.Equal(t, int64(4000026), gjson.Get(body, "ui.nodes.#(attributes.name==transient_payload).messages.0.id").Int(), "%s", body) + }) + } + }) + + t.Run("case=should accept valid transient payload", func(t *testing.T) { + testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/registration.schema.json") + conf.MustSet(ctx, config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), []config.SelfServiceHook{{Name: "session"}}) + t.Cleanup(func() { + conf.MustSet(ctx, config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil) + }) + + setValues := func(username string, v url.Values) { + v.Set("traits.username", username) + v.Set("password", x.NewUUID().String()) + v.Set("traits.foobar", "bar") + v.Set("transient_payload.stuff", "42") + } + + t.Run("type=api", func(t *testing.T) { + username := x.NewUUID().String() + body := expectSuccessfulLogin(t, true, false, nil, func(v url.Values) { + setValues(username, v) + }) + assert.Equal(t, username, 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) + }) + + t.Run("type=spa", func(t *testing.T) { + username := x.NewUUID().String() + body := expectSuccessfulLogin(t, false, true, nil, func(v url.Values) { + setValues(username, v) + }) + assert.Equal(t, username, 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) + }) + + t.Run("type=browser", func(t *testing.T) { + username := x.NewUUID().String() + body := expectSuccessfulLogin(t, false, false, nil, func(v url.Values) { + setValues(username, v) + }) + assert.Equal(t, username, gjson.Get(body, "identity.traits.username").String(), "%s", body) + }) + }) + t.Run("case=should pass and set up a session", func(t *testing.T) { testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/registration.schema.json") conf.MustSet(ctx, config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), []config.SelfServiceHook{{Name: "session"}}) diff --git a/selfservice/strategy/webauthn/.schema/registration.schema.json b/selfservice/strategy/webauthn/.schema/registration.schema.json index 68624249fc7a..2751e944e954 100644 --- a/selfservice/strategy/webauthn/.schema/registration.schema.json +++ b/selfservice/strategy/webauthn/.schema/registration.schema.json @@ -17,6 +17,10 @@ }, "webauthn_register_displayname": { "type": "string" + }, + "transient_payload": { + "type": "object", + "additionalProperties": true } }, "oneOf": [ diff --git a/selfservice/strategy/webauthn/registration.go b/selfservice/strategy/webauthn/registration.go index 5d1418d95da6..79186f7f4269 100644 --- a/selfservice/strategy/webauthn/registration.go +++ b/selfservice/strategy/webauthn/registration.go @@ -60,6 +60,11 @@ type updateRegistrationFlowWithWebAuthnMethod struct { // // swagger:ignore Flow string `json:"flow"` + + // Transient data to pass along to any webhooks + // + // required: false + TransientPayload json.RawMessage `json:"transient_payload,omitempty"` } func (s *Strategy) RegisterRegistrationRoutes(_ *x.RouterPublic) { @@ -97,6 +102,8 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat return s.handleRegistrationError(w, r, f, &p, err) } + f.TransientPayload = p.TransientPayload + if err := flow.EnsureCSRF(s.d, r, f.Type, s.d.Config().DisableAPIFlowEnforcement(r.Context()), s.d.GenerateCSRFToken, p.CSRFToken); err != nil { return s.handleRegistrationError(w, r, f, &p, err) } diff --git a/selfservice/strategy/webauthn/registration_test.go b/selfservice/strategy/webauthn/registration_test.go index ab9baa4eb4d5..59a559f7ce6d 100644 --- a/selfservice/strategy/webauthn/registration_test.go +++ b/selfservice/strategy/webauthn/registration_test.go @@ -171,6 +171,31 @@ func TestRegistration(t *testing.T) { } }) + t.Run("case=should reject invalid transient payload", func(t *testing.T) { + email := testhelpers.RandomEmail() + + var values = func(v url.Values) { + v.Set("traits.username", email) + v.Set("traits.foobar", "bar") + v.Set("transient_payload", "42") + v.Set(node.WebAuthnRegister, "{}") + v.Del("method") + } + + for _, f := range flows { + t.Run("type="+f, func(t *testing.T) { + actual := registrationhelpers.ExpectValidationError(t, publicTS, conf, f, values) + + assert.NotEmpty(t, gjson.Get(actual, "id").String(), "%s", actual) + assert.Contains(t, gjson.Get(actual, "ui.action").String(), publicTS.URL+registration.RouteSubmitFlow, "%s", actual) + registrationhelpers.CheckFormContent(t, []byte(actual), node.WebAuthnRegisterTrigger, "csrf_token", "traits.username", "traits.foobar") + assert.Equal(t, "bar", gjson.Get(actual, "ui.nodes.#(attributes.name==traits.foobar).attributes.value").String(), "%s", actual) + assert.Equal(t, email, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.username).attributes.value").String(), "%s", actual) + assert.Equal(t, int64(4000026), gjson.Get(actual, "ui.nodes.#(attributes.name==transient_payload).messages.0.id").Int(), "%s", actual) + }) + } + }) + t.Run("case=should return an error because webauthn response is invalid", func(t *testing.T) { email := testhelpers.RandomEmail() var values = func(v url.Values) { @@ -301,6 +326,35 @@ func TestRegistration(t *testing.T) { } }) + t.Run("case=should accept valid transient payload", func(t *testing.T) { + useReturnToFromTS(redirNoSessionTS) + t.Cleanup(func() { + useReturnToFromTS(redirTS) + }) + conf.MustSet(ctx, config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil) + + for _, f := range flows { + t.Run("type="+f, func(t *testing.T) { + email := testhelpers.RandomEmail() + actual := makeSuccessfulRegistration(t, f, redirNoSessionTS.URL+"/registration-return-ts", func(v url.Values) { + values(email)(v) + v.Set("transient_payload.stuff", "42") + }) + + if f == "spa" { + assert.Equal(t, email, gjson.Get(actual, "identity.traits.username").String(), "%s", actual) + assert.False(t, gjson.Get(actual, "session").Exists(), "because the registration yielded no session, the user is not expected to be signed in: %s", actual) + } else { + assert.Equal(t, "null\n", actual, "because the registration yielded no session, the user is not expected to be signed in: %s", actual) + } + + 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) + }) + } + }) + t.Run("case=should create the identity and a session and use the correct schema", func(t *testing.T) { conf.MustSet(ctx, config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypeWebAuthn.String()), []config.SelfServiceHook{{Name: "session"}}) conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "advanced-user") diff --git a/spec/api.json b/spec/api.json index 5f217d6c0b87..24a7ed80f033 100755 --- a/spec/api.json +++ b/spec/api.json @@ -1597,6 +1597,10 @@ "description": "ReturnTo contains the requested return_to URL.", "type": "string" }, + "transient_payload": { + "description": "TransientPayload is used to pass data from the registration to a webhook", + "type": "object" + }, "type": { "$ref": "#/components/schemas/selfServiceFlowType" }, @@ -2589,6 +2593,10 @@ "traits": { "description": "The identity traits", "type": "object" + }, + "transient_payload": { + "description": "Transient data to pass along to any webhooks", + "type": "object" } }, "required": [ @@ -2615,6 +2623,10 @@ "traits": { "description": "The identity's traits", "type": "object" + }, + "transient_payload": { + "description": "Transient data to pass along to any webhooks", + "type": "object" } }, "required": [ @@ -2639,6 +2651,10 @@ "description": "The identity's traits", "type": "object" }, + "transient_payload": { + "description": "Transient data to pass along to any webhooks", + "type": "object" + }, "webauthn_register": { "description": "Register a WebAuthn Security Key\n\nIt is expected that the JSON returned by the WebAuthn registration process\nis included here.", "type": "string" diff --git a/spec/swagger.json b/spec/swagger.json index 9717a5b9be54..ba7757488e67 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -4386,6 +4386,10 @@ "description": "ReturnTo contains the requested return_to URL.", "type": "string" }, + "transient_payload": { + "description": "TransientPayload is used to pass data from the registration to a webhook", + "type": "object" + }, "type": { "$ref": "#/definitions/selfServiceFlowType" }, @@ -5276,6 +5280,10 @@ "traits": { "description": "The identity traits", "type": "object" + }, + "transient_payload": { + "description": "Transient data to pass along to any webhooks", + "type": "object" } } }, @@ -5303,6 +5311,10 @@ "traits": { "description": "The identity's traits", "type": "object" + }, + "transient_payload": { + "description": "Transient data to pass along to any webhooks", + "type": "object" } } }, @@ -5326,6 +5338,10 @@ "description": "The identity's traits", "type": "object" }, + "transient_payload": { + "description": "Transient data to pass along to any webhooks", + "type": "object" + }, "webauthn_register": { "description": "Register a WebAuthn Security Key\n\nIt is expected that the JSON returned by the WebAuthn registration process\nis included here.", "type": "string" diff --git a/test/e2e/cypress/helpers/webhook.ts b/test/e2e/cypress/helpers/webhook.ts new file mode 100644 index 000000000000..fdc37b3fbc68 --- /dev/null +++ b/test/e2e/cypress/helpers/webhook.ts @@ -0,0 +1,67 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { fail } from "assert" +import { gen } from "." + +const WEBHOOK_TARGET = "https://webhook-target-gsmwn5ab4a-uc.a.run.app" +const documentUrl = (key: string) => `${WEBHOOK_TARGET}/documents/${key}` +const jsonnet = Buffer.from("function(ctx) ctx").toString("base64") + +export const testRegistrationWebhook = ( + configSetup: ( + hooks: Array<{ hook: string; config?: any }>, + ) => Cypress.Chainable, + act: () => Cypress.Chainable | void, +) => { + const documentID = gen.password() + configSetup([ + { + hook: "web_hook", + config: { + body: "base64://" + jsonnet, + url: documentUrl(documentID), + method: "PUT", + }, + }, + { hook: "session" }, + ]) + + const transient_payload = { + stuff: { + blub: [42, 3.14152], + fu: "bar", + }, + consent: true, + } + cy.intercept("POST", /.*\/self-service\/registration.*/, (req) => { + switch (typeof req.body) { + case "string": + req.body = + req.body + + "&transient_payload=" + + encodeURIComponent(JSON.stringify(transient_payload)) + break + case "object": + req.body = { + ...req.body, + transient_payload, + } + break + + default: + fail() + break + } + req.continue() + }) + + act() + + cy.request(documentUrl(documentID)).then(({ body, status }) => { + const b = JSON.parse(body) + expect(status).to.equal(200) + expect(b.identity).is.not.undefined + expect(b.flow.transient_payload).to.deep.equal(transient_payload) + }) +} diff --git a/test/e2e/cypress/integration/profiles/mobile/registration/success.spec.ts b/test/e2e/cypress/integration/profiles/mobile/registration/success.spec.ts index 3d491daa18fa..ddb7123a60b9 100644 --- a/test/e2e/cypress/integration/profiles/mobile/registration/success.spec.ts +++ b/test/e2e/cypress/integration/profiles/mobile/registration/success.spec.ts @@ -1,7 +1,8 @@ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { MOBILE_URL, gen, website } from "../../../../helpers" +import { gen, MOBILE_URL, website } from "../../../../helpers" +import { testRegistrationWebhook } from "../../../../helpers/webhook" context("Mobile Profile", () => { describe("Login Flow Success", () => { @@ -25,5 +26,23 @@ context("Mobile Profile", () => { cy.get('[data-testid="session-content"]').should("contain", email) cy.get('[data-testid="session-token"]').should("not.be.empty") }) + + it("should pass transient_payload to webhook", () => { + testRegistrationWebhook( + (hooks) => cy.setupHooks("registration", "after", "password", hooks), + () => { + const email = gen.email() + const password = gen.password() + + cy.get('input[data-testid="traits.email"]').type(email) + cy.get('input[data-testid="password"]').type(password) + cy.get('input[data-testid="traits.website"]').type(website) + cy.get('div[data-testid="submit-form"]').click() + + cy.get('[data-testid="session-content"]').should("contain", email) + cy.get('[data-testid="session-token"]').should("not.be.empty") + }, + ) + }) }) }) diff --git a/test/e2e/cypress/integration/profiles/oidc/registration/success.spec.ts b/test/e2e/cypress/integration/profiles/oidc/registration/success.spec.ts index 170b9dca00fd..e9435ce72ebb 100644 --- a/test/e2e/cypress/integration/profiles/oidc/registration/success.spec.ts +++ b/test/e2e/cypress/integration/profiles/oidc/registration/success.spec.ts @@ -4,6 +4,7 @@ import { appPrefix, gen, website } from "../../../../helpers" import { routes as express } from "../../../../helpers/express" import { routes as react } from "../../../../helpers/react" +import { testRegistrationWebhook } from "../../../../helpers/webhook" context("Social Sign Up Successes", () => { ;[ @@ -102,6 +103,22 @@ context("Social Sign Up Successes", () => { }) }) + it("should pass transient_payload to webhook", () => { + testRegistrationWebhook( + (hooks) => cy.setupHooks("registration", "after", "oidc", hooks), + () => { + const email = gen.email() + cy.registerOidc({ + app, + email, + website, + route: registration, + }) + cy.getSession().should(shouldSession(email)) + }, + ) + }) + it("should be able to sign up with complete data", () => { const email = gen.email() diff --git a/test/e2e/cypress/integration/profiles/passwordless/flows.spec.ts b/test/e2e/cypress/integration/profiles/passwordless/flows.spec.ts index 6833a675b78f..4b5bcfbfbdf8 100644 --- a/test/e2e/cypress/integration/profiles/passwordless/flows.spec.ts +++ b/test/e2e/cypress/integration/profiles/passwordless/flows.spec.ts @@ -1,9 +1,10 @@ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { appPrefix, gen, website } from "../../../helpers" +import { appPrefix, gen } from "../../../helpers" import { routes as express } from "../../../helpers/express" import { routes as react } from "../../../helpers/react" +import { testRegistrationWebhook } from "../../../helpers/webhook" const signup = (registration: string, app: string, email = gen.email()) => { cy.visit(registration) @@ -125,6 +126,15 @@ context("Passwordless registration", () => { }) }) + it("should pass transient_payload to webhook", () => { + testRegistrationWebhook( + (hooks) => cy.setupHooks("registration", "after", "webauthn", hooks), + () => { + signup(registration, app) + }, + ) + }) + it("should be able to login with registered account", () => { const email = gen.email() diff --git a/test/e2e/cypress/integration/profiles/verification/verify/success.spec.ts b/test/e2e/cypress/integration/profiles/verification/verify/success.spec.ts index c38b28526e73..4f460638e838 100644 --- a/test/e2e/cypress/integration/profiles/verification/verify/success.spec.ts +++ b/test/e2e/cypress/integration/profiles/verification/verify/success.spec.ts @@ -139,7 +139,7 @@ context("Account Verification Settings Success", () => { }) }) - it.only("should not notify an unknown recipient", () => { + it("should not notify an unknown recipient", () => { const recipient = gen.email() cy.visit(APP_URL + "/self-service/verification/browser") diff --git a/test/e2e/cypress/integration/profiles/webhoooks/registration/success.spec.ts b/test/e2e/cypress/integration/profiles/webhoooks/registration/success.spec.ts index 9064666dfae3..4fe48081146f 100644 --- a/test/e2e/cypress/integration/profiles/webhoooks/registration/success.spec.ts +++ b/test/e2e/cypress/integration/profiles/webhoooks/registration/success.spec.ts @@ -1,8 +1,9 @@ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { APP_URL, appPrefix, gen } from "../../../../helpers" +import { appPrefix, APP_URL, gen } from "../../../../helpers" import { routes as express } from "../../../../helpers/express" +import { testRegistrationWebhook } from "../../../../helpers/webhook" context("Registration success with email profile with webhooks", () => { ;[ @@ -46,6 +47,59 @@ context("Registration success with email profile with webhooks", () => { }) }) + it("should work without transient_payload", () => { + const WEBHOOK_TARGET = "https://webhook-target-gsmwn5ab4a-uc.a.run.app" + const documentUrl = (key: string) => + `${WEBHOOK_TARGET}/documents/${key}` + const email = gen.email() + const password = gen.password() + const documentID = gen.password() + const jsonnet = "function(ctx) ctx" + cy.setupHooks("registration", "after", "password", [ + { + hook: "web_hook", + config: { + body: `base64://${Buffer.from(jsonnet).toString("base64")}`, + url: documentUrl(documentID), + method: "PUT", + }, + }, + ]) + + cy.get('input[name="traits.email"]').type(email) + cy.get('input[name="password"]').type(password) + + cy.submitPasswordForm() + + cy.request({ + url: documentUrl(documentID), + method: "GET", + headers: { + Accept: "application/json", + }, + }).then(({ body, status }) => { + const b = JSON.parse(body) + expect(status).to.equal(200) + expect(b.identity).is.not.undefined + expect(b.flow.transient_payload).is.empty + }) + }) + + it("should pass transient_payload to webhook", () => { + testRegistrationWebhook( + cy.setPostPasswordRegistrationHooks.bind(cy), + () => { + const email = gen.email() + const password = gen.password() + + cy.get('input[name="traits.email"]').type(email) + cy.get('input[name="password"]').type(password) + + cy.submitPasswordForm() + }, + ) + }) + it("should sign up and modify the identity", () => { const email = gen.email() const password = gen.password() diff --git a/test/e2e/cypress/support/commands.ts b/test/e2e/cypress/support/commands.ts index 9f215b1cde21..2e60ef4f2ea8 100644 --- a/test/e2e/cypress/support/commands.ts +++ b/test/e2e/cypress/support/commands.ts @@ -209,13 +209,17 @@ Cypress.Commands.add("enableLoginForVerifiedAddressOnly", () => { }) }) -Cypress.Commands.add("setPostPasswordRegistrationHooks", (hooks) => { +Cypress.Commands.add("setupHooks", (flow, phase, kind, hooks) => { updateConfigFile((config) => { - config.selfservice.flows.registration["after"].password = { hooks } + config.selfservice.flows[flow][phase][kind] = { hooks } return config }) }) +Cypress.Commands.add("setPostPasswordRegistrationHooks", (hooks) => { + cy.setupHooks("registration", "after", "password", hooks) +}) + Cypress.Commands.add("shortLoginLifespan", ({} = {}) => { updateConfigFile((config) => { config.selfservice.flows.login.lifespan = "100ms" diff --git a/test/e2e/cypress/support/index.d.ts b/test/e2e/cypress/support/index.d.ts index a119ff716819..e3cd7935a1ea 100644 --- a/test/e2e/cypress/support/index.d.ts +++ b/test/e2e/cypress/support/index.d.ts @@ -149,6 +149,23 @@ declare global { returnTo?: string }): Chainable + /** + * Sets the hook. + * + * @param hooks + */ + setupHooks( + flow: + | "registration" + | "login" + | "recovery" + | "verification" + | "settings", + phase: "before" | "after", + kind: "password" | "webauthn" | "oidc", + hooks: Array<{ hook: string; config?: any }>, + ): Chainable + /** * Sets the post registration hook. * @@ -156,7 +173,7 @@ declare global { */ setPostPasswordRegistrationHooks( hooks: Array<{ hook: string; config?: any }>, - ) + ): Chainable /** * Submits a verification flow via the Browser diff --git a/test/e2e/cypress/tsconfig.json b/test/e2e/cypress/tsconfig.json index 361ae4610941..6042605a6887 100644 --- a/test/e2e/cypress/tsconfig.json +++ b/test/e2e/cypress/tsconfig.json @@ -6,7 +6,7 @@ "es2015", "dom" ], - "types": ["cypress"], + "types": ["cypress", "node"], "esModuleInterop": true, }, "include": [