Hosted account lifecycle

Status: implemented account authentication policy; lifecycle execution and deployment evidence are recorded separately below. Reversible work continues under the user’s existing authorization. Reviewer fields record evidence and outcomes rather than manufacturing approval or requiring the user to authorize already requested work again. This document supersedes email-first materialization for the hosted free-account signup flow; the protocol's provider/email discovery distinction remains unchanged.

An account is free and may have no email address or phone number. Its stable random account ID is separate from its unique lowercase 3–32-character handle. Turnstile verification and rate limits guard creation, password/passkey login, email-login requests and recovery. Account creation requires explicit agreement to the current dated terms. Stored agreement records contain the accepted version and time. Attempt-level rate limits apply before CAPTCHA to bound abuse, while daily/global creation capacity is reserved only after CAPTCHA, terms, handle, method and password validation and an existing-handle check. Invalid proof or malformed signup cannot exhaust global creation capacity. The account remains pending until an authenticator is configured and recovery setup is completed.

Passkeys are the primary choice. Password signup remains available, with a 15–128-character password and no normalization or composition rules. Passwords use Argon2id with 19 MiB, two iterations, parallelism one, a random 32-byte salt and 32-byte output. The maintained hash-wasm implementation supplies the KDF. Workers statically import its original Argon2 and BLAKE2b WASM modules because runtime compilation is prohibited; a reproducible preparation script retains MIT licensing and source/binary hashes. Actual local workerd tests verify password signup and login and interoperability with the unmodified Node KDF. Deployed CPU/memory budgets still require evidence. Native Worker PBKDF2 has historically capped iteration counts below the desired password work factor and is not silently substituted. No password, session secret or recovery token is persisted in plaintext.

Passkey creation requires a discoverable credential and user verification. Maintained SimpleWebAuthn verifies the exact configured HTTPS origin, RP ID, challenge, operation type, presence and verification flags; assertion signatures, credential/account binding and counters are checked before creating a session. Cross-origin iframe ceremonies are rejected. Challenges expire after five minutes and are removed in the same atomic account update that establishes success. Registration and signed assertion tests exercise the real library with generated ES256 authenticator data. Physical authenticator/browser and deployed Worker verification remain separate release evidence.

After credential setup, the browser downloads a JSON recovery file containing issuer, handle and an unpredictable recovery secret authorizing one recovery transaction. Only its domain-separated SHA-256 hash is stored. Downloading or reissuing a file does not activate the account; explicit confirmation and proof of the current file's secret are required. The service can establish that the browser received the secret and acknowledged saving it; it cannot prove that the user retained a durable external copy. Reissuing invalidates previous files. If email/phone recovery is absent, losing both the authenticator and recovery file permanently loses account access. Operator support must not bypass this boundary.

Recovery starts one transaction with an atomic compare-and-swap, revokes every owner session and outstanding email challenge, and clears the previous authenticators. It returns a limited setup token that expires after 30 minutes instead of an owner session. While the account is recovering, a separate hashed resume authority lets the original file resume the same transaction after a lost response or expired setup. Resumption is CAPTCHA/rate-limit guarded, rotates the setup token and invalidates its predecessor, and preserves already configured replacement credentials and the replacement file hash. It does not start another credential reset or issue an owner session. Keep the original file until replacement setup is complete. Activation still requires the newly downloaded file’s proof and explicit saving acknowledgement; that activation atomically deletes the old resume hash. The original file cannot authenticate, resume or reset the account after activation. Concurrent state transitions have one winner.

Owner sessions use unpredictable tokens stored only as domain-separated hashes. The HTTP adapter sets Secure, HttpOnly, SameSite=Strict host-only cookies on HTTPS and never returns cookie secrets in JSON. Every owner mutation requires the exact configured origin and a session-bound CSRF token. Session lifetimes are bounded to 24 hours, with at most ten concurrent owner sessions. CSRF values are not authentication credentials and may be returned to same-origin authenticated JavaScript. Pending setup sessions expire after 30 minutes. Audit records contain event type and time, never token material, passwords, file contents or email addresses.

Email is optional. A recently authenticated owner may request linking an ordinary ASCII mailbox; only the domain is lowercased, and local-part spelling is preserved. The verification challenge binds the proposed mailbox, account and initiating owner session. Mail delivery receives the plaintext token; HTTP responses do not. A link GET only displays confirmation. Explicit authenticated, CSRF-protected POST consumes the token and atomically claims a unique verified mailbox. Concurrent ownership conflicts fail rather than merge accounts. Cross-device linking requires restarting from the signed-in browser. Mail delivery configuration and provider verification are deployment gates, and disabled delivery returns an explicit unavailable result.

