Skip to content

Commit

Permalink
refactor: replace settings update_successful with state (#488)
Browse files Browse the repository at this point in the history
Closes #449

BREAKING CHANGE: Replaces the `update_successful` field of the settings request
with a field called `state` which can be either `show_form` or `success`.
  • Loading branch information
aeneasr authored Jun 8, 2020
1 parent b198faf commit ca3b3f4
Show file tree
Hide file tree
Showing 19 changed files with 101 additions and 60 deletions.
11 changes: 6 additions & 5 deletions .schema/api.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1444,7 +1444,9 @@
},
"details": {
"type": "object",
"additionalProperties": true
"additionalProperties": {
"type": "object"
}
},
"message": {
"type": "string"
Expand Down Expand Up @@ -1759,7 +1761,7 @@
"request_url",
"methods",
"identity",
"update_successful"
"state"
],
"properties": {
"active": {
Expand Down Expand Up @@ -1796,9 +1798,8 @@
"description": "RequestURL is the initial URL that was requested from ORY Kratos. It can be used\nto forward information contained in the URL's path or query for example.",
"type": "string"
},
"update_successful": {
"description": "Success, if true, indicates that the settings request has been updated successfully with the provided data.\nDone will stay true when repeatedly checking. If set to true, done will revert back to false only\nwhen a request with invalid (e.g. \"please use a valid phone number\") data was sent.",
"type": "boolean"
"state": {
"$ref": "#/definitions/State"
}
}
},
Expand Down
8 changes: 4 additions & 4 deletions docs/docs/concepts/identity-data-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ title: Identity Data Model
---

An identity ("user", "user account", "account", "subject") is the "who" of a
software system. It can be a customer, employee, user, contractor, or
even a programmatic identity such as an IoT device, application, or some
other type of "robot."
software system. It can be a customer, employee, user, contractor, or even a
programmatic identity such as an IoT device, application, or some other type of
"robot."

In ORY Kratos' terminology we call all of them "identities", and it is always
exposed as `identity` in the API endpoints, requests, and response payloads.
Expand All @@ -15,7 +15,7 @@ The following examples use YAML for improved readability. However, the API
payload is usually in JSON format. An `identity` has the following properties:

