# Browser-direct registration and password recovery

All routes here are SSO POSTs below the [browser-direct base](https://m7.org/docs/api/sso.user.m7.org/browser-direct.md),
with endpoint-bound `DPoP`, registered Origin and JSON. No existing session or
management scope is needed; transaction, key and current application policy
provide the ceremony boundary. Use fresh proofs for every retry. Signup requires
the registration gates; forgot-password recovery requires authentication enabled
and works independently of whether new signup is allowed.

## Signup

Signup owns a 30-minute transaction bound to organization, application, origin,
key and authorization context. Start with native signup or explicitly select a
provider while the draft is empty. Provider proof is not account creation.

| Relative path | Exact input | Success |
| --- | --- | --- |
| `/signup/start` | [Authorization context](https://m7.org/docs/api/sso.user.m7.org/browser-direct.md#shared-request-objects) | `transaction` plus signup view |
| `/signup/availability` | `transaction`, `field: username` or `email`, `value` (≤254) | `field`, boolean `available`; draft only |
| `/signup/authorize` | `transaction`, `provider: github`, `discord` or `m7` | `authorize_url`, `state`; navigate to returned URL |
| `/signup/submit` | `transaction`, `username`, native `password,confirm`, optional `email,displayname`, required granted `terms_consent` | Signup view, possibly verification required |
| `/signup/resend` | `transaction` | Signup view after sending a new confirmation |
| `/signup/verify` | `transaction`, `code` (six-digit string) | Signup view with `status: ready` on success |
| `/signup/status` | `transaction` | Current signup view; no account mutation |
| `/signup/complete` | `transaction` | Completed account receipt, or verification-required view after a policy change |
| `/signup/cancel` | `transaction` | Cancelled view, only before completion starts |

Signup view is `signup_id` (UUID), `status`, remaining `expires_in`, optional
`provider`. Completed adds `account_id`, `account_status`, `next_action`
(`sign_in` for active, `await_approval` otherwise), and optional `provider`.
Only start returns a new transaction handle; retain it yourself.

Usernames are 3–32 ASCII characters, begin with a letter, end with a letter or
digit, and contain letters/digits or isolated `.`, `_`, `-` (no adjacent
punctuation). Reserved names include `admin`, `root`, `system`, `null`, `m7`,
`id`, `user`, `me`, `support`. Password and confirmation must match and satisfy
[the shared password policy](https://m7.org/docs/api/sso.user.m7.org/browser-direct-passwords.md#password-validation).
Provider signup omits password/confirm. Use `terms_consent: "yes"` only after
explicit agreement. Email is a valid address up to 254 bytes; displayname is up
to 255 bytes. Required/enabled email and display-name rules come from effective
registration policy. Disabled email is discarded; disabled/empty optional
displayname falls back to the username. Verification defaults to required email
under the normal registration defaults. Phone-only or combined required phone
verification is not supported by this signup ceremony; invalid activation
configuration fails instead of silently bypassing verification.

```json
{
  "transaction":"<signup transaction>",
  "username":"sample.member",
  "password":"<new password>","confirm":"<same new password>",
  "email":"member@example.org","displayname":"Sample Member",
  "terms_consent":"yes"
}
```

```mermaid
stateDiagram-v2
  [*] --> draft
  draft --> verification_required: Submit under verification policy
  draft --> ready: Submit under immediate or approval policy
  verification_required --> ready: Verify email
  ready --> completing: Complete with current policy
  completing --> completed: Account and group enrollment confirmed
  draft --> cancelled
  verification_required --> cancelled
  ready --> cancelled
```

`signup/authorize` requires current federation allowance and a draft with no
selected provider. Its provider ticket lasts at most ten minutes and never
outlives the signup. The callback returns `signup=continue,state`, or an error;
check state and reload status. It proves a provider for this draft, never auto
registers. A provider already linked in this organization is denied: sign in
instead. To change providers, cancel and start a new signup.

Availability is a convenience check, not a reservation. Username and email
ownership are rechecked when creating the member. Submit persists the draft
before mail delivery. Repeating the same submitted details returns its view;
changing them requires a new transaction. A mail failure does not create the
account: inspect status and explicitly resend. Resend replaces the confirmation;
verification consumes a six-digit email proof. This family uses the confirmation
service's delivery/proof policy and does not expose the email-login family's
cooldown/budget contract; do not present those limits as signup guarantees.

Complete rechecks registration, activation and provider policy and enrolls
configured registration groups. It can return to verification-required if
policy changed. `completing` uses a persisted creation receipt: retry **complete
on the same transaction**, not a second signup. Completed status/complete returns
the same receipt. Successful signup grants no token; sign in afterward. Approval
mode waits for an authorized approval. Cancellation cannot undo created accounts
and returns 409 once completing/completed. Expired/wrong-bound transactions or
proofs return `invalid_grant`; state/input conflicts return `invalid_request`
(often 409); disabled gates and ineligible accounts return `access_denied` (403).

## Continue an incomplete signup from sign-in

Password login can return `outcome: signup_required` plus `transaction`, `state`,
`signup_id`, `status`, `expires_in`, and sometimes `send_code: true`.
If you still hold an unexpired native draft, include `signup_transaction` in
login; matching credentials can recover that exact draft. For an already-created
disabled account, the service proves its password and requires a genuine pending
verification marker and pending primary email. It does not reopen an arbitrary
disabled or previously verified account. Legacy hosted verification-pending
accounts can qualify; approval-pending accounts do not become self-approved.

When `send_code` is true, call signup/resend explicitly, then verify and complete
using the returned transaction. This continuation cannot change submitted
details or choose a provider. Current email ownership and account state are
rechecked. Completion is bound to its own receipt; concurrent disabling or a
different activation cannot be mistaken for this transaction's success.

## Forgot-password recovery

Recovery is for active, admitted **native tenant password** members with exactly
one verified native password identity and a usable verified email. It cannot
create a first password, revive a retired password, reset consumer-linked
credentials or change a provider's password. For those accounts use the original
provider/consumer recovery or the signed-in [first-password flow](https://m7.org/docs/api/sso.user.m7.org/browser-direct-passwords.md).

| Relative path | Exact input | Success |
| --- | --- | --- |
| `/recovery/start` | `username` (username/member UUID or email, trimmed, ≤254) | `transaction` plus recovery view |
| `/recovery/resend` | `transaction` | View with new send cooldown |
| `/recovery/verify` | `transaction`, six-digit string `code` | `status: ready` view |
| `/recovery/status` | `transaction` | Read-only current view |
| `/recovery/complete` | `transaction`, `password`, `confirm` | Completed view if password update was confirmed |
| `/recovery/cancel` | `transaction` | Cancelled view before password update starts |

Recovery view contains `status`, `expires_in`, and, during verification,
generic `message` and `resend_after`. Completed adds `next_action: sign_in`;
completing includes an uncertainty message. No account UUID, selected mailbox,
identity credential or confirmation-job ID is disclosed.

```json
{"username":"member@example.org"}
```

Unknown/ineligible accounts receive the same generic verification-required view.
An email identifier must have one verified active owner in the organization and
recovers through that exact address. Username recovery prefers a verified
primary address, with a stable verified-address fallback. Current ownership,
identity, account and group admission are rechecked before reset.

The transaction lasts 30 minutes, allows five verification attempts in total,
and enforces 30 seconds between resends (`slow_down`, 429). A resend does not
reset the attempt budget. This is not the email-login family's cross-transaction
member/IP limit. Invalid code/expired authority is `invalid_grant`; malformed
code or wrong phase is `invalid_request`; denied current policy is access denied.

States are `verification_required → ready → completing → completed`, or
`cancelled` before completing. The reset authority is consumed **before** the
remote password mutation. Completed receipt can be read/repeated safely, but a
stuck `completing` operation must not replay the password write. Inspect status,
try a fresh sign-in with the intended password, or start a new recovery. There
is no guarantee that a lost upstream response means failure. Password reset
changes native credential/certificate authority; old native token use must not
be treated as valid, and fresh sign-in is required. The response does not claim
all member/app sessions were synchronously removed. Tenant 2FA enrollment and
email preference survive; recovered password sign-in still follows current 2FA.
