13#include <nlohmann/json.hpp>
19using json = nlohmann::json;
40 if (username ==
"guest" && password ==
"guest") {
45 if (username.empty() || password.empty()) {
46 LOG_WARNING(
"Authentication failed: empty credentials");
51 if (username.length() < 3 || password.length() < 4) {
52 LOG_WARNING(
"Authentication failed: credentials too short (username: ", username.length(),
53 ", password: ", password.length(),
" chars)");
60 LOG_WARNING(
"Authentication failed: account '", username,
"' doesn't exist");
66 LOG_WARNING(
"Authentication failed: incorrect password for '", username,
"'");
71 auto now = std::chrono::system_clock::now();
73 std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
74 it->second.lastLogin = nowSeconds;
87 LOG_INFO(
"✓ Authentication successful for user: ", username);
93 std::random_device rd;
94 std::mt19937 gen(rd());
95 std::uniform_int_distribution<> dis(0, 15);
97 const char *hex =
"0123456789abcdef";
98 std::ostringstream token;
99 token << username <<
"_";
100 for (
int i = 0; i < 32; ++i) {
101 token << hex[dis(gen)];
125 if (username ==
"guest" || username.starts_with(
"Guest_")) {
126 LOG_WARNING(
"Registration failed: username '", username,
"' is reserved for guest access");
131 if (username.empty() || password.empty()) {
132 LOG_WARNING(
"Registration failed: empty username or password");
136 if (username.length() < 3) {
137 LOG_WARNING(
"Registration failed: username '", username,
"' too short (", username.length(),
138 " chars, minimum 3)");
142 if (password.length() < 4) {
143 LOG_WARNING(
"Registration failed: password too short (", password.length(),
" chars, minimum 4)");
149 LOG_WARNING(
"Registration failed: username '", username,
"' already exists");
154 std::string passwordHash;
157 }
catch (
const std::exception &e) {
158 LOG_ERROR(
"Registration failed: password hashing failed for '", username,
"': ", e.what());
163 auto now = std::chrono::system_clock::now();
164 uint64_t timestamp = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
176 LOG_INFO(
"✓ Registration successful for user: ", username);
182 if (!file.is_open()) {
190 file.seekg(0, std::ios::end);
191 if (file.tellg() == 0) {
192 LOG_INFO(
"Accounts file is empty, starting with empty database");
196 file.seekg(0, std::ios::beg);
203 if (!j.contains(
"accounts") || !j[
"accounts"].is_array()) {
204 LOG_WARNING(
"Invalid accounts file format, starting with empty database");
208 for (
const auto &accountJson : j[
"accounts"]) {
209 if (!accountJson.contains(
"username") || !accountJson.contains(
"passwordHash")) {
210 LOG_WARNING(
"Skipping invalid account entry (missing username or passwordHash)");
215 account.
username = accountJson[
"username"].get<std::string>();
216 account.
passwordHash = accountJson[
"passwordHash"].get<std::string>();
217 account.
createdAt = accountJson.value(
"createdAt", 0ULL);
218 account.
lastLogin = accountJson.value(
"lastLogin", 0ULL);
226 }
catch (
const json::exception &e) {
227 LOG_ERROR(
"Failed to parse accounts file: ", e.what());
228 LOG_WARNING(
"Starting with empty accounts database");
235 j[
"version"] =
"1.0";
236 j[
"accounts"] = json::array();
238 for (
const auto &[username, account] :
_accounts) {
240 accountJson[
"username"] = account.username;
241 accountJson[
"passwordHash"] = account.passwordHash;
242 accountJson[
"createdAt"] = account.createdAt;
243 accountJson[
"lastLogin"] = account.lastLogin;
244 accountJson[
"autoMatchmaking"] = account.autoMatchmaking;
246 j[
"accounts"].push_back(accountJson);
250 if (!file.is_open()) {
260 }
catch (
const json::exception &e) {
261 LOG_ERROR(
"Failed to save accounts: ", e.what());
268 LOG_WARNING(
"Cannot update auto-matchmaking: user '", username,
"' not found");
272 it->second.autoMatchmaking = enabled;
276 auto now = std::chrono::system_clock::now();
277 _lastSaveTime = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
280 LOG_INFO(
"✓ Auto-matchmaking ", enabled ?
"enabled" :
"disabled",
" for user '", username,
290 return it->second.autoMatchmaking;
Argon2id password hashing wrapper.
std::unordered_map< std::string, AccountData > _accounts
Map of username to account data.
std::unique_ptr< IPasswordHasher > _passwordHasher
Password hashing implementation.
bool validateToken(const std::string &token)
Validate a token.
bool getAutoMatchmaking(const std::string &username) const
Get auto-matchmaking preference for a user.
std::unordered_map< std::string, std::string > _activeTokens
Map of tokens to usernames.
void loadAccounts()
Load user accounts from JSON file.
std::unordered_set< std::string > _authenticatedUsers
Set of authenticated usernames.
static constexpr uint64_t SAVE_INTERVAL_SECONDS
Save every 60 seconds.
bool isUserAuthenticated(const std::string &username) const
Check if a user is authenticated.
std::string generateToken(const std::string &username)
Generate an authentication token for a user.
bool registerUser(const std::string &username, const std::string &password)
Register a new user account.
void saveAccounts()
Save user accounts to JSON file.
uint64_t _lastSaveTime
Timestamp of last save.
std::string _accountsFile
JSON file to store accounts.
bool _accountsDirty
Flag indicating unsaved changes.
bool updateAutoMatchmaking(const std::string &username, bool enabled)
Update auto-matchmaking preference for a user.
bool authenticate(const std::string &username, const std::string &password) override
Authenticate a user with username and password.
void revokeToken(const std::string &token)
Revoke a token.