diff --git a/crates/sql-backend-handler/src/sql_opaque_handler.rs b/crates/sql-backend-handler/src/sql_opaque_handler.rs index 6c0fe3d..ccaec93 100644 --- a/crates/sql-backend-handler/src/sql_opaque_handler.rs +++ b/crates/sql-backend-handler/src/sql_opaque_handler.rs @@ -78,40 +78,24 @@ impl SqlBackendHandler { Ok(users.first().map(|user_and_groups| user_and_groups.user.user_id.clone())) } + + } #[async_trait] impl LoginHandler for SqlBackendHandler { #[instrument(skip_all, level = "debug", err)] async fn bind(&self, request: BindRequest) -> Result<()> { - // First try to authenticate with the provided name as a user ID - let mut actual_user_id = request.name.clone(); - let mut password_hash = self + if let Some(password_hash) = self .get_password_file_for_user(request.name.clone()) - .await?; - - // If no user found by user ID, try to find by email - if password_hash.is_none() { - debug!(r#"User "{}" not found by user ID, trying email lookup"#, &request.name); - if let Some(user_id_by_email) = self - .find_user_id_by_email(&request.name.as_str()) - .await? - { - debug!(r#"Found user by email: "{}""#, &user_id_by_email); - actual_user_id = user_id_by_email; - password_hash = self - .get_password_file_for_user(actual_user_id.clone()) - .await?; - } - } - - if let Some(password_hash) = password_hash { - info!(r#"Login attempt for "{}" (input: "{}")"#, &actual_user_id, &request.name); + .await? + { + info!(r#"Login attempt for "{}""#, &request.name); if passwords_match( &password_hash, &request.password, &self.opaque_setup, - &actual_user_id, + &request.name, ) .is_ok() { @@ -137,14 +121,33 @@ impl OpaqueHandler for SqlOpaqueHandler { &self, request: login::ClientLoginStartRequest, ) -> Result { - let user_id = request.username; - info!(r#"OPAQUE login attempt for "{}""#, &user_id); - let maybe_password_file = self - .get_password_file_for_user(user_id.clone()) - .await? + // First try to authenticate with the provided name as a user ID + let mut actual_user_id = request.username.clone(); + let mut maybe_password_file = self + .get_password_file_for_user(request.username.clone()) + .await?; + + // If no user found by user ID, try to find by email for web UI login + if maybe_password_file.is_none() { + debug!(r#"User "{}" not found by user ID, trying email lookup for web login"#, &request.username); + if let Some(user_id_by_email) = self + .find_user_id_by_email(request.username.as_str()) + .await? + { + debug!(r#"Found user by email: "{}""#, &user_id_by_email); + actual_user_id = user_id_by_email; + maybe_password_file = self + .get_password_file_for_user(actual_user_id.clone()) + .await?; + } + } + + info!(r#"OPAQUE login attempt for "{}" (input: "{}")"#, &actual_user_id, &request.username); + + let maybe_password_file = maybe_password_file .map(|bytes| { opaque::server::ServerRegistration::deserialize(&bytes).map_err(|_| { - DomainError::InternalError(format!("Corrupted password file for {}", &user_id)) + DomainError::InternalError(format!("Corrupted password file for {}", &actual_user_id)) }) }) .transpose()?; @@ -156,11 +159,11 @@ impl OpaqueHandler for SqlOpaqueHandler { &self.opaque_setup, maybe_password_file, request.login_start_request, - &user_id, + &actual_user_id, )?; let secret_key = self.get_orion_secret_key()?; let server_data = login::ServerData { - username: user_id, + username: actual_user_id, server_login: start_response.state, }; let encrypted_state = orion::aead::seal(&secret_key, &bincode::serialize(&server_data)?)?; @@ -342,6 +345,7 @@ mod tests { let handler = SqlOpaqueHandler::new(generate_random_private_key(), sql_pool.clone()); insert_user(&handler, "bob", "bob00").await; + // Test login with username (should work) handler .bind(BindRequest { name: UserId::new("bob"), @@ -349,6 +353,8 @@ mod tests { }) .await .unwrap(); + + // Test login with non-existent user handler .bind(BindRequest { name: UserId::new("andrew"), @@ -356,6 +362,8 @@ mod tests { }) .await .unwrap_err(); + + // Test login with wrong password handler .bind(BindRequest { name: UserId::new("bob"), @@ -363,47 +371,37 @@ mod tests { }) .await .unwrap_err(); + + // Test that email login is NOT supported for LDAP bind + handler + .bind(BindRequest { + name: UserId::new("bob@bob.bob"), + password: "bob00".to_string(), + }) + .await + .unwrap_err(); } #[tokio::test] - async fn test_bind_user_with_email() { + async fn test_opaque_login_with_email() { let sql_pool = get_initialized_db().await; - let handler = SqlOpaqueHandler::new(generate_random_private_key(), sql_pool.clone()); - insert_user(&handler, "bob", "bob00").await; - - // Test login with username (should work as before) - handler - .bind(BindRequest { - name: UserId::new("bob"), - password: "bob00".to_string(), - }) - .await - .unwrap(); - - // Test login with email (new functionality) - handler - .bind(BindRequest { - name: UserId::new("bob@bob.bob"), - password: "bob00".to_string(), - }) - .await - .unwrap(); - - // Test login with non-existent email - handler - .bind(BindRequest { - name: UserId::new("nonexistent@bob.bob"), - password: "bob00".to_string(), - }) + crate::logging::init_for_tests(); + let backend_handler = SqlBackendHandler::new(generate_random_private_key(), sql_pool); + insert_user(&backend_handler, "bob", "bob00").await; + + // Test OPAQUE login with username (should work as before) + attempt_login(&backend_handler, "bob", "bob00").await.unwrap(); + + // Test OPAQUE login with email (new functionality) + attempt_login(&backend_handler, "bob@bob.bob", "bob00").await.unwrap(); + + // Test OPAQUE login with non-existent email + attempt_login(&backend_handler, "nonexistent@bob.bob", "bob00") .await .unwrap_err(); - - // Test login with wrong password using email - handler - .bind(BindRequest { - name: UserId::new("bob@bob.bob"), - password: "wrong_password".to_string(), - }) + + // Test OPAQUE login with wrong password using email + attempt_login(&backend_handler, "bob@bob.bob", "wrong_password") .await .unwrap_err(); }