A verified mailbox is initially only a contact address. Email login is disabled by default and requires a separate explicit recently authenticated, CSRF-protected opt-in. An owner may disable it independently of retaining a verified contact; disabling clears outstanding email-login challenges, and replacing the verified address requires opting in again. Start and redemption both check the current opt-in. Only explicitly enabled verified mailboxes support login links. Requests are Turnstile/rate-limit guarded and return a generic result for existing and unknown mailboxes. Unverified requests never create an account. Login links expire after ten minutes; GET is nonconsuming and POST requires explicit acceptance before issuing a new session. The challenge binds the mailbox's ownership revision, preventing stale reassociation from authenticating. Secret-bearing pages require no-referrer, no external assets, removal of token-bearing URLs from the address bar and scrubbed logs. This implementation does not claim phone verification or arbitrary-email account materialization.

The platform-independent service receives repository, crypto, clock, Turnstile, rate-limit, WebAuthn, password and optional mail resources. Actions and queries use the actual vendored ngin Engine, Provider, Action and Query APIs. Repository create(account) atomically enforces unique IDs and handles; compareAndSwap(id, revision, account) enforces the expected version, unique verified email ownership and index changes together. Both return booleans. get, findByHandle and findByEmail return independent account snapshots. An in-memory test resource is not production persistence. Platform adapters must verify transaction races against their real durable store.

The user's requested retention policy distinguishes unused accounts from established accounts. An unused account receives a confirmation prompt after two days, followed by a seven-day grace period before proposed removal if the user does not keep it. An established account becomes a long-inactivity candidate after one year; its notice/grace period increases with account age, and a verified email or phone earns additional leniency and an opportunity to contact the user. Activity or an explicit keep-account confirmation cancels the candidate. Exact age-scaled arithmetic is still a proposed implementation detail; these requested two-day, seven-day and one-year thresholds must not be replaced by unrelated defaults.

The initial identity planning helper used 180-day shelving, two-year deletion and a 90-day grace period. Those were unsolicited conservative proposals that diverged from the user's request; they are not the adopted retention policy, are not invoked by the Worker scheduler and must not be represented as implemented user-requested behavior. They remain visible in the unused helper pending replacement with the requested policy and integrated notice/recovery/backup evidence. This deviation belongs in DEVLOG.md until reconciled. Existing user authorization covers implementing the requested policy; no additional blanket permission requirement is introduced here.

Separately, the Worker performs seven-day R2 cache eviction, which preserves account data. Cold message/resource metadata moves to a private R2 archive; identity, credential and capability-verification records stay online. Archive writes are read back for integrity, and hot-copy deletion plus the restore marker commit in one transaction. Restores validate the version, owner and digest and restore records transactionally before clearing the marker. Owner use restores cold metadata and resets the cache timer. This cache policy does not start a deletion grace period and does not implement the user's unused-account or long-inactivity removal policy. Blobs remain in R2 throughout.

Automatic permanent deletion remains disabled until actual notice delivery, recovery, activity-race cancellation and backup/export handling are integrated and verified. An unseen dashboard prompt must not be described as delivered notification. A recovery file does not provide an outbound contact channel. Verified contacts should receive the requested greater leniency; no-contact deletion requires its own truthful notice semantics before enabling execution. The present scheduler deletes no account. planAccountLifecycle is a nonexecuting proposal helper whose older thresholds do not establish release behavior. Sensitive snapshots need operator-controlled access, encryption and backup/key-rotation checks; this is not an end-to-end-encryption claim.

Decision Reviewer Date Outcome
Free signup, passkey/password and mandatory recovery gate Pending Pending Provisional implementation under user authorization
Password parameters and Worker resource budget Pending Pending Argon2id implemented; deployment verification required
Optional verified-email ownership and recovery policy Pending Pending Implemented service; configured delivery/provider verification required
Seven-day R2 cache eviction and restoration Pending Pending Separate nondestructive cache policy implemented; actual Worker tests cover restore
Requested unused-account and long-inactivity retention Pending Pending Two-day prompt/seven-day grace and one-year inactivity policy recorded; execution disabled pending notification/recovery/backup integration

Primary implementation references: SimpleWebAuthn server API, SimpleWebAuthn passkey guidance, Cloudflare Web Crypto, and workerd PBKDF2 ceiling. The two-day/seven-day/one-year lifecycle thresholds come from the user’s request, not from those references. The earlier 180/730/90-day helper defaults are recorded above as a proposal deviation.