[fix] synchronize authenticated password changes

This commit is contained in:
dijunkun committed 2026-08-16 16:55:30 +08:00
1 parent 174745a98b
commit 8bda471bbd
4 files changed
+134 -1

No files matched your search

+49 -1
View File
@@ -70,11 +70,16 @@ bool SignalNegotiation::login_user(websocketpp::connection_hdl hdl,
// Check if AddDevice failed
if (ret_host_id.empty()) {
std::string reason = "Failed to register device";
if (!host_id.empty() &&
device_db_manager_->VerifyDevice(host_id, password) == -1) {
reason = "Incorrect password";
}
LOG_ERROR("Failed to add device for host_id [{}]", host_id);
json message = {{"type", "login"},
{"user_id", ""},
{"status", "fail"},
{"reason", "Failed to register device"}};
{"reason", reason}};
send_msg_(hdl, message);
return true;
}
@@ -426,6 +431,49 @@ bool SignalNegotiation::new_candidate_mid(websocketpp::connection_hdl hdl,
return true;
}
bool SignalNegotiation::change_password(websocketpp::connection_hdl hdl,
const json& j) {
json message = {{"type", "change_password"}};
std::string request_id;
if (!GetStringField(j, "request_id", request_id) || request_id.empty()) {
message["status"] = "fail";
message["reason"] = "Missing or invalid request ID";
send_msg_(hdl, message);
return true;
}
message["request_id"] = request_id;
const std::string user_id = transmission_manager_->GetUserId(hdl);
message["user_id"] = user_id;
if (user_id.empty()) {
message["status"] = "fail";
message["reason"] = "Not authenticated";
} else if (user_id.rfind("C-", 0) == 0 ||
user_id.rfind("web-", 0) == 0) {
// Controller and browser identities are temporary and are not authenticated
// against a persisted device password.
message["status"] = "fail";
message["reason"] = "Password changes are not allowed for this identity";
} else {
std::string new_password;
if (!GetStringField(j, "new_password", new_password) ||
new_password.size() != 6) {
message["status"] = "fail";
message["reason"] = "Password must contain exactly 6 characters";
} else if (!device_db_manager_->UpdatePassword(user_id, new_password)) {
message["status"] = "fail";
message["reason"] = "Failed to update password";
} else {
message["status"] = "success";
LOG_INFO("Authenticated client [{}] changed its device password", user_id);
}
}
send_msg_(hdl, message);
return true;
}
bool SignalNegotiation::turn_credentials(websocketpp::connection_hdl hdl,
const json& j) {
(void)j;
+1
View File
@@ -36,6 +36,7 @@ class SignalNegotiation {
bool answer(websocketpp::connection_hdl hdl, const json& j);
bool new_candidate(websocketpp::connection_hdl hdl, const json& j);
bool new_candidate_mid(websocketpp::connection_hdl hdl, const json& j);
bool change_password(websocketpp::connection_hdl hdl, const json& j);
bool turn_credentials(websocketpp::connection_hdl hdl, const json& j);
void OnWebClientDisconnect(const std::string& user_id);
+3
View File
@@ -828,6 +828,9 @@ void SignalServer::OnMessage(websocketpp::connection_hdl hdl,
case "new_candidate_mid"_H:
signal_negotiation_->new_candidate_mid(hdl, j);
break;
case "change_password"_H:
signal_negotiation_->change_password(hdl, j);
break;
case "turn_credentials"_H:
signal_negotiation_->turn_credentials(hdl, j);
break;
+81
View File
@@ -84,12 +84,52 @@ int main() {
"wrong device password login response has login type");
expect(sent_messages[0].value("status", "") == "fail",
"wrong device password login fails");
expect(sent_messages[0].value("reason", "") == "Incorrect password",
"wrong device password login reports a specific reason");
}
expect(db.VerifyDevice(offline_host.device_id, offline_host.password) == 0,
"wrong device password login does not replace original password");
expect(db.VerifyDevice(offline_host.device_id, "attacker-password") != 0,
"wrong device password is not accepted after failed login");
sent_messages.clear();
negotiation.change_password(
attacker_hdl,
{{"type", "change_password"}, {"new_password", "654321"}});
expect(sent_messages.size() == 1 &&
sent_messages[0].value("status", "") == "fail" &&
sent_messages[0].value("reason", "") ==
"Missing or invalid request ID",
"password change requires a request id");
sent_messages.clear();
negotiation.change_password(
attacker_hdl,
{{"type", "change_password"},
{"request_id", "unauthenticated"},
{"new_password", "654321"}});
expect(sent_messages.size() == 1 &&
sent_messages[0].value("status", "") == "fail" &&
sent_messages[0].value("reason", "") == "Not authenticated",
"unauthenticated connection cannot change a device password");
expect(db.VerifyDevice(offline_host.device_id, offline_host.password) == 0,
"unauthenticated password change leaves the password unchanged");
sent_messages.clear();
auto controller_connection = std::make_shared<int>(5);
websocketpp::connection_hdl controller_hdl(controller_connection);
transmission->BindUserToWsHandle("C-controller", controller_hdl);
negotiation.change_password(
controller_hdl,
{{"type", "change_password"},
{"request_id", "controller"},
{"new_password", "654321"}});
expect(sent_messages.size() == 1 &&
sent_messages[0].value("status", "") == "fail",
"temporary controller identity cannot change a device password");
expect(db.VerifyDevice(offline_host.device_id, offline_host.password) == 0,
"controller password change leaves the device password unchanged");
sent_messages.clear();
auto authenticated_connection = std::make_shared<int>(3);
websocketpp::connection_hdl authenticated_hdl(authenticated_connection);
@@ -132,6 +172,47 @@ int main() {
sent_messages[0].contains("turn"),
"authenticated client can explicitly refresh TURN credentials");
sent_messages.clear();
negotiation.change_password(
authenticated_hdl,
{{"type", "change_password"},
{"request_id", "malformed"},
{"new_password", "short"}});
expect(sent_messages.size() == 1 &&
sent_messages[0].value("status", "") == "fail",
"authenticated client cannot set a malformed password");
expect(db.VerifyDevice(offline_host.device_id, offline_host.password) == 0,
"malformed password change leaves the password unchanged");
sent_messages.clear();
const std::string new_password = "654321";
negotiation.change_password(
authenticated_hdl,
{{"type", "change_password"},
{"request_id", "successful"},
{"new_password", new_password}});
expect(sent_messages.size() == 1 &&
sent_messages[0].value("type", "") == "change_password" &&
sent_messages[0].value("status", "") == "success" &&
sent_messages[0].value("request_id", "") == "successful" &&
sent_messages[0].value("user_id", "") == offline_host.device_id,
"authenticated device can change its own password");
expect(db.VerifyDevice(offline_host.device_id, offline_host.password) != 0,
"old password is rejected after a successful password change");
expect(db.VerifyDevice(offline_host.device_id, new_password) == 0,
"new password is accepted after a successful password change");
sent_messages.clear();
auto relogin_connection = std::make_shared<int>(4);
websocketpp::connection_hdl relogin_hdl(relogin_connection);
negotiation.login_user(
relogin_hdl,
{{"type", "login"},
{"user_id", offline_host.device_id + "@" + new_password}});
expect(sent_messages.size() == 1 &&
sent_messages[0].value("status", "") == "success",
"device can log in again with its changed password");
}
std::filesystem::remove(db_path);