diff --git a/crates/handlers/src/views/register/mod.rs b/crates/handlers/src/views/register/mod.rs index 70d2b2b68..3e9528985 100644 --- a/crates/handlers/src/views/register/mod.rs +++ b/crates/handlers/src/views/register/mod.rs @@ -177,13 +177,17 @@ pub(crate) async fn post( ) -> Result { let user_agent = user_agent.map(|ua| ua.as_str().to_owned()); - let Ok(form) = cookie_jar.verify_form(&clock, form) else { + let Ok(mut form) = cookie_jar.verify_form(&clock, form) else { // An invalid token most likely comes from a page left open past the CSRF token lifetime tracing::debug!("Invalid CSRF token on the registration form, redirecting to a fresh one"); let destination = Register::from(query.post_auth_action); return Ok((cookie_jar, url_builder.redirect(&destination)).into_response()); }; + // Matrix localparts are lowercase, so a stray capital or surrounding space + // isn't worth an error + form.username = form.username.trim().to_lowercase(); + // Carry the username along so we can prefill it if the user gets to pick // one when they come back if let Some(provider_id) = &form.provider { @@ -201,9 +205,8 @@ pub(crate) async fn post( }; // The username is carried in a cookie, so its size has to be bounded - let username = form.username.trim(); - let username = if username.len() <= MAX_USERNAME_LENGTH { - username + let username = if form.username.len() <= MAX_USERNAME_LENGTH { + form.username.as_str() } else { "" }; diff --git a/crates/handlers/src/views/register/password.rs b/crates/handlers/src/views/register/password.rs index 4704806c7..20a7090a5 100644 --- a/crates/handlers/src/views/register/password.rs +++ b/crates/handlers/src/views/register/password.rs @@ -364,6 +364,55 @@ mod tests { }, }; + /// The username is trimmed and lowercased before anything checks it + #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] + async fn test_register_normalizes_username(pool: PgPool) { + setup(); + let state = TestState::from_pool(pool).await.unwrap(); + let cookies = CookieHelper::new(); + + let request = Request::get(&*mas_router::Register::default().path_and_query()).empty(); + let request = cookies.with_cookies(request); + let response = state.request(request).await; + cookies.save_cookies(&response); + response.assert_status(StatusCode::OK); + let csrf_token = response + .body() + .split("name=\"csrf\" value=\"") + .nth(1) + .unwrap() + .split('\"') + .next() + .unwrap(); + + let request = Request::post(&*mas_router::Register::default().path_and_query()).form( + serde_json::json!({ + "csrf": csrf_token, + "username": " John ", + "email": "john@example.com", + "password": "correcthorsebatterystaple", + "password_confirm": "correcthorsebatterystaple", + "accept_terms": "on", + }), + ); + let request = cookies.with_cookies(request); + let response = state.request(request).await; + response.assert_status(StatusCode::SEE_OTHER); + let location = response.headers().get(LOCATION).unwrap(); + let id = location + .to_str() + .unwrap() + .rsplit('/') + .nth(1) + .unwrap() + .parse() + .unwrap(); + + let mut repo = state.repository().await.unwrap(); + let registration = repo.user_registration().lookup(id).await.unwrap().unwrap(); + assert_eq!(registration.username, "john"); + } + /// Test the registration happy path #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] async fn test_register(pool: PgPool) {