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