```yaml title="$ curl http://kratos/admin-endpoint/identities/9f425a8d-7efc-4768-8f23-7647a74fdf13"
# A UUID that is generated when the identity is created and
# A UUID that is generated when the identity is created and
# which cannot be changed or updated at a later stage.
id: '9f425a8d-7efc-4768-8f23-7647a74fdf13'

Expand Down
4 changes: 2 additions & 2 deletions docs/docs/concepts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ title: Overview
ORY Kratos is a new software archetype **Identity Infrastructure Service**.
Traditional identity systems - sometimes referred to as Identity and Access
Management (IAM), Identity Management (IdM), Identity Provider (IP/IdP), or
Identity as a Service (IDaaS) - have shortcomings that highlight the main differences
between ORY Kratos and other systems.
Identity as a Service (IDaaS) - have shortcomings that highlight the main
differences between ORY Kratos and other systems.

ORY Kratos solves identity on the network. It is not an on-device, for instance
mobile phones, user database. In Ory Kratos there is always an exchange of
Expand Down
36 changes: 21 additions & 15 deletions docs/docs/concepts/ui-user-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ At present, there is no Open Source AUI for ORY Kratos.
## Self-service User Interface (SSUI)

The SSUI shows screens such as "login", "Registration", "Update your profile",
"Recover access to your account", and others. The following provides more reference for
SSUI at
"Recover access to your account", and others. The following provides more
reference for SSUI at
[github.com/ory/kratos-selfservice-ui-node](https:/ory/kratos-selfservice-ui-node).

The SSUI can be built in any programming language including Java, Node, or
Expand All @@ -37,34 +37,38 @@ applications.
## Messages

ORY Kratos helps users understand what is happening by providing messages that
explain what went wrong or what needs to be done. Examples are
"The provided credentials are invalid", "Missing property email" and similar.
explain what went wrong or what needs to be done. Examples are "The provided
credentials are invalid", "Missing property email" and similar.

Typically login, registration, settings, ... flows include such messages on
different levels:

1. At the root level, indicating that the message affects the whole request (e.g. request expired)
2. At the method (password, oidc, profile) level, indicating that the message affects a specific
method / form.
3. At the field level, indicating that the message affects a form field (e.g. validation errors).
1. At the root level, indicating that the message affects the whole request
(e.g. request expired)
2. At the method (password, oidc, profile) level, indicating that the message
affects a specific method / form.
3. At the field level, indicating that the message affects a form field (e.g.
validation errors).

Each message has a layout of:

```json5
{
id: 1234, // This ID will not change and can be used to translate the message or use your own message content.
text: "Some default text", // A default text in english that you can display if you do not want to customize the message.
text: 'Some default text', // A default text in english that you can display if you do not want to customize the message.
context: {} // A JSON object which may contain additional fields such as `expires_at`. This is helpful if you want to craft your own messages.
}
```

We will list all messages, their contents, their contexts, and their IDs at a later stage. For now
please check out the code in the [text module](https:/ory/kratos/tree/master/text).
We will list all messages, their contents, their contexts, and their IDs at a
later stage. For now please check out the code in the
[text module](https:/ory/kratos/tree/master/text).

The message ID is a 7-digit number (`xyyzzzz`) where

- `x` is the message type which is either `1` for an info message (e.g. `1020000`),
`4` (e.g. `4020000`) for an input validation error message, and `5` (e.g. `5020000`) for a generic error message.
- `x` is the message type which is either `1` for an info message (e.g.
`1020000`), `4` (e.g. `4020000`) for an input validation error message, and
`5` (e.g. `5020000`) for a generic error message.
- `yy` is the module or flow this error references and can be:
- `01` for login messages (e.g. `1010000`)
- `02` for logout messages (e.g. `1020000`)
Expand All @@ -73,5 +77,7 @@ The message ID is a 7-digit number (`xyyzzzz`) where
- `05` for settings messages (e.g. `1050000`)
- `06` for account recovery messages (e.g. `1060000`)
- `07` for email/phone verification messages (e.g. `1070000`)
- `zzzz` is the message ID and typically starts at `0001`.
For example, message ID `4070001` (`4` for input validation error, `07` for verification, `0001` for the concrete message) is: `The verification code has expired or was otherwise invalid. Please request another code.`.
- `zzzz` is the message ID and typically starts at `0001`. For example, message
ID `4070001` (`4` for input validation error, `07` for verification, `0001`
for the concrete message) is:
`The verification code has expired or was otherwise invalid. Please request another code.`.
18 changes: 17 additions & 1 deletion docs/docs/self-service/flows/user-settings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,22 @@ user's browser using e.g. `window.open` `location.href` or plain and simple old

:::

#### User Settings Request State

The Settings Request contains a state with two possible values:

- `show_form`: No user data has been collected, or it is invalid, and thus the form should be shown.
- `successful`: Indicates that the settings request has been updated successfully with the provided data.
The state will stay `successful` when repeatedly checking. If the form is still displayed and the user adds more data, the state will revert back to `show_form` on validation errors.

<Mermaid chart={`stateDiagram-v2
[*] --> show_form
show_form --> show_form: Validation fails
show_form --> success: Identity was updated
success --> show_form: Validation fails
success --> [*]
`}></Mermaid>

## Self-Service User Settings for API Clients

Will be addressed in a future release.
Expand All @@ -421,4 +437,4 @@ Will be addressed in a future release.

ORY Kratos allows you to configure hooks that run before and after a profile
update was successful. For more information about hooks please read the
[Hook Documentation](../hooks/index.mdx).
[Hook Documentation](../hooks/index).
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Response Payload will include a `password` method:
email: 'h71x9a@j6r8c',
},
},
update_successful: false,
state: "show_form",
}
```

