Closed louischan-oursky closed 8 months ago
send_account_recovery_code_flows:
# The current forgot password flow
# 1. Take a email or phone, it won't tell the user if it is correct or not
# 2. Send the code if the email or phone is valid
- name: may_recover
steps:
- type: identify
on_failure: ignore
one_of:
- identification: email
steps:
- type: send_account_recovery_code
on_failure: ignore
channel: email
- identification: phone
steps:
- type: send_account_recovery_code
on_failure: ignore
channel: sms
# Forgot password for reauth flow
# 1. Take a id token and identify the user
# 2. Take a channel (sms/email)
# 3. Must send the code
- name: must_recover
steps:
- type: identify
on_failure: error
one_of:
- identification: id_token
steps:
- type: select_account_recovery_code_channel
- type: send_account_recovery_code
account_recovery_flows:
# Reset password flow
- name: default
steps:
- type: input_recovery_code
- type: reset_password
@louischan-oursky Please see the proposed design above. You can add comments here or have a huddle if you want to have a discussion. :adore:
request_account_recovery_flows:
- name: default
steps:
- type: identify
# on_failure is moved to the branch.
one_of:
# Available identification are "email", "phone", and "id_token".
- identification: email
# on_failure is either "ignore" or "error"
# If it is "ignore", do not report error and proceed.
# If it is "error", report not found (i.e. allow account enumeration).
on_failure: ignore
- identification: phone
on_failure: ignore
- identification: id_token
on_failure: ignore
# This step outputs an array of options, where the options look like
# [
# {"masked_display_name": "+852*****325", "channel": "sms", "otp_form": "code"},
# {"masked_display_name": "+852*****325", "channel": "whatsapp", "otp_form": "code"},
# {"masked_display_name": "lou******@oursky.com", "channel": "email", "otp_form": "link"}
# ]
- type: select_destination
# No more steps because we have gathered enough information to deliver
# the account recovery code.
account_recovery_flows:
- name: default
steps:
# This step verify if the account recovery code is valid.
- type: verify_account_recovery_code
# This step is the same as type: change_password in login flow.
# That is, no old password is required.
- type: reset_password
# In addition to step: reset_password
# we may support more type to recover an account.
# For example, when 2FA becomes mandatory, and lock out an end-user.
# The customer support can issue an account recovery code.
# And the flow guides the end-user to set up TOTP.
# type: create_authenticator is the same as type: create_authenticator in signup flow.
- type: create_authenticator
one_of:
- authentication: secondary_totp
# Finishing an account recovery flow DOES NOT authenticate the end-user,
# no session is created.
@louischan-oursky
This step outputs an array of options, ...
This actually tells the user whether his input correctly identified a user, therefore on_failure: ignore
becomes useless.input_destination
and identity
. If they are separated types then we don't need on_failure
.request_account_recovery_flows:
# The current forgot password flow
# 1. Take a email or phone, it won't tell the user if it is correct or not
# 2. Send the code if the email or phone is valid
- name: default
steps:
# This step take an input from user,
# and send the code to the input no matter it is correct or not
- type: input_destination
# Forgot password for reauth flow
# 1. Take a id token and identify the user
# 2. Take a destination (sms/email)
# 3. Always send the code
- name: reauth_forgot_password
steps:
- type: identify
one_of:
- identification: id_token
# This step outputs an array of options with masked login ids
- type: select_destination
See if this updated version looks good?
Conclusion of offline discussion:
@louischan-oursky Please check the following example of supported steps in the flows.
request_account_recovery_flows:
- name: default
steps:
- type: identify
one_of:
- identification: email
# If "ignore", it still pass the step even the email is wrong.
on_failure: ignore
# If true, all login id of the user will be enumerated in next step.
enumerate_destinations: false
- identification: phone
on_failure: ignore
enumerate_destinations: false
- type: select_destination
account_recovery_flows:
- name: default
steps:
- type: verify_account_recovery_code
- type: reset_password
request_account_recovery_flows:
- name: default
steps:
- type: identify
one_of:
- identification: email
on_failure: ignore
steps:
- type: select_destination
enumerate_destinations: false
- identification: phone
on_failure: ignore
steps:
- type: select_destination
enumerate_destinations: true
account_recovery_flows:
- name: default
steps:
- type: verify_account_recovery_code
- type: reset_password
I suggest we move enumerate_destinations
to type: select_destination
(because it is a property that controls the behavior of the step). And we allow nested steps to do branching.
Account recovery examples:
// Request
{
"type": "request_account_recovery",
"name": "default",
"batch_input": []
}
// Response
{
"result": {
"state_token": "authflowstate_HAQPWJ80JFQSBPQZYFQY2HXJPVSDBYNC",
"id": "authflow_3JGC7RXSK41381NJ189SAAYZH7KMSSCQ",
"type": "account_recovery",
"name": "default",
"action": {
"type": "identify",
"data": {
"options": [
{
"identification": "email"
},
{
"identification": "phone"
}
]
}
}
}
}
// Request
{
"state_token": "authflowstate_56KJBZWYFDPFBGB62T1QQGWZNNT8JCQA",
"batch_input": [
{
"identification": "email",
"login_id": "test@example.com"
}
]
}
// Response
{
"result": {
"state_token": "authflowstate_D2Y301GPK0E6NH81MCDFAXJ72XXSG4MW",
"id": "authflow_JGR9YYY0KGVA610VK2KVYCQEHMY3TKXV",
"type": "account_recovery",
"name": "default",
"action": {
"type": "select_destination",
"data": {
"options": [
{
"id": "0",
"masked_display_name": "te**@example.com",
"channel": "email"
}
]
}
}
}
}
// Request
{
"state_token": "authflowstate_D2Y301GPK0E6NH81MCDFAXJ72XXSG4MW",
"batch_input": [
{
"option_id": "0"
}
]
}
// Response
{
"result": {
"state_token": "authflowstate_QH09EWN95VTVEW2QT893SF9FQPXB6RWH",
"id": "authflow_3JGC7RXSK41381NJ189SAAYZH7KMSSCQ",
"type": "account_recovery",
"name": "default",
"action": {
"type": "finished",
"data": {}
}
}
}
@louischan-oursky I've added an example request & response of a complete flow. Please see if any opinion? Maybe @chpapa can also leave comments if any.
request_account_recovery_flows
and merge it with account_recovery_flows
state_token
optional and accept special inputs, which can start the flow at the middle. account_recovery_flows:
- name: default
steps:
- type: identify
one_of:
- identification: email
on_failure: ignore
steps:
- type: select_destination
enumerate_destinations: false
- identification: phone
on_failure: ignore
steps:
- type: select_destination
enumerate_destinations: false
- type: verify_account_recovery_code
- type: reset_password
The two steps verify_account_recovery_code
and reset_password
were mistakenly repeated.
account_recovery_flows:
- name: default
steps:
- type: identify # Identity the user by one of the following method
one_of:
- identification: email
on_failure: ignore # ignore / error. If error, this step will return error if the login id doesn't exist.
steps:
- type: select_destination
enumerate_destinations: false # If true, this step list all possible destinations (email / phone) of the identified user.
- identification: phone
on_failure: ignore
steps:
- type: select_destination
enumerate_destinations: false
- type: verify_account_recovery_code # Input & verify a account recovery code
- type: reset_password # Input a new password and reset password
// Request POST /api/v1/authentication_flows
{
"type": "account_recovery",
"name": "default",
"batch_input": []
}
// Response
{
"result": {
"state_token": "authflowstate_00000000000000000000001",
"type": "account_recovery",
"name": "default",
"action": {
"type": "identify",
"data": {
"options": [
{
"identification": "email"
},
{
"identification": "phone"
}
]
}
}
}
}
// Request POST /api/v1/authentication_flows/states/input
{
"state_token": "authflowstate_00000000000000000000001",
"batch_input": [
{
"identification": "email",
"login_id": "test@example.com"
}
]
}
// Response
{
"result": {
"state_token": "authflowstate_00000000000000000000002",
"type": "account_recovery",
"name": "default",
"action": {
"type": "select_destination",
"data": {
"options": [
{
"masked_display_name": "te**@example.com",
"channel": "email"
}
]
}
}
}
}
// Request POST /api/v1/authentication_flows/states/input
{
"state_token": "authflowstate_00000000000000000000002",
"batch_input": [
{
"index": 0
}
]
}
// Response
{
"result": {
"state_token": "authflowstate_00000000000000000000003",
"type": "account_recovery",
"name": "default",
"action": {
"type": "verify_account_recovery_code",
"data": {
"destination": {
"id": "0",
"masked_display_name": "te**@example.com",
"channel": "email"
}
}
}
}
}
// Request POST /api/v1/authentication_flows/states/input
{
// Note that `state_token` is not needed in this request.
// This is an exception case only for account_recovery_code input
// We expect user to continue the flow in another device which might not know the current state_token, therefore we allow an input with the code only.
// The server will continue the flow according to the code.
"batch_input": [
{
"account_recovery_code": "123456ABCDEFG"
}
]
}
// Response
{
"result": {
"state_token": "authflowstate_00000000000000000000004",
"type": "account_recovery",
"name": "default",
"action": {
"type": "reset_password",
"data": {
"password_policy": {
"minimum_length": 8,
"uppercase_required": true,
"lowercase_required": true,
"alphabet_required": true,
"digit_required": true,
"symbol_required": true,
"minimum_zxcvbn_score": 3
}
}
}
}
}
// Request POST /api/v1/authentication_flows/states/input
{
"state_token": "authflowstate_00000000000000000000004",
"batch_input": [
{
"new_password": "12345678"
}
]
}
// Response
{
"result": {
"state_token": "authflowstate_00000000000000000000005",
"type": "account_recovery",
"name": "default",
"action": {
"type": "finish",
"data": {}
}
}
}
state_token
of input api is no longer a required field. It can be omitted if the first input can be used to determine the flow state. Currently, only account_recovery_code
can be used to determine the flow state. Meanwhile, we define the follow behavior:
state_token
is provided, and at the same time account_recovery_code
is used: state_token
take precedence.batch_input
is used, and multiple account_recovery_code
inputs are given: only the first account_recovery_code
will be used to determine the flow state.Originally, the lifetime of a flow state is 20 minutes. However, the lifetime of forgot password code is configurable in each project. Therefore, we decided to allow account_recovery
flow to be continued at step verify_account_recovery_code
even the lifetime of flow state is passed, respecting the lifetime of forgot password code.
@louischan-oursky @chpapa
I've updated the config and api design according to our previous discussion, please take a look and see if any comments.
@louischan-oursky
1
Lets use index.
2
Updated in the above example.
tested ok
request_account_recovery_flows
account_recovery_flows