Files
web/authmail/README.md
T
gamertan 494b72fa3b
verify / verify (push) Successful in 4m29s
Release v0.1.0-preview.28: transactional account email
Reviewed source export adds verified TLS mail, encrypted outbox, mailbox verification and password reset protocols. Preserve public ancestry; omit local development history and operational queue. Consumer deployment and inbox delivery proof remain separate.
2026-09-11 04:12:17 -04:00

84 lines
5.4 KiB
Markdown

<!-- SPDX-License-Identifier: MPL-2.0 -->
# Account mail protocols
`authmail` provides optional verification, confirmed address changes and password
reset. It does not deliver mail directly or own the application router. Use
`authsqlite.Store.AccountMail` for an adapter sharing the identity database and
encrypted transactional outbox. Call `MigrateMail` explicitly and require its
independent schema version before enabling these routes. Base identity schema 11
and historical commerce records are unchanged; existing mailboxes start unverified.
## Operations
- Verification requires a current unrestricted account session and confirmation
at its existing canonical mailbox. Reading/inspecting a link does not verify it.
- Address change requires the current password and that session, then separate
confirmation at both current and proposed mailboxes. The current address stays
authoritative until both confirm. No passkey-approval boolean bypass exists.
A future passkey-only path needs a separately bound fresh-approval protocol.
- Password reset is available only through the current verified mailbox of an
active, fully registered account. Request results are generic for unknown,
unverified, inactive, malformed and account-throttled addresses. A reset creates
no login, removes no passkey/recovery code and bypasses no existing MFA policy.
Tokens use 32 random bytes and purpose-bound SHA-256 digests, expire in 15 minutes,
and work once. Requests bind the user ID, canonical address, profile revision and
current password digest; address/verification requests also bind the real acting
session. A replacement request invalidates the previous link for that purpose.
Already-in-flight older mail may still arrive; an invalidated token cannot act.
The SQLite adapter rechecks authority and identity under its writer lock, including
fresh time after waits/password hashing. A changed password, profile, address,
status or acting session rejects stale requests. Address uniqueness is checked
again at final confirmation. Old-address invitations are revoked, not moved;
new-address invitations still need normal token/authority checks to be accepted.
Existing memberships, ownership and purchase snapshots retain the immutable user.
Successful reset/address changes revoke sessions, pending ceremonies, enrollment
and recovery grants, and outstanding account-mail requests. Mailbox changes notify
both addresses; resets notify the current mailbox. Account changes, notifications
and secret-free audits commit atomically. Delivery capacity/audit failures roll
everything back, leaving valid tokens retryable until their original expiry.
Per-account requests are limited to one per minute and five per hour across these
purposes. At most one pending request per account/purpose remains. Applications
must also impose IP and password-hashing concurrency limits and handle anonymous
request responses without disclosing per-account operational failures.
## Required application boundaries
- Use a configured HTTPS origin and fixed route paths. Never build links from a
request Host header. `Composer` customizes reviewed plain-text copy, not security
state, sender, recipient, headers or editable executable templates.
- Render a deliberate confirmation/reset form; only POST consumes a token. Keep
strict same-origin/CSRF protection and private/no-store responses. Prefer
`TokenInFragment` so the browser transfers the code into the deliberate POST
without placing it in HTTP/proxy request targets. Native forms can require a
same-origin referrer policy to retain a usable Origin header; fragments are
never included in referrers. Script-only flows can use no-referrer. Do not
weaken origin validation to accept opaque/null origins, and never allow link
scanners or GET requests to change account state.
- Exclude tokens, query strings, message bodies, addresses, passwords and SMTP
credentials from logs/telemetry. Restrict token-bearing pages and avoid external
analytics/resources. Debug redaction does not make structured serialization safe.
- Return anonymous request responses consistently, independent of eligibility or
SMTP acceptance. Run SMTP through the bounded outbox worker, not in the request.
- After reset/change, clear the browser's old session and return to normal login.
Preserve the application's passkey/MFA checks; mailbox control is not an owner
recovery grant. Keep printed/owner-assisted recovery separate.
- Existing Stripe receipts/billing emails are financial snapshots, not canonical
login identifiers. Do not rewrite them as part of an account email change.
The design follows the applicable OWASP guidance on
[password reset](https://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html)
and [registered-email changes](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#changing-a-users-registered-email-address).
This is not a claim of a completed application security audit.
Local tests cover both confirmation orders, replay/expiry, generic reset requests,
credential/session races, competing resets, address conflicts, stable ownership,
retained factors, invitation handling, rate limits, restart and transactional
migration/audit/outbox rollback. The account packages and mail/outbox suites pass;
focused races and vet pass. Consumer HTTP/UI integration, release/publication and
controlled real-mail proof remain pending in the repository queue.