Docs: sign-up form, no-JS time-lock fallback and its tradeoff; steward keeps a verified address for sign-in recovery
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9c4ab5646a
commit
185cfcd0e7
2 changed files with 53 additions and 22 deletions
|
|
@ -59,7 +59,7 @@ There are no development or ephemeral keys in the host. Key files are provisione
|
|||
| `preferred_username` | `NormalizedBifrostIdentity` (minted once; there is no rename path) |
|
||||
| `name` | `DisplayName`, else `BifrostIdentity` |
|
||||
|
||||
No e-mail, no Heimdall id, no GitHub id, and no membership detail leaves Bifrost.
|
||||
No e-mail, no Heimdall id, no GitHub id, and no membership detail leaves Bifrost. A native account's verified address, once that is built, stays inside Bifrost as sign-in recovery and is never a claim (`registration-gates.md`, Email verification).
|
||||
|
||||
## Who may sign in
|
||||
|
||||
|
|
|
|||
|
|
@ -41,11 +41,29 @@ server verifies the protection, the expiry and the hash, and spends the id once.
|
|||
(`Bifrost:Registration:Limits`, 10 per minute by default, IPv6 by /64, over budget is 429 with Retry-After). An attacker
|
||||
with many addresses can still fill the set; that is the accepted bound, the same as oidc-issuer.md.
|
||||
|
||||
Not built: the sign-up form. No registration page exists today; the endpoint is the contract and the only client is a
|
||||
script. The form (solving in a worker, a `<noscript>` message) is the cut after this one. Without JavaScript a person
|
||||
cannot solve the challenge, so the accessible fallback for them is owed with the form: a documented solver is not one. Do
|
||||
not call the CAPTCHA gate complete for people until that fallback exists.
|
||||
### The sign-up form and the no-JS fallback
|
||||
|
||||
`GET /auth/bifrost/register` renders the form: identity, optional display name, the notice in full beside an unchecked
|
||||
consent box (the box is the consent act), and a challenge. With JavaScript the page solves the proof of work in the
|
||||
background and reports progress in a polite live region. Without JavaScript the form still submits and succeeds, by a
|
||||
time-lock:
|
||||
|
||||
- The challenge carries its issue time. A submission that says it waited is accepted only once `FallbackWaitSeconds`
|
||||
(default 30) have passed since issue; earlier, the page comes back saying how many seconds remain and the challenge is
|
||||
not spent (a fresh challenge would restart the clock). Every other failure re-renders with a fresh challenge, the
|
||||
consent box unchecked again and the identity kept. `FallbackWaitSeconds` is validated at 5 to under 300, since a challenge
|
||||
lives 300 seconds.
|
||||
- Fallback submissions have their own, stricter per-address budget (`Limits:FallbackPermits`, default 3 per
|
||||
`FallbackWindow`, default one hour; over it is 429 with Retry-After).
|
||||
- A submission names a nonce or says it waited, never both.
|
||||
|
||||
Tradeoff, stated plainly. The time-lock is accessible to everyone who can use a browser: nothing to see, hear, read or
|
||||
solve, no script, no third party. It is also weaker than the proof of work. It costs an attacker wall-clock time, not CPU,
|
||||
and an attacker can start many challenges at once and wait once, so what bounds the fallback is the per-address budget
|
||||
and nothing else. An attacker with many addresses passes it at the price of the wait. We accept that because a
|
||||
registration buys little: the account is `Authenticated`, in Users, with no vote and no member surface, and the other
|
||||
gates (consent, verification once built, reserved names, suspension) still apply. If fallback registrations are abused,
|
||||
raise the wait or lower the budget; do not add a visual or audio puzzle, which excludes the people the fallback is for.
|
||||
## Consent receipt
|
||||
|
||||
Lives in Bifrost: the account, the receipt and the registration are one transaction, and chat admission is Bifrost's
|
||||
|
|
@ -65,22 +83,35 @@ decision (`admission-is-bifrost-membership`).
|
|||
|
||||
Scope limits: accounts that arrive through Heimdall providers (Discord, Patreon, Twitch, GitHub) are created in
|
||||
`HeimdallAuthService.UpsertUserAccountAsync` and get no receipt. They reach chat the same way. Closing that needs a
|
||||
consent step on the completion page and is the next cut after the form. Existing accounts are not re-asked.
|
||||
consent step on the completion page and is the next cut. Existing accounts are not re-asked.
|
||||
|
||||
## Email verification
|
||||
|
||||
Not built. It cannot be built in Bifrost alone:
|
||||
Not built. Decided (steward, 2026-11-11): Bifrost keeps a verified address, as sign-in recovery.
|
||||
|
||||
- There is no mail transport anywhere in Bifrost, Heimdall or the chat stack. A sender (SMTP relay or provider, SPF/DKIM
|
||||
for the sending domain) is ops infrastructure, and Bifrost's side is an `IEmailSender` port with that relay's settings
|
||||
as secret files.
|
||||
- A native account has no credential and no sign-in route (`concept-mill-votes-target.md`, claim 12). Verifying an
|
||||
address proves nothing unless the address is also how the person returns. That makes email the credential: a
|
||||
single-use sign-in link, and the choice of whether Bifrost keeps the address (needed to send it) or only a hash (cannot
|
||||
send). The issuer doc promises no email leaves Bifrost; keeping one inside it is a privacy decision for the operator.
|
||||
- Shape once ruled: registration becomes two steps. Step one takes identity, address, consent and the CAPTCHA, and mails a
|
||||
single-use token; no account exists yet. Step two redeems the token and creates the account and receipt together. An
|
||||
unverified account then cannot exist, so it cannot chat.
|
||||
Why: a native account has no credential and no sign-in route (`concept-mill-votes-target.md`, claim 12). A person who
|
||||
loses the session cookie loses the account, and the chat localpart minted from it is permanent. An address that was
|
||||
verified is the one way back, and the usual way (MAS, Matrix homeservers and most accounts hold one). Hashing it instead
|
||||
cannot send mail, so it recovers nothing.
|
||||
|
||||
Failure it prevents: accounts for addresses nobody holds, and unrecoverable accounts. Owed: a mail relay from ops, and the
|
||||
ruling on keeping the address.
|
||||
Privacy handling, which the build must honour:
|
||||
|
||||
- Stored only for native accounts, one verified address per account, unique across accounts (a keyed hash of the
|
||||
normalized address is the lookup; the address itself is encrypted with Data Protection, since sending needs it).
|
||||
- Never in a token or claim (the issuer doc's claim table is unchanged), never in a log line, a dashboard, a Verse
|
||||
document, a training export, or Heimdall. The account page shows it only to its owner.
|
||||
- Used for two things: the verification mail and a single-use sign-in link (15 minutes, spent on use, per-address and
|
||||
per-account send budgets). No notices, no newsletters, no sharing.
|
||||
- The owner can remove it (then there is no recovery) or change it (a new verification first). Deleting the account
|
||||
deletes it.
|
||||
- Existing accounts and provider accounts are not asked.
|
||||
|
||||
The build cannot start until ops provides a transport: there is no mail sender anywhere in Bifrost, Heimdall or the chat
|
||||
stack. Bifrost's side is an `IEmailSender` port with the relay's settings as secret files; the relay, its sending domain
|
||||
and SPF/DKIM are ops infrastructure.
|
||||
|
||||
Shape: registration becomes two steps. Step one takes identity, address, consent and the CAPTCHA, and mails a single-use
|
||||
token; no account exists yet. Step two redeems the token and creates the account, the receipt and the stored address
|
||||
together. An unverified account then cannot exist, so it cannot chat.
|
||||
|
||||
Failure it prevents: accounts for addresses nobody holds, and unrecoverable accounts.
|
||||
|
|
|
|||
Loading…
Reference in a new issue