proposed core.sign-up

Register a password account, the mailbox first

A registration interaction asks for an address. The realm and the client must both allow sign-up, on every round. The engine counts the registration start, mails an 8-digit code through the capped sender and asks for it, with the same step for a new address and an existing one. A right code proves the mailbox. For an address no account holds, the engine then asks for a new password, checks it against the realm's password policy and breach list, and creates the account in one database transaction; the registration completes with no amr and no auth_time. For an address an account holds, the person is helped to sign in to it instead.

What this view shows

a registration's rounds, from the first round of a registration interaction to the account created or the person sent on to sign in, round by round.

Open world: what is not drawn here is not ruled out. Only the guarantees below are binding.

Left out of this view:

The main story

When: The realm's accounts.allowSignUp is on, and the Interaction API serves it, the client is enabled and its hosted_flows name register, the transport serves identifier, email_code and new_password, the registration starts of this client and this IP are under their cap, the send caps let the code through, or drop its mail quietly at the address-and-IP tier, neither the realm's botChallenge.requiredAtSignUp nor the client's registration_challenge is on, the transport does not serve the captcha step, the provider can issue a challenge and check its answer, no account of the realm holds the address, the policy accepts it, and the realm's breach list does not hold it or the realm checks none, nobody took the address since the round read it.

  1. Ask whether the registration can be served at all: every step is served
  2. Read whether the realm and the client both allow sign-up: both allow it
  3. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  4. Go on from where the transaction is: nothing is named yet: the identifier step, alone
  5. Ask for the address: the person types an address
  6. Ask whether the registration can be served at all: every step is served
  7. Read whether the realm and the client both allow sign-up: both allow it
  8. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  9. Go on from where the transaction is: the round gives the address: it is checked, normalised and kept, and never changes
  10. Count a registration start on the limiters: the start is counted
  11. Read who holds the address, for the mail: the holder is read
  12. Issue an 8-digit code and ask the capped sender to mail it: a new address: the code is queued
  13. Ask for the code: the person types the code from the mail
  14. Ask whether the registration can be served at all: every step is served
  15. Read whether the realm and the client both allow sign-up: both allow it
  16. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  17. Go on from where the transaction is: the round answers the code with the right one
  18. Compare the right answer with the held code, in constant time: the code is right
  19. Read the address's holder again, before the right code is taken: the code is taken: consumed, email_code joins the verified steps with no amr, audited as passed
  20. Read whether an account holds the address now: no account holds it: the new password is asked for
  21. Ask for a new password: the person types a new password
  22. Ask whether the registration can be served at all: every step is served
  23. Read whether the realm and the client both allow sign-up: both allow it
  24. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  25. Go on from where the transaction is: the mailbox is proved: the address is read again before the password
  26. Read whether an account holds the address now: no account holds it, and the round brings a new password
  27. Check the new password against the realm's password policy: the policy accepts it
  28. Check the new password against the realm's breach list: the new password is not on the list, or the realm checks none
  29. Create the account in one database transaction: the account is created: new_password joins the verified steps with no amr, audited as passed

Result: Complete the registration.

The flow

