This is an automated email from the ASF dual-hosted git repository.

piotr pushed a commit to branch fix_password_hash
in repository https://gitbox.apache.org/repos/asf/iggy.git

commit 9444f45c6a8a7cd0e7015377e3b390c6c7ac4ce6
Author: spetz <[email protected]>
AuthorDate: Wed Feb 11 22:48:06 2026 +0100

    fix(server): password hashing for new user
---
 core/integration/tests/data_integrity/mod.rs       |  1 +
 .../verify_user_login_after_restart.rs             | 90 ++++++++++++++++++++++
 core/server/src/http/users.rs                      |  6 +-
 core/server/src/shard/handlers.rs                  |  7 +-
 4 files changed, 100 insertions(+), 4 deletions(-)

diff --git a/core/integration/tests/data_integrity/mod.rs 
b/core/integration/tests/data_integrity/mod.rs
index 5cf911c94..742e3128e 100644
--- a/core/integration/tests/data_integrity/mod.rs
+++ b/core/integration/tests/data_integrity/mod.rs
@@ -17,3 +17,4 @@
  */
 
 mod verify_after_server_restart;
+mod verify_user_login_after_restart;
diff --git 
a/core/integration/tests/data_integrity/verify_user_login_after_restart.rs 
b/core/integration/tests/data_integrity/verify_user_login_after_restart.rs
new file mode 100644
index 000000000..1fe010765
--- /dev/null
+++ b/core/integration/tests/data_integrity/verify_user_login_after_restart.rs
@@ -0,0 +1,90 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use iggy::prelude::*;
+use integration::harness::{TestHarness, TestServerConfig, USER_PASSWORD, 
create_user, login_user};
+use serial_test::parallel;
+
+#[tokio::test]
+#[parallel]
+async fn should_login_non_root_user_after_restart() {
+    let mut harness = TestHarness::builder()
+        .server(TestServerConfig::default())
+        .build()
+        .unwrap();
+
+    harness.start().await.unwrap();
+
+    // Create a non-root user via TCP (goes through shard handler)
+    let root_client = harness.tcp_root_client().await.unwrap();
+    create_user(&root_client, "testuser").await;
+
+    // Verify login works before restart
+    let client = harness.tcp_new_client().await.unwrap();
+    login_user(&client, "testuser").await;
+    drop(client);
+    drop(root_client);
+
+    // Restart server — state log is replayed
+    harness.restart_server().await.unwrap();
+
+    // Login as root should still work
+    let root_client = harness.tcp_root_client().await.unwrap();
+    let users = root_client.get_users().await.unwrap();
+    assert_eq!(users.len(), 2, "Expected root + testuser after restart");
+    drop(root_client);
+
+    // Login as the non-root user must work after restart
+    let client = harness.tcp_new_client().await.unwrap();
+    login_user(&client, "testuser").await;
+}
+
+#[tokio::test]
+#[parallel]
+async fn should_login_after_password_change_and_restart() {
+    let mut harness = TestHarness::builder()
+        .server(TestServerConfig::default())
+        .build()
+        .unwrap();
+
+    harness.start().await.unwrap();
+
+    let root_client = harness.tcp_root_client().await.unwrap();
+    create_user(&root_client, "testuser").await;
+
+    // Change the user's password
+    let new_password = "new_secret_password";
+    let user_id = Identifier::named("testuser").unwrap();
+    root_client
+        .change_password(&user_id, USER_PASSWORD, new_password)
+        .await
+        .unwrap();
+
+    // Verify login works with new password before restart
+    let client = harness.tcp_new_client().await.unwrap();
+    client.login_user("testuser", new_password).await.unwrap();
+    drop(client);
+    drop(root_client);
+
+    // Restart server — state log is replayed
+    harness.restart_server().await.unwrap();
+
+    // Login with the changed password must work after restart
+    let client = harness.tcp_new_client().await.unwrap();
+    client.login_user("testuser", new_password).await.unwrap();
+}
diff --git a/core/server/src/http/users.rs b/core/server/src/http/users.rs
index 8e4dcd1b9..b2b0c1fba 100644
--- a/core/server/src/http/users.rs
+++ b/core/server/src/http/users.rs
@@ -298,7 +298,11 @@ async fn change_password(
         })?;
 
     {
-        let entry_command = EntryCommand::ChangePassword(command);
+        let entry_command = EntryCommand::ChangePassword(ChangePassword {
+            user_id: command.user_id,
+            current_password: "".into(),
+            new_password: crypto::hash_password(&command.new_password),
+        });
         let future = SendWrapper::new(
             state
                 .shard
diff --git a/core/server/src/shard/handlers.rs 
b/core/server/src/shard/handlers.rs
index 53756d5f8..3782d6e3b 100644
--- a/core/server/src/shard/handlers.rs
+++ b/core/server/src/shard/handlers.rs
@@ -26,6 +26,7 @@ use crate::{
             message::{ShardMessage, ShardRequest, ShardRequestPayload},
         },
     },
+    streaming::utils::crypto,
     tcp::{
         connection_handler::{ConnectionAction, handle_connection, 
handle_error},
         tcp_listener::cleanup_connection,
@@ -406,7 +407,7 @@ async fn handle_request(
 
             let command = iggy_common::create_user::CreateUser {
                 username,
-                password,
+                password: crypto::hash_password(&password),
                 status,
                 permissions,
             };
@@ -560,8 +561,8 @@ async fn handle_request(
 
             let command = iggy_common::change_password::ChangePassword {
                 user_id,
-                current_password,
-                new_password,
+                current_password: "".into(),
+                new_password: 
crate::streaming::utils::crypto::hash_password(&new_password),
             };
             shard
                 .state

Reply via email to