Exposes a new query telling whether a given username is available for
registration, and if not, why: already taken, or rejected by the
registration policy. A username reserved on the homeserver side is
deliberately reported as taken, mirroring the ambiguity of the
registration form, which doesn't tell the two apart either.
Because this effectively is a username-existence oracle, the query is
rate-limited per requester, configurable through the new
`rate_limiting.username_availability` option.
The server-rendered registration form is replaced by a React island mounted
on `/register`, which renders the CAPTCHA in-page, gives live password
complexity feedback, and lays out the upstream provider buttons and the
sign-in link the way `/login` does. The island owns the whole form, so every
variant of the page is rendered from the mount data alone, and the user fills
it in one step. Server-side validation errors are threaded back into it
through the mount node, so a rejected submission re-renders with the errors
attached to the fields they belong to, until the user edits them.
`RegisterContext` resolves the provider names and the sign-in link server-side
so that the island only has to render them. The sign-in link is derived from
the post-auth action in the query string instead of the `PostAuthContext`
loaded from the database, which drops a round trip from both the GET and the
invalid-form re-render.
Registering now requires JavaScript, including on the variant with upstream
providers only and on the one without a CAPTCHA: the <noscript> block shows a
notice instead of a duplicate form, which could not work anyway when a CAPTCHA
is configured. While the script loads, the page shows a skeleton of the form
to avoid layout shifts, and a hidden error notice is revealed if it fails to
boot. The server-rendered CAPTCHA component and its entrypoint are removed
with their only consumer.
The class becomes `or-separator`, as the global `.separator` also matched the
footer's separator. The module is imported by `templates.css` for the SSR
`field.separator()` macro.
`POST /register` trims and lowercases the username before either the password
or the upstream provider path looks at it, since Matrix localparts are
lowercase.
The page imported it on its own as well, which is what broke the CAPTCHA rendering fixed by #5992 on the old password registration page. Also test that the page renders with a CAPTCHA configured.
The registration page had two steps: `/register` picked between a password
and an upstream provider, then `/register/password` collected the username,
email and password. Both now live on `/register`, whose `POST` handler runs
the provider branch first and otherwise hands the form to the registration
body, which stays in `password.rs` as a helper.
The page renders the username, email, password and terms-of-service fields,
the captcha and the provider buttons in a single form. The provider buttons
submit that same form, so they are marked `formnovalidate` to skip the
validation of the fields they do not use, and come after the primary submit
button so that Enter still registers.
`/register/password` is gone; a `GET` on it redirects to `/register` with the
query string it was given, so bookmarks and links keep working. Its
`?username=` prefill has no producer left and is dropped.
`GET /register` now renders the page whenever password registration is
enabled, instead of redirecting to `/register/password` when there is no
upstream provider. Without password registration, a single provider still
gets redirected to directly, and no provider at all now redirects to
`/login`, where `/register/password` used to send that case.
An unknown or disabled provider gives a 404 through an inline `GenericError`
rather than the route-wide `RouteError`, and an expired CSRF token on the
password path now sends the user back to `/register` instead of failing the
request.
The registration page is about to render the password registration form
itself, so its context needs the form state and the captcha configuration
`PasswordRegisterContext` carries today.
ulid 3 moves to `rand` 0.10, so it no longer pulls the `rand` 0.9
generation. Only `opentelemetry_sdk` and `sentry` still need it.
ULID generation keeps going through `UlidExt`, whose bit layout matches
ulid's own `from_datetime_with_source`. The only API change affecting
MAS is the rename of `Ulid::new` to `Ulid::generate`, which the clippy
ban list now targets.
`rand` 0.10 and the new RustCrypto generation both build on `rand_core`
0.10, so they have to move together:
- `rand`, `rand_chacha` and `rand_core` 0.10
- `digest` 0.11, `sha2` 0.11, `hmac` 0.13, `const-oid` 0.10
- `elliptic-curve` 0.14, `ecdsa` 0.17, `p256`, `p384` and `k256` 0.14,
`signature` 3
- `der`, `spki` and `sec1` 0.8, `pkcs8` 0.11, `pem-rfc7468` 1.0
- `rsa` 0.10.0-rc.19 and `pkcs1` 0.8.0-rc.5, which have no stable
release yet. They are pinned exactly, because release candidates
break their API between releases.
- `argon2` 0.6 and `pbkdf2` 0.13
On the `rand` side, `RngCore` becomes `Rng` and the old `Rng` becomes
`RngExt`. `gen_*` becomes `random_*`, `thread_rng` becomes `rng`,
`distributions` becomes `distr`, and `from_rng` can no longer fail. On
the crypto side, `mas-jose` and `mas-keystore` move to `BoxedUint` for
RSA, to the `pkcs1` and `pkcs8` reference types, and to the `Generate`
trait for key generation, and `mas-jose` no longer depends on
`generic-array`. Password hashing goes through `hash_password_with_rng`.
Stored data is unaffected. The password hashing snapshots are unchanged,
so bcrypt, Argon2 and PBKDF2 produce the same hashes for the same input
and salt, and `pbkdf2` 0.13 keeps the same defaults (SHA-256, 600 000
rounds). The keystore snapshots change only because keys generated from
a seeded RNG differ. The snapshots of keys loaded from files are
unchanged.
`opa-wasm` 0.3.3 already uses this generation, so most of the duplicate
crates it pulled in go away. The `deny.toml` skip list now names what
still pulls the older versions: cookie, sqlx, headers, wasmtime and pest
for RustCrypto, and opentelemetry_sdk, sentry and ulid for `rand` 0.9.
It moves from `generic-array` to `hybrid-array`, so `Encrypter` builds
its key and nonce with the `Key` and `Nonce` aliases, and `mas-keystore`
no longer depends on `generic-array`. Cookie (via `aes-gcm`) is still on
`aead` 0.5, so the older `aead` and `universal-hash` go on the cargo-deny
skip list.
The hashes it produces are unchanged. It pulls `blowfish` 0.10, which is
on `cipher` 0.5 while the rest of the tree is on 0.4, so the older
`cipher` and `inout` go on the cargo-deny skip list.
The `cargo deny` bans check fails on main because two dependency bumps
introduced duplicate crate versions:
- `opa-wasm` 0.3.3 (#6029) uses `rand` 0.10, `base64` 0.23 and the
`digest` 0.11 generation of the RustCrypto crates, while the rest of
the tree is still on `rand` 0.9, `base64` 0.22 and `digest` 0.10.
- The icu bump (#6015) pulls `zerovec-derive` 0.11.6, which uses `syn` 3,
while everything else is on `syn` 2.
Add the older versions to the skip list until the rest of the tree
catches up.