a step or a port is missing: the engine answers a redirect, and the transaction ends every step is served the realm or the client does not allow sign-up: refused, audited as registration_closed both allow it the challenge is owed and no provider can answer it: refused, never waved through the challenge is owed: the captcha step alone, and any other answer this round is ignored the challenge is owed and the transport does not serve the captcha step: refused as registration_closed, never sent to a browser in its place no challenge is owed the mailbox is proved: the address is read again before the password the round answers the code with the right one the round answers the code with another one the round asks for a new code, a minute after the last; the count of wrong answers stays the round carries no answer to the code: the same step, nothing mailed the transaction holds the address but no code yet: a cap refused the first one the round gives the address: it is checked, normalised and kept, and never changes nothing is named yet: the identifier step, alone the start cap is full: refused, audited as rate_limited, nothing mailed the start is counted the holder is read a tier that answers 429 is full: nothing is mailed or kept, send_capped is audited a disabled user's address: the code is held, its mail withheld, and the user is not touched an account holds the address: the code and the account-exists notice are queued a new address: the code is queued the code is right refused with invalid_credentials, audited as invalid_code a disabled user's address: refused with invalid_credentials, counted, audited as account_disabled the code is taken: consumed, email_code joins the verified steps with no amr, audited as passed an account holds the address: the sign-in goes on with its first factor no account holds it, and the round brings a new password no account holds it: the new password is asked for the policy refuses it: the step again with password_rejected and its reasons the policy accepts it the new password is listed: refused as blocklisted, audited as password_breached the new password is not on the list, or the realm checks none fails: the check fails: counted and recorded, and the new password is accepted another account took the address meanwhile: nothing is written, audited as registration_conflict the account is created: new_password joins the verified steps with no amr, audited as passed the next round carries no answer the person types an address the next round carries no answer to the challenge, or a wrong one: the challenge again a right answer to the challenge the next round carries no answer: the same step, nothing mailed the person types the code from the mail the person types another code the person asks for a new code: an answer with no otp the next round carries no answer the person types the right code the person types another wrong code the person asks for a new code the next round carries no answer: the same step the person types a new password the next round carries no answer the person types another new password start A registration needs the identifier, email_code and new_password steps, and the engine needs its registration, email-code and password-update ports. A transport or an engine without them sends the person elsewhere. Ask whether the registration can be served at all checkingSteps · server step On every round, before anything is mailed or written (ADR-017 Decision 8): the realm's accounts.allowSignUp, served by the Interaction API, and the client's operator-set hosted_flows naming register. The client is read again, so a client disabled meanwhile allows nothing. A store error refuses the round. Read whether the realm and the client both allow sign-up readingGate · server step After the gate and before anything is mailed (#643; ADR-017 Decision 7): the realm's botChallenge.requiredAtSignUp or the client's operator-set registration_challenge, read again on every round, until this transaction passed the challenge. A store error refuses the round. Ask whether the registration owes the bot challenge (rule 5) checkingChallenge · server step The transaction says how far the registration got: a proved mailbox, a held code, an address, or nothing yet. The round's answer goes to the step it answers; an answer to another step is ignored, as every eager answer is. Go on from where the transaction is dispatching · server step Before the first code (ADR-017 Decision 7): the registration start cap, per client and per IP. A refused start mails nothing and keeps nothing; a later round tries again. Count a registration start on the limiters reservingStart · server step Before every code it mints, the first and every resend: whether an account holds the address, and whether it is enabled. It decides only the mail; the step and the answer are the same for every address. Read who holds the address, for the mail readingMailHolder · server step Eight digits from a CSPRNG and a salt of their own; the transaction keeps the salt and the keyed HMAC, never the code, and a new code keeps the count of wrong answers. The send caps of PRO-14 count the request before anything is kept. The mail says what the holder read: the code for a new address, the code and an "account exists" notice for an enabled account, and nothing at all for a disabled one, whose code is still held so that the step is the same. Issue an 8-digit code and ask the capped sender to mail it issuingCode · server step After the code's own rules (flows/core/account-verification): the limiters, then the email-code ladder, then the answer's HMAC under the code's salt against the held one, in constant time. It is right; whether it is taken depends on the holder. Compare the right answer with the held code, in constant time comparingCode · server step The answer's HMAC differs from the held one: the transaction counts one wrong answer more, and the ladder one failure more. The 5th of a transaction burns the code (flows/core/account-verification). Compare a wrong answer with the held code, in constant time comparingWrongCode · server step A right code for a disabled user's address is answered exactly as a wrong one, and counted as one: the person learns nothing, and nothing of the user changes. Any other holder, or none, lets the code be taken. A store error refuses the round; the code stays live. Read the address's holder again, before the right code is taken readingAnswerHolder · server step On every round once the mailbox is proved. An account that holds the address, created before the registration or by another one since, is not registered again: the person is helped to sign in to it, the mailbox already proved (ADR-017 Decision 8). Read whether an account holds the address now readingPasswordHolder · server step The one password policy (ADR-017 Decision 8, #634): the length as typed, NFC, the realm's minimum length, the whole-password blocklist with the address and the service and realm names as context words, and the score floor. Check the new password against the realm's password policy checkingNewPassword · server step The same check as every new password's, on the password in NFC, when the realm names a source. A listed one is refused as blocklisted. A check that fails accepts it: counted on the metric and recorded in the audit log. Check the new password against the realm's breach list checkingNewBreachList · server step Only now, the mailbox and the password both passed: the users row, the password credential with the Argon2id hash of the password in NFC, its normalisation marker and email_verified, the verified email attribute, and the user.registered event, all in one database transaction. The unique constraint on the address decides a race; a store error writes nothing, and the round is refused. Create the account in one database transaction creatingAccount · server step The identifier step alone: no first factor, no upstream provider. The request's login_hint fills it in. Ask for the address offeringAddress · server step The captcha step alone, its puzzle kept in the transaction until it is answered. A right answer passes it, and the registration goes on from where it was; a wrong one is refused with captcha_failed and a new puzzle, and counted as a failed round on the limiters. The answer is never logged or audited. Ask for the bot challenge offeringChallenge · server step The same step and the same personal data for a new address, an enabled account's address and a disabled account's, answered no earlier than the floor: nothing in the answer, its body or its time, says whether an account holds the address. Only the mail differs, and a disabled user's gets none. Ask for the code offeringCode · server step The same step with invalid_credentials; the code stays, and so does the count of wrong answers. A disabled user's right code lands here too, alike. Ask for the code again codeRefused · server step The new_password step with the purpose register, the realm's minimum length and the longest password accepted; the personal data is the transaction's address. No account exists yet. Ask for a new password offeringNewPassword · server step The same step with password_rejected and the reasons: too_short, too_long, blocklisted (a whole dictionary or context word, or a password on the realm's breach list) or too_weak. Ask for a new password again newPasswordRefused · server step The account exists, and the transaction ends with it, so nothing completes twice. A registration signs nobody in (ADR-017 Decision 10): the transport sets no session, and the code it mints carries no auth_time and no amr. Complete the registration ends here The engine goes on with the sign-in's rule 7, drawn by flows/core/sign-in: the account's first factor, offered in this same round. Its rule 8 verifies an address not verified yet, as the mailbox is already proved. Help the person sign in to the account that holds the address ends here The transaction stays as the round found it. The Interaction API answers it as an interaction that has expired. Refuse a registration the realm or the client does not allow ends here The transport cannot serve a step the registration needs, or the engine lacks a port. The transaction ends, and nothing is mailed or written. Continue elsewhere ends here Nothing is mailed or kept; the transaction stays. Refuse a registration start over its cap ends here The same 429 for every address. A code the transaction held stays live. Refuse the round for the send caps ends here Another transaction created an account with the address between the round's read and the write. Nothing was written; the FiPA endpoint answers invalid_session, and the Interaction API starts again. End the transaction for an address taken meanwhile ends here The gate, the limiters, the account directory, the send caps or the database cannot be reached: fail closed. The transaction stays as far as the round took it. Refuse the round while a store is down ends here
a step or a port is missing: the engine answers a redirect, and the transaction ends every step is served the realm or the client does not allow sign-up: refused, audited as registration_closed both allow it fails: the realm or the client cannot be read the challenge is owed and no provider can answer it: refused, never waved through the challenge is owed: the captcha step alone, and any other answer this round is ignored the challenge is owed and the transport does not serve the captcha step: refused as registration_closed, never sent to a browser in its place no challenge is owed the mailbox is proved: the address is read again before the password the round answers the code with the right one the round answers the code with another one the round asks for a new code, a minute after the last; the count of wrong answers stays the round carries no answer to the code: the same step, nothing mailed the transaction holds the address but no code yet: a cap refused the first one the round gives the address: it is checked, normalised and kept, and never changes nothing is named yet: the identifier step, alone the start cap is full: refused, audited as rate_limited, nothing mailed the start is counted fails: the limiter store fails; nothing is mailed the holder is read fails: the account directory fails; nothing is mailed a tier that answers 429 is full: nothing is mailed or kept, send_capped is audited a disabled user's address: the code is held, its mail withheld, and the user is not touched an account holds the address: the code and the account-exists notice are queued a new address: the code is queued fails: the send-cap store fails: nothing is mailed or kept the code is right refused with invalid_credentials, audited as invalid_code a disabled user's address: refused with invalid_credentials, counted, audited as account_disabled the code is taken: consumed, email_code joins the verified steps with no amr, audited as passed fails: the account directory fails; the code stays live an account holds the address: the sign-in goes on with its first factor no account holds it, and the round brings a new password no account holds it: the new password is asked for fails: the account directory fails the policy refuses it: the step again with password_rejected and its reasons the policy accepts it the new password is listed: refused as blocklisted, audited as password_breached the new password is not on the list, or the realm checks none fails: the check fails: counted and recorded, and the new password is accepted another account took the address meanwhile: nothing is written, audited as registration_conflict the account is created: new_password joins the verified steps with no amr, audited as passed fails: the database fails: nothing is written the next round carries no answer the person types an address the next round carries no answer to the challenge, or a wrong one: the challenge again a right answer to the challenge the next round carries no answer: the same step, nothing mailed the person types the code from the mail the person types another code the person asks for a new code: an answer with no otp the next round carries no answer the person types the right code the person types another wrong code the person asks for a new code the next round carries no answer: the same step the person types a new password the next round carries no answer the person types another new password start A registration needs the identifier, email_code and new_password steps, and the engine needs its registration, email-code and password-update ports. A transport or an engine without them sends the person elsewhere. Ask whether the registration can be served at all checkingSteps · server step On every round, before anything is mailed or written (ADR-017 Decision 8): the realm's accounts.allowSignUp, served by the Interaction API, and the client's operator-set hosted_flows naming register. The client is read again, so a client disabled meanwhile allows nothing. A store error refuses the round. Read whether the realm and the client both allow sign-up readingGate · server step After the gate and before anything is mailed (#643; ADR-017 Decision 7): the realm's botChallenge.requiredAtSignUp or the client's operator-set registration_challenge, read again on every round, until this transaction passed the challenge. A store error refuses the round. Ask whether the registration owes the bot challenge (rule 5) checkingChallenge · server step The transaction says how far the registration got: a proved mailbox, a held code, an address, or nothing yet. The round's answer goes to the step it answers; an answer to another step is ignored, as every eager answer is. Go on from where the transaction is dispatching · server step Before the first code (ADR-017 Decision 7): the registration start cap, per client and per IP. A refused start mails nothing and keeps nothing; a later round tries again. Count a registration start on the limiters reservingStart · server step Before every code it mints, the first and every resend: whether an account holds the address, and whether it is enabled. It decides only the mail; the step and the answer are the same for every address. Read who holds the address, for the mail readingMailHolder · server step Eight digits from a CSPRNG and a salt of their own; the transaction keeps the salt and the keyed HMAC, never the code, and a new code keeps the count of wrong answers. The send caps of PRO-14 count the request before anything is kept. The mail says what the holder read: the code for a new address, the code and an "account exists" notice for an enabled account, and nothing at all for a disabled one, whose code is still held so that the step is the same. Issue an 8-digit code and ask the capped sender to mail it issuingCode · server step After the code's own rules (flows/core/account-verification): the limiters, then the email-code ladder, then the answer's HMAC under the code's salt against the held one, in constant time. It is right; whether it is taken depends on the holder. Compare the right answer with the held code, in constant time comparingCode · server step The answer's HMAC differs from the held one: the transaction counts one wrong answer more, and the ladder one failure more. The 5th of a transaction burns the code (flows/core/account-verification). Compare a wrong answer with the held code, in constant time comparingWrongCode · server step A right code for a disabled user's address is answered exactly as a wrong one, and counted as one: the person learns nothing, and nothing of the user changes. Any other holder, or none, lets the code be taken. A store error refuses the round; the code stays live. Read the address's holder again, before the right code is taken readingAnswerHolder · server step On every round once the mailbox is proved. An account that holds the address, created before the registration or by another one since, is not registered again: the person is helped to sign in to it, the mailbox already proved (ADR-017 Decision 8). Read whether an account holds the address now readingPasswordHolder · server step The one password policy (ADR-017 Decision 8, #634): the length as typed, NFC, the realm's minimum length, the whole-password blocklist with the address and the service and realm names as context words, and the score floor. Check the new password against the realm's password policy checkingNewPassword · server step The same check as every new password's, on the password in NFC, when the realm names a source. A listed one is refused as blocklisted. A check that fails accepts it: counted on the metric and recorded in the audit log. Check the new password against the realm's breach list checkingNewBreachList · server step Only now, the mailbox and the password both passed: the users row, the password credential with the Argon2id hash of the password in NFC, its normalisation marker and email_verified, the verified email attribute, and the user.registered event, all in one database transaction. The unique constraint on the address decides a race; a store error writes nothing, and the round is refused. Create the account in one database transaction creatingAccount · server step The identifier step alone: no first factor, no upstream provider. The request's login_hint fills it in. Ask for the address offeringAddress · server step The captcha step alone, its puzzle kept in the transaction until it is answered. A right answer passes it, and the registration goes on from where it was; a wrong one is refused with captcha_failed and a new puzzle, and counted as a failed round on the limiters. The answer is never logged or audited. Ask for the bot challenge offeringChallenge · server step The same step and the same personal data for a new address, an enabled account's address and a disabled account's, answered no earlier than the floor: nothing in the answer, its body or its time, says whether an account holds the address. Only the mail differs, and a disabled user's gets none. Ask for the code offeringCode · server step The same step with invalid_credentials; the code stays, and so does the count of wrong answers. A disabled user's right code lands here too, alike. Ask for the code again codeRefused · server step The new_password step with the purpose register, the realm's minimum length and the longest password accepted; the personal data is the transaction's address. No account exists yet. Ask for a new password offeringNewPassword · server step The same step with password_rejected and the reasons: too_short, too_long, blocklisted (a whole dictionary or context word, or a password on the realm's breach list) or too_weak. Ask for a new password again newPasswordRefused · server step The account exists, and the transaction ends with it, so nothing completes twice. A registration signs nobody in (ADR-017 Decision 10): the transport sets no session, and the code it mints carries no auth_time and no amr. Complete the registration ends here The engine goes on with the sign-in's rule 7, drawn by flows/core/sign-in: the account's first factor, offered in this same round. Its rule 8 verifies an address not verified yet, as the mailbox is already proved. Help the person sign in to the account that holds the address ends here The transaction stays as the round found it. The Interaction API answers it as an interaction that has expired. Refuse a registration the realm or the client does not allow ends here The transport cannot serve a step the registration needs, or the engine lacks a port. The transaction ends, and nothing is mailed or written. Continue elsewhere ends here Nothing is mailed or kept; the transaction stays. Refuse a registration start over its cap ends here The same 429 for every address. A code the transaction held stays live. Refuse the round for the send caps ends here Another transaction created an account with the address between the round's read and the write. Nothing was written; the FiPA endpoint answers invalid_session, and the Interaction API starts again. End the transaction for an address taken meanwhile ends here The gate, the limiters, the account directory, the send caps or the database cannot be reached: fail closed. The transaction stays as far as the round took it. Refuse the round while a store is down ends here

blue line: the main storyred dashed line: a failure path (shown with “Show failure paths”)dotted line: decided by a rule, nobody actsdashed grey box: the server decides, nothing is shown

What each step answers

StepThe outside world sees
Ask for the address
offeringAddress
kind: step · body: {"current":{"step":"identifier","params":{}},"alternatives":[],"personal":{"identifier":"the request's login_hint, if it has one"}}
Ask for the bot challenge
offeringChallenge
kind: step · body: {"current":{"step":"captcha","params":"the provider's"},"alternatives":[],"personal":{}}
Ask for the code
offeringCode
kind: step · body: {"current":{"step":"email_code","params":{"purpose":"verify_account","length":8,"expires_in":"the seconds the code has left, at most 600","resend_after":"the seconds until another code may be asked for, at most 60"}},"alternatives":[],"personal":{"identifier":"the transaction's address"}}
Ask for the code again
codeRefused
kind: step · body: {"current":{"step":"email_code","params":{"purpose":"verify_account","length":8,"expires_in":"the seconds the code has left, at most 600","resend_after":"the seconds until another code may be asked for, at most 60"},"error":"invalid_credentials"},"alternatives":[],"personal":{"identifier":"the transaction's address"}}
Ask for a new password
offeringNewPassword
kind: step · body: {"current":{"step":"new_password","params":{"purpose":"register","min_length":"the realm's password.minLength","max_length":256}},"alternatives":[],"personal":{"identifier":"the transaction's address"}}
Ask for a new password again
newPasswordRefused
kind: step · body: {"current":{"step":"new_password","params":{"purpose":"register","min_length":"the realm's password.minLength","max_length":256,"reasons":"why the new password was refused"},"error":"password_rejected"},"alternatives":[],"personal":{"identifier":"the transaction's address"}}
Complete the registration
registered
kind: complete · body: {"transaction":null,"subject":"the new account's user id","identifier":"the transaction's address, normalised","registered":true}
Help the person sign in to the account that holds the address
signInHelped
kind: continue · body: {"verified":"the transaction verified email_code with purpose verify_account; it adds no amr value"}
Refuse a registration the realm or the client does not allow
registrationClosed
kind: refused · body: {"reason":"request_refused","code":"registration_not_allowed"}
Continue elsewhere
redirected
kind: redirect · body: {"reason":"step_not_available","transaction":null}
Refuse a registration start over its cap
startThrottled
kind: refused · body: {"reason":"throttled","retryAfterSeconds":"the start cap's"}
Refuse the round for the send caps
sendCapped
kind: refused · body: {"reason":"throttled","retryAfterSeconds":"the send cap's"}
End the transaction for an address taken meanwhile
registrationConflict
kind: refused · body: {"reason":"transaction_ended","transaction":null}
Refuse the round while a store is down
unavailable
kind: refused · body: {"reason":"unavailable"}

How it decides what happens next

Ask whether the registration can be served at all

#IfThen
1a step or a port is missing: the engine answers a redirect, and the transaction endsContinue elsewhere
2every step is servedRead whether the realm and the client both allow sign-up

The first matching rule wins.

Exact conditions
#ConditionGoes toSituations
1notServedredirected4608 of 30720
2otherwisereadingGate26112 of 30720
notServed
not stepsServed

Ask whether the registration owes the bot challenge (rule 5)

#IfThen
1the challenge is owed and no provider can answer it: refused, never waved throughRefuse the round while a store is down
2the challenge is owed: the captcha step alone, and any other answer this round is ignoredAsk for the bot challenge
3the challenge is owed and the transport does not serve the captcha step: refused as registration_closed, never sent to a browser in its placeRefuse a registration the realm or the client does not allow
4no challenge is owedGo on from where the transaction is

The first matching rule wins.

Exact conditions
#ConditionGoes toSituations
1challengeUnanswerableunavailable96 of 6528
2challengeOfferedofferingChallenge96 of 6528
3challengeOwedregistrationClosed192 of 6528
4otherwisedispatching6144 of 6528
challengeOwed
challenge == 'owed'
challengeOffered
challenge == 'owed' and captchaServed and challengeProvider == 'answers'
challengeUnanswerable
challenge == 'owed' and captchaServed and challengeProvider == 'down'

Go on from where the transaction is

#IfThen
1the mailbox is proved: the address is read again before the passwordRead whether an account holds the address now
2the round answers the code with the right oneCompare the right answer with the held code, in constant time
3the round answers the code with another oneCompare a wrong answer with the held code, in constant time
4the round asks for a new code, a minute after the last; the count of wrong answers staysRead who holds the address, for the mail
5the round carries no answer to the code: the same step, nothing mailedAsk for the code
6the transaction holds the address but no code yet: a cap refused the first oneCount a registration start on the limiters
7the round gives the address: it is checked, normalised and kept, and never changesCount a registration start on the limiters
8nothing is named yet: the identifier step, aloneAsk for the address

The first matching rule wins.

Exact conditions
#ConditionGoes toSituations
1mailboxProvedreadingPasswordHolder1536 of 6144
2codeHeld and answer == 'rightCode'comparingCode768 of 6144
3codeHeld and answer == 'wrongCode'comparingWrongCode768 of 6144
4codeHeld and answer == 'resend'readingMailHolder768 of 6144
5codeHeldofferingCode768 of 6144
6addressTakenreservingStart0 of 6144
7answer == 'address'reservingStart768 of 6144
8otherwiseofferingAddress768 of 6144

Compare the right answer with the held code, in constant time

#IfThen
1the code is rightRead the address's holder again, before the right code is taken

The first matching rule wins.

Exact conditions
#ConditionGoes toSituations
1otherwisereadingAnswerHolder768 of 768

Compare a wrong answer with the held code, in constant time

#IfThen
1refused with invalid_credentials, audited as invalid_codeAsk for the code again

The first matching rule wins.

Exact conditions
#ConditionGoes toSituations
1otherwisecodeRefused768 of 768

Check the new password against the realm's password policy

#IfThen
1the policy refuses it: the step again with password_rejected and its reasonsAsk for a new password again
2the policy accepts itCheck the new password against the realm's breach list

The first matching rule wins.

Exact conditions
#ConditionGoes toSituations
1newPasswordRejectednewPasswordRefused64 of 256
2otherwisecheckingNewBreachList192 of 256
newPasswordRejected
newPassword == 'rejected'

Every way it can end

It ends withWhenWhat happens
Complete the registration
ends here
The realm's accounts.allowSignUp is on, and the Interaction API serves it, the client is enabled and its hosted_flows name register, the transport serves identifier, email_code and new_password, the registration starts of this client and this IP are under their cap, the send caps let the code through, or drop its mail quietly at the address-and-IP tier, neither the realm's botChallenge.requiredAtSignUp nor the client's registration_challenge is on, the transport does not serve the captcha step, the provider can issue a challenge and check its answer, no account of the realm holds the address, the policy accepts it, and the realm's breach list does not hold it or the realm checks none, nobody took the address since the round read it
  1. Ask whether the registration can be served at all: every step is served
  2. Read whether the realm and the client both allow sign-up: both allow it
  3. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  4. Go on from where the transaction is: nothing is named yet: the identifier step, alone
  5. Ask for the address: the person types an address
  6. Ask whether the registration can be served at all: every step is served
  7. Read whether the realm and the client both allow sign-up: both allow it
  8. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  9. Go on from where the transaction is: the round gives the address: it is checked, normalised and kept, and never changes
  10. Count a registration start on the limiters: the start is counted
  11. Read who holds the address, for the mail: the holder is read
  12. Issue an 8-digit code and ask the capped sender to mail it: a new address: the code is queued
  13. Ask for the code: the person types the code from the mail
  14. Ask whether the registration can be served at all: every step is served
  15. Read whether the realm and the client both allow sign-up: both allow it
  16. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  17. Go on from where the transaction is: the round answers the code with the right one
  18. Compare the right answer with the held code, in constant time: the code is right
  19. Read the address's holder again, before the right code is taken: the code is taken: consumed, email_code joins the verified steps with no amr, audited as passed
  20. Read whether an account holds the address now: no account holds it: the new password is asked for
  21. Ask for a new password: the person types a new password
  22. Ask whether the registration can be served at all: every step is served
  23. Read whether the realm and the client both allow sign-up: both allow it
  24. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  25. Go on from where the transaction is: the mailbox is proved: the address is read again before the password
  26. Read whether an account holds the address now: no account holds it, and the round brings a new password
  27. Check the new password against the realm's password policy: the policy accepts it
  28. Check the new password against the realm's breach list: the new password is not on the list, or the realm checks none
  29. Create the account in one database transaction: the account is created: new_password joins the verified steps with no amr, audited as passed
Help the person sign in to the account that holds the address
ends here
The realm's accounts.allowSignUp is on, and the Interaction API serves it, the client is enabled and its hosted_flows name register, the transport serves identifier, email_code and new_password, the registration starts of this client and this IP are under their cap, the send caps let the code through, or drop its mail quietly at the address-and-IP tier, neither the realm's botChallenge.requiredAtSignUp nor the client's registration_challenge is on, the transport does not serve the captcha step, the provider can issue a challenge and check its answer, no account of the realm holds the address, the policy accepts it, and the realm's breach list does not hold it or the realm checks none, nobody took the address since the round read it
  1. Ask whether the registration can be served at all: every step is served
  2. Read whether the realm and the client both allow sign-up: both allow it
  3. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  4. Go on from where the transaction is: nothing is named yet: the identifier step, alone
  5. Ask for the address: the person types an address
  6. Ask whether the registration can be served at all: every step is served
  7. Read whether the realm and the client both allow sign-up: both allow it
  8. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  9. Go on from where the transaction is: the round gives the address: it is checked, normalised and kept, and never changes
  10. Count a registration start on the limiters: the start is counted
  11. Read who holds the address, for the mail: the holder is read
  12. Issue an 8-digit code and ask the capped sender to mail it: a new address: the code is queued
  13. Ask for the code: the person types the code from the mail
  14. Ask whether the registration can be served at all: every step is served
  15. Read whether the realm and the client both allow sign-up: both allow it
  16. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  17. Go on from where the transaction is: the round answers the code with the right one
  18. Compare the right answer with the held code, in constant time: the code is right
  19. Read the address's holder again, before the right code is taken: the code is taken: consumed, email_code joins the verified steps with no amr, audited as passed
  20. Read whether an account holds the address now: an account holds the address: the sign-in goes on with its first factor
Refuse a registration the realm or the client does not allow
ends here
The realm does not allow sign-up, or the deployment runs no Interaction API, the client is gone or disabled, or its operator-set hosted_flows do not name register, the transport serves identifier, email_code and new_password, the registration starts of this client and this IP are under their cap, the send caps let the code through, or drop its mail quietly at the address-and-IP tier, neither the realm's botChallenge.requiredAtSignUp nor the client's registration_challenge is on, the transport does not serve the captcha step, the provider can issue a challenge and check its answer, no account of the realm holds the address, the policy accepts it, and the realm's breach list does not hold it or the realm checks none, nobody took the address since the round read it
  1. Ask whether the registration can be served at all: every step is served
  2. Read whether the realm and the client both allow sign-up: the realm or the client does not allow sign-up: refused, audited as registration_closed
Continue elsewhere
ends here
The realm does not allow sign-up, or the deployment runs no Interaction API, the client is gone or disabled, or its operator-set hosted_flows do not name register, the transport cannot serve identifier, email_code or new_password, or the engine lacks the ports, the registration starts of this client and this IP are under their cap, the send caps let the code through, or drop its mail quietly at the address-and-IP tier, neither the realm's botChallenge.requiredAtSignUp nor the client's registration_challenge is on, the transport does not serve the captcha step, the provider can issue a challenge and check its answer, no account of the realm holds the address, the policy accepts it, and the realm's breach list does not hold it or the realm checks none, nobody took the address since the round read it
  1. Ask whether the registration can be served at all: a step or a port is missing: the engine answers a redirect, and the transaction ends
Refuse a registration start over its cap
ends here
The realm's accounts.allowSignUp is on, and the Interaction API serves it, the client is enabled and its hosted_flows name register, the transport serves identifier, email_code and new_password, the registration starts of this client and this IP are under their cap, the send caps let the code through, or drop its mail quietly at the address-and-IP tier, neither the realm's botChallenge.requiredAtSignUp nor the client's registration_challenge is on, the transport does not serve the captcha step, the provider can issue a challenge and check its answer, no account of the realm holds the address, the policy accepts it, and the realm's breach list does not hold it or the realm checks none, nobody took the address since the round read it
  1. Ask whether the registration can be served at all: every step is served
  2. Read whether the realm and the client both allow sign-up: both allow it
  3. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  4. Go on from where the transaction is: nothing is named yet: the identifier step, alone
  5. Ask for the address: the person types an address
  6. Ask whether the registration can be served at all: every step is served
  7. Read whether the realm and the client both allow sign-up: both allow it
  8. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  9. Go on from where the transaction is: the round gives the address: it is checked, normalised and kept, and never changes
  10. Count a registration start on the limiters: the start cap is full: refused, audited as rate_limited, nothing mailed
Refuse the round for the send caps
ends here
The realm's accounts.allowSignUp is on, and the Interaction API serves it, the client is enabled and its hosted_flows name register, the transport serves identifier, email_code and new_password, the registration starts of this client and this IP are under their cap, the send caps let the code through, or drop its mail quietly at the address-and-IP tier, neither the realm's botChallenge.requiredAtSignUp nor the client's registration_challenge is on, the transport does not serve the captcha step, the provider can issue a challenge and check its answer, no account of the realm holds the address, the policy accepts it, and the realm's breach list does not hold it or the realm checks none, nobody took the address since the round read it
  1. Ask whether the registration can be served at all: every step is served
  2. Read whether the realm and the client both allow sign-up: both allow it
  3. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  4. Go on from where the transaction is: nothing is named yet: the identifier step, alone
  5. Ask for the address: the person types an address
  6. Ask whether the registration can be served at all: every step is served
  7. Read whether the realm and the client both allow sign-up: both allow it
  8. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  9. Go on from where the transaction is: the round gives the address: it is checked, normalised and kept, and never changes
  10. Count a registration start on the limiters: the start is counted
  11. Read who holds the address, for the mail: the holder is read
  12. Issue an 8-digit code and ask the capped sender to mail it: a tier that answers 429 is full: nothing is mailed or kept, send_capped is audited
End the transaction for an address taken meanwhile
ends here
The realm's accounts.allowSignUp is on, and the Interaction API serves it, the client is enabled and its hosted_flows name register, the transport serves identifier, email_code and new_password, the registration starts of this client and this IP are under their cap, the send caps let the code through, or drop its mail quietly at the address-and-IP tier, neither the realm's botChallenge.requiredAtSignUp nor the client's registration_challenge is on, the transport does not serve the captcha step, the provider can issue a challenge and check its answer, no account of the realm holds the address, the policy accepts it, and the realm's breach list does not hold it or the realm checks none, nobody took the address since the round read it
  1. Ask whether the registration can be served at all: every step is served
  2. Read whether the realm and the client both allow sign-up: both allow it
  3. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  4. Go on from where the transaction is: nothing is named yet: the identifier step, alone
  5. Ask for the address: the person types an address
  6. Ask whether the registration can be served at all: every step is served
  7. Read whether the realm and the client both allow sign-up: both allow it
  8. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  9. Go on from where the transaction is: the round gives the address: it is checked, normalised and kept, and never changes
  10. Count a registration start on the limiters: the start is counted
  11. Read who holds the address, for the mail: the holder is read
  12. Issue an 8-digit code and ask the capped sender to mail it: a new address: the code is queued
  13. Ask for the code: the person types the code from the mail
  14. Ask whether the registration can be served at all: every step is served
  15. Read whether the realm and the client both allow sign-up: both allow it
  16. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  17. Go on from where the transaction is: the round answers the code with the right one
  18. Compare the right answer with the held code, in constant time: the code is right
  19. Read the address's holder again, before the right code is taken: the code is taken: consumed, email_code joins the verified steps with no amr, audited as passed
  20. Read whether an account holds the address now: no account holds it: the new password is asked for
  21. Ask for a new password: the person types a new password
  22. Ask whether the registration can be served at all: every step is served
  23. Read whether the realm and the client both allow sign-up: both allow it
  24. Ask whether the registration owes the bot challenge (rule 5): no challenge is owed
  25. Go on from where the transaction is: the mailbox is proved: the address is read again before the password
  26. Read whether an account holds the address now: no account holds it, and the round brings a new password
  27. Check the new password against the realm's password policy: the policy accepts it
  28. Check the new password against the realm's breach list: the new password is not on the list, or the realm checks none
  29. Create the account in one database transaction: another account took the address meanwhile: nothing is written, audited as registration_conflict
Refuse the round while a store is down
ends here
The realm does not allow sign-up, or the deployment runs no Interaction API, the client is gone or disabled, or its operator-set hosted_flows do not name register, the transport serves identifier, email_code and new_password, the registration starts of this client and this IP are under their cap, the send caps let the code through, or drop its mail quietly at the address-and-IP tier, neither the realm's botChallenge.requiredAtSignUp nor the client's registration_challenge is on, the transport does not serve the captcha step, the provider can issue a challenge and check its answer, no account of the realm holds the address, the policy accepts it, and the realm's breach list does not hold it or the realm checks none, nobody took the address since the round read it
  1. Ask whether the registration can be served at all: every step is served
  2. Read whether the realm and the client both allow sign-up: it fails, because the realm or the client cannot be read

Each row is also a generated test: set the starting situation, send the events, expect the ending.

What is guaranteed

These hold in all 136800 situations the model can reach, from 9216 different starting situations.

Exact rules
IdNever
nothing-before-the-gatestate in ['offeringAddress', 'offeringCode', 'codeRefused', 'offeringNewPassword', 'newPasswordRefused', 'registered', 'signInHelped', 'startThrottled', 'sendCapped', 'registrationConflict'] and (not realmAllows or not clientAllows)
nothing-mailed-while-the-challenge-is-owed(mailQueued or accountCreated) and challenge == 'owed'
no-account-before-the-codeaccountCreated and not mailboxProved
no-account-before-the-passwordaccountCreated and not passwordAccepted
an-account-only-for-a-free-addressstate == 'registered' and holder != 'nobody'
no-factor-at-registrationfactorClaimed
a-disabled-users-address-gets-no-mailmailQueued and holder == 'disabled'
a-disabled-users-code-is-never-takenmailboxProved and answer == 'rightCode' and holder == 'disabled'
the-mail-says-who-holds-the-address(mailKind == 'newAddress' and holder != 'nobody') or (mailKind == 'accountExists' and holder != 'enabled')
the-start-is-counted-firstcodeHeld and not startCounted
nothing-mailed-when-cappedstate in ['sendCapped', 'startThrottled'] and mailQueued
compare-only-a-held-codecodeCompared and not startCounted
every-refusal-is-auditedstate in ['registrationClosed', 'startThrottled', 'sendCapped', 'codeRefused', 'newPasswordRefused', 'registrationConflict', 'registered'] and not audited
every-round-waits-the-floorstate in ['offeringAddress', 'offeringChallenge', 'offeringCode', 'codeRefused', 'offeringNewPassword', 'newPasswordRefused', 'registered', 'registrationClosed', 'redirected', 'startThrottled', 'sendCapped', 'registrationConflict', 'unavailable'] and not floorWaited

Reference

All transitions (60)
FromEventGuardToEffectsTaken
checkingStepsrule 1notServedredirectedwaitFlooryes
checkingStepsrule 2readingGateyes
readingGateDONEclosedregistrationClosedaudit waitFlooryes
readingGateDONEcheckingChallengeyes
readingGateFAIL.storeunavailablewaitFlooryes
checkingChallengerule 1challengeUnanswerableunavailablewaitFlooryes
checkingChallengerule 2challengeOfferedofferingChallengewaitFlooryes
checkingChallengerule 3challengeOwedregistrationClosedaudit waitFlooryes
checkingChallengerule 4dispatchingyes
dispatchingrule 1mailboxProvedreadingPasswordHolderyes
dispatchingrule 2codeHeld and answer == 'rightCode'comparingCodeyes
dispatchingrule 3codeHeld and answer == 'wrongCode'comparingWrongCodeyes
dispatchingrule 4codeHeld and answer == 'resend'readingMailHolderyes
dispatchingrule 5codeHeldofferingCodewaitFlooryes
dispatchingrule 6addressTakenreservingStartnever
dispatchingrule 7answer == 'address'reservingStarttakeAddressyes
dispatchingrule 8offeringAddresswaitFlooryes
reservingStartDONEstartFullstartThrottledaudit waitFlooryes
reservingStartDONEreadingMailHoldercountStartyes
reservingStartFAIL.storeunavailablewaitFlooryes
readingMailHolderDONEissuingCodeyes
readingMailHolderFAIL.storeunavailablewaitFlooryes
issuingCodeDONEcappedsendCappedaudit waitFlooryes
issuingCodeDONEheldByDisabledofferingCodeholdCode waitFlooryes
issuingCodeDONEheldByEnabledofferingCodeholdCode queueAccountExistsMail waitFlooryes
issuingCodeDONEheldByNobodyofferingCodeholdCode queueNewAddressMail waitFlooryes
issuingCodeFAIL.storeunavailablewaitFlooryes
comparingCoderule 1readingAnswerHoldercompareCodeyes
comparingWrongCoderule 1codeRefusedcompareCode audit waitFlooryes
readingAnswerHolderDONEheldByDisabledcodeRefusedaudit waitFlooryes
readingAnswerHolderDONEreadingPasswordHolderproveMailbox audityes
readingAnswerHolderFAIL.storeunavailablewaitFlooryes
readingPasswordHolderDONEaddressHeldsignInHelpedyes
readingPasswordHolderDONEanswer == 'newPassword'checkingNewPasswordyes
readingPasswordHolderDONEofferingNewPasswordwaitFlooryes
readingPasswordHolderFAIL.storeunavailablewaitFlooryes
checkingNewPasswordrule 1newPasswordRejectednewPasswordRefusedaudit waitFlooryes
checkingNewPasswordrule 2checkingNewBreachListyes
checkingNewBreachListDONEnewPasswordListednewPasswordRefusedaudit waitFlooryes
checkingNewBreachListDONEnewPassword == 'accepted'creatingAccountacceptPasswordyes
checkingNewBreachListFAIL.lookupnewPassword == 'checkFailed'creatingAccountacceptPassword recordBreachFailureyes
creatingAccountDONEracedregistrationConflictaudit waitFlooryes
creatingAccountDONEregisteredcreateAccount audit waitFlooryes
creatingAccountFAIL.storeunavailablewaitFlooryes
offeringAddressROUNDcheckingStepsnewRound moveOnyes
offeringAddressADDRESScheckingStepsnewRound moveOn answerAddressyes
offeringChallengeROUNDcheckingStepsnewRound moveOnyes
offeringChallengeCHALLENGE_PASSEDcheckingStepsnewRound moveOn passChallengeyes
offeringCodeROUNDcheckingStepsnewRound moveOnyes
offeringCodeRIGHT_CODEcheckingStepsnewRound moveOn answerRightyes
offeringCodeWRONG_CODEcheckingStepsnewRound moveOn answerWrongyes
offeringCodeRESENDcheckingStepsnewRound moveOn answerResendyes
codeRefusedROUNDcheckingStepsnewRound moveOnyes
codeRefusedRIGHT_CODEcheckingStepsnewRound moveOn answerRightyes
codeRefusedWRONG_CODEcheckingStepsnewRound moveOn answerWrongyes
codeRefusedRESENDcheckingStepsnewRound moveOn answerResendyes
offeringNewPasswordROUNDcheckingStepsnewRound moveOnyes
offeringNewPasswordNEW_PASSWORDcheckingStepsnewRound moveOn answerNewPasswordyes
newPasswordRefusedROUNDcheckingStepsnewRound moveOnyes
newPasswordRefusedNEW_PASSWORDcheckingStepsnewRound moveOn answerNewPasswordyes