Expand Down Expand Up @@ -112,7 +112,7 @@ be included:
},
},
// identity, ...
update_successful: false,
state: "show_form",
}
```

Expand All @@ -122,7 +122,7 @@ A successful flow will be marked with:
{
id: '71da1753-e135-441c-b4df-e7b7cd90ad88',
// expires_at, active, methods, ...
update_successful: true,
state: "success",
}
```

Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -926,8 +926,6 @@ github.com/ory/mail v2.3.1+incompatible h1:vHntHDHtQXamt2T+iwTTlCoBkDvILUeujE9Oc
github.com/ory/mail v2.3.1+incompatible/go.mod h1:87D9/1gB6ewElQoN0lXJ0ayfqcj3cW3qCTXh+5E9mfU=
github.com/ory/mail/v3 v3.0.0 h1:8LFMRj473vGahFD/ntiotWEd4S80FKYFtiZTDfOQ+sM=
github.com/ory/mail/v3 v3.0.0/go.mod h1:JGAVeZF8YAlxbaFDUHqRZAKBCSeW2w1vuxf28hFbZAw=
github.com/ory/sdk/swagutil v0.0.0-20200508110558-16957df12672 h1:3KTFyxZt6USQGmeQliC2w9MjSrmxaMqjoPNOaQ82API=
github.com/ory/sdk/swagutil v0.0.0-20200508110558-16957df12672/go.mod h1:Ufg1eAyz+Zt3+oweSZVThG13ewewWCKwBmoNmK8Z0co=
github.com/ory/sdk/swagutil v0.0.0-20200528141631-666b43d19aa3 h1:kp8wBBpZzracz3ut3ThGaeSZ0iyGr1K7+PO23EdImQk=
github.com/ory/sdk/swagutil v0.0.0-20200528141631-666b43d19aa3/go.mod h1:71A/G1sB4wLBi2cvhOSC0RYb6KkiFKTaPMwoq/2gTqU=
github.com/ory/viper v1.5.6/go.mod h1:TYmpFpKLxjQwvT4f0QPpkOn4sDXU1kDgAwJpgLYiQ28=
Expand Down
2 changes: 1 addition & 1 deletion internal/httpclient/models/generic_error_payload.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions internal/httpclient/models/settings_request.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions persistence/sql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ $ soda generate sql -e postgres -c ./contrib/sql/.soda.yml -p ./contrib/sql/migr
$ soda generate sql -e cockroach -c ./contrib/sql/.soda.yml -p ./contrib/sql/migrations [name]
```

or, alternative run
or, alternative run

```
$ soda generate sql -c ./contrib/sql/.soda.yml -p ./contrib/sql/migrations
$ soda generate sql -c ./contrib/sql/.soda.yml -p ./contrib/sql/migrations
```

and remove the `sqlite` part from the newly generated file to create a SQL migrations that works with all
Expand Down
2 changes: 2 additions & 0 deletions persistence/sql/migrations/20200607165100_settings.down.fizz
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
drop_column("selfservice_settings_requests", "state")
add_column("selfservice_settings_requests", "update_successful", "bool")
2 changes: 2 additions & 0 deletions persistence/sql/migrations/20200607165100_settings.up.fizz
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
add_column("selfservice_settings_requests", "state", "string", {"default": "show_form"})
drop_column("selfservice_settings_requests", "update_successful")
6 changes: 5 additions & 1 deletion selfservice/flow/recovery/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ type Request struct {
// MethodsRaw is a helper struct field for gobuffalo.pop.
MethodsRaw RequestMethodsRaw `json:"-" faker:"-" has_many:"selfservice_recovery_request_methods" fk_id:"selfservice_recovery_request_id"`

// State represents the state of this request.
// State represents the state of this request:
//
// - choose_method: ask the user to choose a method (e.g. recover account via email)
// - sent_email: the email has been sent to the user
// - passed_challenge: the request was successful and the recovery challenge was passed.
//
// required: true
State State `json:"state" faker:"-" db:"state"`
Expand Down
3 changes: 1 addition & 2 deletions selfservice/flow/settings/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,7 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request,
}

ctxUpdate.Session.Identity = i
ctxUpdate.Request.UpdateSuccessful = true

ctxUpdate.Request.State = StateSuccess
if config.cb != nil {
if err := config.cb(ctxUpdate); err != nil {
return err
Expand Down
12 changes: 8 additions & 4 deletions selfservice/flow/settings/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,15 @@ type Request struct {
// required: true
Identity *identity.Identity `json:"identity" faker:"identity" db:"-" belongs_to:"identities" fk_id:"IdentityID"`

// Success, if true, indicates that the settings request has been updated successfully with the provided data.
// Done will stay true when repeatedly checking. If set to true, done will revert back to false only
// when a request with invalid (e.g. "please use a valid phone number") data was sent.
// State represents the state of this request. It knows two states:
//
// - show_form: No user data has been collected, or it is invalid, and thus the form should be shown.
// - success: Indicates that the settings request has been updated successfully with the provided data.
// Done will stay true when repeatedly checking. If set to true, done will revert back to false only
// when a request with invalid (e.g. "please use a valid phone number") data was sent.
//
// required: true
UpdateSuccessful bool `json:"update_successful" faker:"-" db:"update_successful"`
State State `json:"state" faker:"-" db:"state"`

// IdentityID is a helper struct field for gobuffalo.pop.
IdentityID uuid.UUID `json:"-" faker:"-" db:"identity_id"`
Expand All @@ -102,6 +105,7 @@ func NewRequest(exp time.Duration, r *http.Request, s *session.Session) *Request
RequestURL: x.RequestURL(r).String(),
IdentityID: s.Identity.ID,
Identity: s.Identity,
State: StateShowForm,
Methods: map[string]*RequestMethod{},
}
}
Expand Down
8 changes: 8 additions & 0 deletions selfservice/flow/settings/state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package settings

type State string

const (
StateShowForm State = "show_form"
StateSuccess State = "success"
)
12 changes: 6 additions & 6 deletions selfservice/flow/settings/strategy_profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func TestStrategyTraits(t *testing.T) {
values := testhelpers.SDKFormFieldsToURLValues(config.Fields)
values.Set("traits.email", newEmail)
actual, response := testhelpers.SettingsSubmitForm(t, config, primaryUser, values)
assert.True(t, pointerx.BoolR(response.Payload.UpdateSuccessful), "%s", actual)
assert.EqualValues(t, settings.StateSuccess, response.Payload.State, "%s", actual)

assert.Empty(t, gjson.Get(actual, "methods.profile.config.fields.#(name==traits.numby).errors").Value(), "%s", actual)
assert.Empty(t, gjson.Get(actual, "methods.profile.config.fields.#(name==traits.should_big_number).errors").Value(), "%s", actual)
Expand Down Expand Up @@ -219,7 +219,7 @@ func TestStrategyTraits(t *testing.T) {
values := testhelpers.SDKFormFieldsToURLValues(f.Fields)
values.Set("traits.should_big_number", "1")
actual, response := testhelpers.SettingsSubmitForm(t, f, primaryUser, values)
assert.False(t, pointerx.BoolR(response.Payload.UpdateSuccessful), "%s", actual)
assert.EqualValues(t, settings.StateShowForm, response.Payload.State, "%s", actual)

assert.Equal(t, "1", gjson.Get(actual, "methods.profile.config.fields.#(name==traits.should_big_number).value").String(), "%s", actual)
assert.Equal(t, "must be >= 1200 but found 1", gjson.Get(actual, "methods.profile.config.fields.#(name==traits.should_big_number).messages.0.text").String(), "%s", actual)
Expand All @@ -235,7 +235,7 @@ func TestStrategyTraits(t *testing.T) {
values.Set("traits.should_long_string", "short")
values.Set("traits.numby", "this-is-not-a-number")
actual, response := testhelpers.SettingsSubmitForm(t, f, primaryUser, values)
assert.False(t, pointerx.BoolR(response.Payload.UpdateSuccessful), "%s", actual)
assert.EqualValues(t, settings.StateShowForm, response.Payload.State, "%s", actual)

assert.Empty(t, gjson.Get(actual, "methods.profile.config.fields.#(name==traits.should_big_number).messages.0.text").String(), "%s", actual)
assert.Empty(t, gjson.Get(actual, "methods.profile.config.fields.#(name==traits.should_big_number).value").String(), "%s", actual)
Expand All @@ -259,7 +259,7 @@ func TestStrategyTraits(t *testing.T) {
values.Set("traits.should_big_number", "9001")
values.Set("traits.should_long_string", "this is such a long string, amazing stuff!")
actual, response := testhelpers.SettingsSubmitForm(t, f, primaryUser, values)
assert.True(t, pointerx.BoolR(response.Payload.UpdateSuccessful), "%s", actual)
assert.EqualValues(t, settings.StateSuccess, response.Payload.State, "%s", actual)

assert.Empty(t, gjson.Get(actual, "methods.profile.config.fields.#(name==traits.numby).errors").Value(), "%s", actual)
assert.Empty(t, gjson.Get(actual, "methods.profile.config.fields.#(name==traits.should_big_number).errors").Value(), "%s", actual)
Expand All @@ -278,7 +278,7 @@ func TestStrategyTraits(t *testing.T) {
values := testhelpers.SDKFormFieldsToURLValues(f.Fields)
values.Set("traits.should_long_string", "short")
actual, response := testhelpers.SettingsSubmitForm(t, f, primaryUser, values)
assert.False(t, pointerx.BoolR(response.Payload.UpdateSuccessful), "%s", actual)
assert.EqualValues(t, settings.StateShowForm, response.Payload.State, "%s", actual)
})
})
})
Expand Down Expand Up @@ -326,7 +326,7 @@ func TestStrategyTraits(t *testing.T) {
values.Set("traits.email", newEmail)

actual, response := testhelpers.SettingsSubmitForm(t, config, primaryUser, values)
assert.True(t, pointerx.BoolR(response.Payload.UpdateSuccessful), "%s", actual)
assert.EqualValues(t, settings.StateSuccess, response.Payload.State, "%s", actual)
assert.Equal(t, newEmail, gjson.Get(actual, "methods.profile.config.fields.#(name==traits.email).value").Value(), "%s", actual)

m, err := reg.CourierPersister().LatestQueuedMessage(context.Background())
Expand Down
Loading

0 comments on commit ca3b3f4

Please sign in to comment.