[fix] make password changes idempotent

This commit is contained in:
dijunkun committed 2026-08-23 21:59:20 +08:00
1 parent 78a5fb98d2
commit d8f48ebad6
2 files changed
+65 -6

No files matched your search

+52 -6
View File
@@ -1,5 +1,9 @@
#include "signal_negotiation.h"
#include <openssl/sha.h>
#include <iomanip>
#include <sstream>
#include <utility>
#include "log.h"
@@ -14,6 +18,18 @@ bool GetStringField(const json& j, const char* key, std::string& value) {
return true;
}
std::string PasswordFingerprint(const std::string& password) {
unsigned char digest[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const unsigned char*>(password.data()),
password.size(), digest);
std::ostringstream stream;
stream << std::hex << std::setfill('0');
for (unsigned char byte : digest) {
stream << std::setw(2) << static_cast<unsigned int>(byte);
}
return stream.str();
}
} // namespace
SignalNegotiation::SignalNegotiation(
@@ -433,9 +449,12 @@ bool SignalNegotiation::new_candidate_mid(websocketpp::connection_hdl hdl,
bool SignalNegotiation::change_password(websocketpp::connection_hdl hdl,
const json& j) {
constexpr size_t kMaxRememberedPasswordChanges = 4096;
constexpr size_t kMaxPasswordChangeRequestIdLength = 128;
json message = {{"type", "change_password"}};
std::string request_id;
if (!GetStringField(j, "request_id", request_id) || request_id.empty()) {
if (!GetStringField(j, "request_id", request_id) || request_id.empty() ||
request_id.size() > kMaxPasswordChangeRequestIdLength) {
message["status"] = "fail";
message["reason"] = "Missing or invalid request ID";
send_msg_(hdl, message);
@@ -461,12 +480,39 @@ bool SignalNegotiation::change_password(websocketpp::connection_hdl hdl,
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);
const std::string cache_key = user_id + "\n" + request_id;
const std::string password_fingerprint =
PasswordFingerprint(new_password);
std::lock_guard<std::mutex> lock(password_change_mutex_);
const auto cached = password_change_results_.find(cache_key);
if (cached != password_change_results_.end()) {
if (cached->second.password_fingerprint != password_fingerprint) {
message["status"] = "fail";
message["reason"] = "Request ID was already used";
} else {
message = cached->second.response;
LOG_INFO("Replay password change result for authenticated client "
"[{}] request [{}]",
user_id, request_id);
}
} else if (!device_db_manager_->UpdatePassword(user_id, new_password)) {
message["status"] = "fail";
message["reason"] = "Failed to update password";
} else {
message["status"] = "success";
password_change_results_.emplace(
cache_key, PasswordChangeResult{password_fingerprint, message});
password_change_result_order_.push_back(cache_key);
while (password_change_result_order_.size() >
kMaxRememberedPasswordChanges) {
password_change_results_.erase(
password_change_result_order_.front());
password_change_result_order_.pop_front();
}
LOG_INFO("Authenticated client [{}] changed its device password",
user_id);
}
}
}
+13
View File
@@ -7,7 +7,11 @@
#ifndef _SIGNAL_NEGOTIATION_H_
#define _SIGNAL_NEGOTIATION_H_
#include <deque>
#include <mutex>
#include <nlohmann/json.hpp>
#include <string>
#include <unordered_map>
#include "device_db_manager.h"
#include "transmission_manager.h"
@@ -41,12 +45,21 @@ class SignalNegotiation {
void OnWebClientDisconnect(const std::string& user_id);
private:
struct PasswordChangeResult {
std::string password_fingerprint;
json response;
};
void AddTurnCredentials(json& message, const std::string& user_id) const;
std::shared_ptr<TransmissionManager> transmission_manager_;
DeviceDBManager* device_db_manager_;
std::shared_ptr<TurnCredentialIssuer> turn_credential_issuer_;
std::function<void(websocketpp::connection_hdl, json)> send_msg_;
std::mutex password_change_mutex_;
std::unordered_map<std::string, PasswordChangeResult>
password_change_results_;
std::deque<std::string> password_change_result_order_;
};
#endif