|
@@ -0,0 +1,982 @@
|
|
|
|
|
+#include "user_manager.h"
|
|
|
|
|
+#include "jwt_auth.h"
|
|
|
|
|
+#include <nlohmann/json.hpp>
|
|
|
|
|
+#include <fstream>
|
|
|
|
|
+#include <filesystem>
|
|
|
|
|
+#include <sstream>
|
|
|
|
|
+#include <iomanip>
|
|
|
|
|
+#include <algorithm>
|
|
|
|
|
+#include <regex>
|
|
|
|
|
+#include <crypt.h>
|
|
|
|
|
+#include <unistd.h>
|
|
|
|
|
+#include <pwd.h>
|
|
|
|
|
+#include <shadow.h>
|
|
|
|
|
+#include <sys/types.h>
|
|
|
|
|
+#include <openssl/sha.h>
|
|
|
|
|
+
|
|
|
|
|
+using json = nlohmann::json;
|
|
|
|
|
+
|
|
|
|
|
+// Define static permission constants
|
|
|
|
|
+const std::string UserManager::Permissions::READ = "read";
|
|
|
|
|
+const std::string UserManager::Permissions::GENERATE = "generate";
|
|
|
|
|
+const std::string UserManager::Permissions::QUEUE_MANAGE = "queue_manage";
|
|
|
|
|
+const std::string UserManager::Permissions::MODEL_MANAGE = "model_manage";
|
|
|
|
|
+const std::string UserManager::Permissions::USER_MANAGE = "user_manage";
|
|
|
|
|
+const std::string UserManager::Permissions::ADMIN = "admin";
|
|
|
|
|
+
|
|
|
|
|
+UserManager::UserManager(const std::string& dataDir,
|
|
|
|
|
+ AuthMethod authMethod,
|
|
|
|
|
+ bool enableUnixAuth)
|
|
|
|
|
+ : m_dataDir(dataDir)
|
|
|
|
|
+ , m_authMethod(authMethod)
|
|
|
|
|
+ , m_unixAuthEnabled(enableUnixAuth)
|
|
|
|
|
+{
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+UserManager::~UserManager() {
|
|
|
|
|
+ shutdown();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::initialize() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Create data directory if it doesn't exist
|
|
|
|
|
+ std::filesystem::create_directories(m_dataDir);
|
|
|
|
|
+
|
|
|
|
|
+ // Load existing user data
|
|
|
|
|
+ if (!loadUserData()) {
|
|
|
|
|
+ // Create default admin user if no users exist
|
|
|
|
|
+ if (m_users.empty()) {
|
|
|
|
|
+ auto [success, adminId] = createUser("admin", "admin123", "admin@localhost", UserRole::ADMIN, "system");
|
|
|
|
|
+ if (!success) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Load existing API key data
|
|
|
|
|
+ loadApiKeyData();
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void UserManager::shutdown() {
|
|
|
|
|
+ // Save data before shutdown
|
|
|
|
|
+ saveUserData();
|
|
|
|
|
+ saveApiKeyData();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+AuthResult UserManager::authenticateUser(const std::string& username, const std::string& password) {
|
|
|
|
|
+ AuthResult result;
|
|
|
|
|
+ result.success = false;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Find user by username
|
|
|
|
|
+ auto it = m_users.find(username);
|
|
|
|
|
+ if (it == m_users.end()) {
|
|
|
|
|
+ result.errorMessage = "User not found";
|
|
|
|
|
+ result.errorCode = "USER_NOT_FOUND";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const UserInfo& user = it->second;
|
|
|
|
|
+
|
|
|
|
|
+ // Check if user is active
|
|
|
|
|
+ if (!user.active) {
|
|
|
|
|
+ result.errorMessage = "User account is disabled";
|
|
|
|
|
+ result.errorCode = "ACCOUNT_DISABLED";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Verify password
|
|
|
|
|
+ if (!verifyPassword(password, user.passwordHash)) {
|
|
|
|
|
+ result.errorMessage = "Invalid password";
|
|
|
|
|
+ result.errorCode = "INVALID_PASSWORD";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Authentication successful
|
|
|
|
|
+ result.success = true;
|
|
|
|
|
+ result.userId = user.id;
|
|
|
|
|
+ result.username = user.username;
|
|
|
|
|
+ result.role = user.role;
|
|
|
|
|
+ result.permissions = user.permissions;
|
|
|
|
|
+
|
|
|
|
|
+ // Update last login time
|
|
|
|
|
+ m_users[username].lastLoginAt = getCurrentTimestamp();
|
|
|
|
|
+ saveUserData();
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ result.errorMessage = "Authentication failed: " + std::string(e.what());
|
|
|
|
|
+ result.errorCode = "AUTH_ERROR";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+AuthResult UserManager::authenticateUnix(const std::string& username) {
|
|
|
|
|
+ AuthResult result;
|
|
|
|
|
+ result.success = false;
|
|
|
|
|
+
|
|
|
|
|
+ if (!m_unixAuthEnabled) {
|
|
|
|
|
+ result.errorMessage = "Unix authentication is disabled";
|
|
|
|
|
+ result.errorCode = "UNIX_AUTH_DISABLED";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Get user information from system
|
|
|
|
|
+ struct passwd* pw = getpwnam(username.c_str());
|
|
|
|
|
+ if (!pw) {
|
|
|
|
|
+ result.errorMessage = "Unix user not found";
|
|
|
|
|
+ result.errorCode = "UNIX_USER_NOT_FOUND";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if user exists in our system or create guest user
|
|
|
|
|
+ UserInfo user;
|
|
|
|
|
+ auto it = m_users.find(username);
|
|
|
|
|
+ if (it != m_users.end()) {
|
|
|
|
|
+ user = it->second;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Create guest user for Unix authentication
|
|
|
|
|
+ user.id = generateUserId();
|
|
|
|
|
+ user.username = username;
|
|
|
|
|
+ user.email = username + "@localhost";
|
|
|
|
|
+ user.role = roleToString(UserRole::USER);
|
|
|
|
|
+ user.permissions = getDefaultPermissions(UserRole::USER);
|
|
|
|
|
+ user.active = true;
|
|
|
|
|
+ user.createdAt = getCurrentTimestamp();
|
|
|
|
|
+ user.createdBy = "system";
|
|
|
|
|
+
|
|
|
|
|
+ m_users[username] = user;
|
|
|
|
|
+ saveUserData();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Authentication successful
|
|
|
|
|
+ result.success = true;
|
|
|
|
|
+ result.userId = user.id;
|
|
|
|
|
+ result.username = user.username;
|
|
|
|
|
+ result.role = user.role;
|
|
|
|
|
+ result.permissions = user.permissions;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ result.errorMessage = "Unix authentication failed: " + std::string(e.what());
|
|
|
|
|
+ result.errorCode = "UNIX_AUTH_ERROR";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+AuthResult UserManager::authenticateApiKey(const std::string& apiKey) {
|
|
|
|
|
+ AuthResult result;
|
|
|
|
|
+ result.success = false;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Hash the API key to compare with stored hashes
|
|
|
|
|
+ std::string keyHash = hashApiKey(apiKey);
|
|
|
|
|
+
|
|
|
|
|
+ auto it = m_apiKeyMap.find(keyHash);
|
|
|
|
|
+ if (it == m_apiKeyMap.end()) {
|
|
|
|
|
+ result.errorMessage = "Invalid API key";
|
|
|
|
|
+ result.errorCode = "INVALID_API_KEY";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const std::string& keyId = it->second;
|
|
|
|
|
+ const ApiKeyInfo& keyInfo = m_apiKeys[keyId];
|
|
|
|
|
+
|
|
|
|
|
+ // Check if key is active
|
|
|
|
|
+ if (!keyInfo.active) {
|
|
|
|
|
+ result.errorMessage = "API key is disabled";
|
|
|
|
|
+ result.errorCode = "API_KEY_DISABLED";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check expiration
|
|
|
|
|
+ if (keyInfo.expiresAt > 0 && getCurrentTimestamp() >= keyInfo.expiresAt) {
|
|
|
|
|
+ result.errorMessage = "API key has expired";
|
|
|
|
|
+ result.errorCode = "API_KEY_EXPIRED";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Get user information
|
|
|
|
|
+ auto userIt = m_users.find(keyInfo.userId);
|
|
|
|
|
+ if (userIt == m_users.end()) {
|
|
|
|
|
+ result.errorMessage = "API key owner not found";
|
|
|
|
|
+ result.errorCode = "USER_NOT_FOUND";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const UserInfo& user = userIt->second;
|
|
|
|
|
+ if (!user.active) {
|
|
|
|
|
+ result.errorMessage = "API key owner account is disabled";
|
|
|
|
|
+ result.errorCode = "ACCOUNT_DISABLED";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Authentication successful
|
|
|
|
|
+ result.success = true;
|
|
|
|
|
+ result.userId = user.id;
|
|
|
|
|
+ result.username = user.username;
|
|
|
|
|
+ result.role = user.role;
|
|
|
|
|
+ result.permissions = keyInfo.permissions.empty() ? user.permissions : keyInfo.permissions;
|
|
|
|
|
+
|
|
|
|
|
+ // Update last used timestamp
|
|
|
|
|
+ m_apiKeys[keyId].lastUsedAt = getCurrentTimestamp();
|
|
|
|
|
+ saveApiKeyData();
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ result.errorMessage = "API key authentication failed: " + std::string(e.what());
|
|
|
|
|
+ result.errorCode = "API_KEY_AUTH_ERROR";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::pair<bool, std::string> UserManager::createUser(const std::string& username,
|
|
|
|
|
+ const std::string& password,
|
|
|
|
|
+ const std::string& email,
|
|
|
|
|
+ UserRole role,
|
|
|
|
|
+ const std::string& createdBy) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Validate inputs
|
|
|
|
|
+ if (!validateUsername(username)) {
|
|
|
|
|
+ return {false, "Invalid username format"};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!validatePassword(password)) {
|
|
|
|
|
+ return {false, "Password does not meet requirements"};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!validateEmail(email)) {
|
|
|
|
|
+ return {false, "Invalid email format"};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if user already exists
|
|
|
|
|
+ if (m_users.find(username) != m_users.end()) {
|
|
|
|
|
+ return {false, "User already exists"};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Create user
|
|
|
|
|
+ UserInfo user;
|
|
|
|
|
+ user.id = generateUserId();
|
|
|
|
|
+ user.username = username;
|
|
|
|
|
+ user.email = email;
|
|
|
|
|
+ user.passwordHash = hashPassword(password);
|
|
|
|
|
+ user.role = roleToString(role);
|
|
|
|
|
+ user.permissions = getDefaultPermissions(role);
|
|
|
|
|
+ user.active = true;
|
|
|
|
|
+ user.createdAt = getCurrentTimestamp();
|
|
|
|
|
+ user.passwordChangedAt = getCurrentTimestamp();
|
|
|
|
|
+ user.createdBy = createdBy;
|
|
|
|
|
+
|
|
|
|
|
+ m_users[username] = user;
|
|
|
|
|
+ saveUserData();
|
|
|
|
|
+
|
|
|
|
|
+ return {true, user.id};
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ return {false, "Failed to create user: " + std::string(e.what())};
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::pair<bool, std::string> UserManager::updateUser(const std::string& userId,
|
|
|
|
|
+ const std::map<std::string, std::string>& updates) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Find user by ID
|
|
|
|
|
+ std::string username;
|
|
|
|
|
+ for (const auto& pair : m_users) {
|
|
|
|
|
+ if (pair.second.id == userId) {
|
|
|
|
|
+ username = pair.first;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (username.empty()) {
|
|
|
|
|
+ return {false, "User not found"};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ UserInfo& user = m_users[username];
|
|
|
|
|
+
|
|
|
|
|
+ // Update allowed fields
|
|
|
|
|
+ for (const auto& update : updates) {
|
|
|
|
|
+ const std::string& field = update.first;
|
|
|
|
|
+ const std::string& value = update.second;
|
|
|
|
|
+
|
|
|
|
|
+ if (field == "email") {
|
|
|
|
|
+ if (!validateEmail(value)) {
|
|
|
|
|
+ return {false, "Invalid email format"};
|
|
|
|
|
+ }
|
|
|
|
|
+ user.email = value;
|
|
|
|
|
+ } else if (field == "role") {
|
|
|
|
|
+ user.role = value;
|
|
|
|
|
+ user.permissions = getDefaultPermissions(stringToRole(value));
|
|
|
|
|
+ } else if (field == "active") {
|
|
|
|
|
+ user.active = (value == "true" || value == "1");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ saveUserData();
|
|
|
|
|
+ return {true, "User updated successfully"};
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ return {false, "Failed to update user: " + std::string(e.what())};
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::pair<bool, std::string> UserManager::deleteUser(const std::string& userId,
|
|
|
|
|
+ const std::string& requestingUserId) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Find user by ID
|
|
|
|
|
+ std::string username;
|
|
|
|
|
+ for (const auto& pair : m_users) {
|
|
|
|
|
+ if (pair.second.id == userId) {
|
|
|
|
|
+ username = pair.first;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (username.empty()) {
|
|
|
|
|
+ return {false, "User not found"};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check permissions
|
|
|
|
|
+ if (!canManageUser(requestingUserId, userId)) {
|
|
|
|
|
+ return {false, "Insufficient permissions to delete user"};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Delete user's API keys
|
|
|
|
|
+ auto it = m_apiKeys.begin();
|
|
|
|
|
+ while (it != m_apiKeys.end()) {
|
|
|
|
|
+ if (it->second.userId == userId) {
|
|
|
|
|
+ m_apiKeyMap.erase(it->second.keyHash);
|
|
|
|
|
+ it = m_apiKeys.erase(it);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ++it;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Delete user
|
|
|
|
|
+ m_users.erase(username);
|
|
|
|
|
+ saveUserData();
|
|
|
|
|
+ saveApiKeyData();
|
|
|
|
|
+
|
|
|
|
|
+ return {true, "User deleted successfully"};
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ return {false, "Failed to delete user: " + std::string(e.what())};
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::pair<bool, std::string> UserManager::changePassword(const std::string& userId,
|
|
|
|
|
+ const std::string& oldPassword,
|
|
|
|
|
+ const std::string& newPassword,
|
|
|
|
|
+ const std::string& requestingUserId) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Find user by ID
|
|
|
|
|
+ std::string username;
|
|
|
|
|
+ for (const auto& pair : m_users) {
|
|
|
|
|
+ if (pair.second.id == userId) {
|
|
|
|
|
+ username = pair.first;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (username.empty()) {
|
|
|
|
|
+ return {false, "User not found"};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ UserInfo& user = m_users[username];
|
|
|
|
|
+
|
|
|
|
|
+ // Check permissions (admin can change without old password)
|
|
|
|
|
+ if (requestingUserId != userId) {
|
|
|
|
|
+ if (!canManageUser(requestingUserId, userId)) {
|
|
|
|
|
+ return {false, "Insufficient permissions to change password"};
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // User changing own password - verify old password
|
|
|
|
|
+ if (!verifyPassword(oldPassword, user.passwordHash)) {
|
|
|
|
|
+ return {false, "Current password is incorrect"};
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Validate new password
|
|
|
|
|
+ if (!validatePassword(newPassword)) {
|
|
|
|
|
+ return {false, "New password does not meet requirements"};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Update password
|
|
|
|
|
+ user.passwordHash = hashPassword(newPassword);
|
|
|
|
|
+ user.passwordChangedAt = getCurrentTimestamp();
|
|
|
|
|
+ saveUserData();
|
|
|
|
|
+
|
|
|
|
|
+ return {true, "Password changed successfully"};
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ return {false, "Failed to change password: " + std::string(e.what())};
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+UserInfo UserManager::getUserInfo(const std::string& userId) {
|
|
|
|
|
+ for (const auto& pair : m_users) {
|
|
|
|
|
+ if (pair.second.id == userId) {
|
|
|
|
|
+ return pair.second;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return UserInfo{};
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+UserInfo UserManager::getUserInfoByUsername(const std::string& username) {
|
|
|
|
|
+ auto it = m_users.find(username);
|
|
|
|
|
+ if (it != m_users.end()) {
|
|
|
|
|
+ return it->second;
|
|
|
|
|
+ }
|
|
|
|
|
+ return UserInfo{};
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<UserInfo> UserManager::listUsers(const std::string& requestingUserId) {
|
|
|
|
|
+ std::vector<UserInfo> users;
|
|
|
|
|
+
|
|
|
|
|
+ // Check if requester is admin
|
|
|
|
|
+ UserInfo requester = getUserInfo(requestingUserId);
|
|
|
|
|
+ bool isAdmin = (requester.role == roleToString(UserRole::ADMIN));
|
|
|
|
|
+
|
|
|
|
|
+ for (const auto& pair : m_users) {
|
|
|
|
|
+ const UserInfo& user = pair.second;
|
|
|
|
|
+
|
|
|
|
|
+ // Non-admins can only see themselves
|
|
|
|
|
+ if (!isAdmin && user.id != requestingUserId) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Don't include sensitive information for non-admins
|
|
|
|
|
+ UserInfo userInfo = user;
|
|
|
|
|
+ if (!isAdmin) {
|
|
|
|
|
+ userInfo.passwordHash = "";
|
|
|
|
|
+ userInfo.apiKeys.clear();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ users.push_back(userInfo);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return users;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::pair<bool, std::string> UserManager::createApiKey(const std::string& userId,
|
|
|
|
|
+ const std::string& name,
|
|
|
|
|
+ const std::vector<std::string>& permissions,
|
|
|
|
|
+ int64_t expiresAt,
|
|
|
|
|
+ const std::string& createdBy) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Check if user exists
|
|
|
|
|
+ bool userExists = false;
|
|
|
|
|
+ for (const auto& pair : m_users) {
|
|
|
|
|
+ if (pair.second.id == userId) {
|
|
|
|
|
+ userExists = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!userExists) {
|
|
|
|
|
+ return {false, "User not found"};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Generate API key
|
|
|
|
|
+ std::string apiKey = JWTAuth::generateApiKey(32);
|
|
|
|
|
+ std::string keyId = generateKeyId();
|
|
|
|
|
+ std::string keyHash = hashApiKey(apiKey);
|
|
|
|
|
+
|
|
|
|
|
+ // Create API key info
|
|
|
|
|
+ ApiKeyInfo keyInfo;
|
|
|
|
|
+ keyInfo.keyId = keyId;
|
|
|
|
|
+ keyInfo.keyHash = keyHash;
|
|
|
|
|
+ keyInfo.name = name;
|
|
|
|
|
+ keyInfo.userId = userId;
|
|
|
|
|
+ keyInfo.permissions = permissions;
|
|
|
|
|
+ keyInfo.active = true;
|
|
|
|
|
+ keyInfo.createdAt = getCurrentTimestamp();
|
|
|
|
|
+ keyInfo.lastUsedAt = 0;
|
|
|
|
|
+ keyInfo.expiresAt = expiresAt;
|
|
|
|
|
+ keyInfo.createdBy = createdBy;
|
|
|
|
|
+
|
|
|
|
|
+ m_apiKeys[keyId] = keyInfo;
|
|
|
|
|
+ m_apiKeyMap[keyHash] = keyId;
|
|
|
|
|
+ saveApiKeyData();
|
|
|
|
|
+
|
|
|
|
|
+ return {true, apiKey};
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ return {false, "Failed to create API key: " + std::string(e.what())};
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::pair<bool, std::string> UserManager::revokeApiKey(const std::string& keyId,
|
|
|
|
|
+ const std::string& requestingUserId) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ auto it = m_apiKeys.find(keyId);
|
|
|
|
|
+ if (it == m_apiKeys.end()) {
|
|
|
|
|
+ return {false, "API key not found"};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const ApiKeyInfo& keyInfo = it->second;
|
|
|
|
|
+
|
|
|
|
|
+ // Check permissions
|
|
|
|
|
+ if (keyInfo.userId != requestingUserId) {
|
|
|
|
|
+ if (!canManageUser(requestingUserId, keyInfo.userId)) {
|
|
|
|
|
+ return {false, "Insufficient permissions to revoke API key"};
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Remove API key
|
|
|
|
|
+ m_apiKeyMap.erase(keyInfo.keyHash);
|
|
|
|
|
+ m_apiKeys.erase(it);
|
|
|
|
|
+ saveApiKeyData();
|
|
|
|
|
+
|
|
|
|
|
+ return {true, "API key revoked successfully"};
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ return {false, "Failed to revoke API key: " + std::string(e.what())};
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<ApiKeyInfo> UserManager::listApiKeys(const std::string& userId,
|
|
|
|
|
+ const std::string& requestingUserId) {
|
|
|
|
|
+ std::vector<ApiKeyInfo> apiKeys;
|
|
|
|
|
+
|
|
|
|
|
+ // Check if requester is admin or owner
|
|
|
|
|
+ UserInfo requester = getUserInfo(requestingUserId);
|
|
|
|
|
+ bool isAdmin = (requester.role == roleToString(UserRole::ADMIN));
|
|
|
|
|
+ bool isOwner = (requestingUserId == userId);
|
|
|
|
|
+
|
|
|
|
|
+ for (const auto& pair : m_apiKeys) {
|
|
|
|
|
+ const ApiKeyInfo& keyInfo = pair.second;
|
|
|
|
|
+
|
|
|
|
|
+ // Filter by user ID if specified
|
|
|
|
|
+ if (!userId.empty() && keyInfo.userId != userId) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check permissions
|
|
|
|
|
+ if (!isAdmin && keyInfo.userId != requestingUserId) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Don't include hash for non-owners
|
|
|
|
|
+ ApiKeyInfo keyInfoCopy = keyInfo;
|
|
|
|
|
+ if (!isOwner && !isAdmin) {
|
|
|
|
|
+ keyInfoCopy.keyHash = "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ apiKeys.push_back(keyInfoCopy);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return apiKeys;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+ApiKeyInfo UserManager::getApiKeyInfo(const std::string& keyId,
|
|
|
|
|
+ const std::string& requestingUserId) {
|
|
|
|
|
+ auto it = m_apiKeys.find(keyId);
|
|
|
|
|
+ if (it == m_apiKeys.end()) {
|
|
|
|
|
+ return ApiKeyInfo{};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const ApiKeyInfo& keyInfo = it->second;
|
|
|
|
|
+
|
|
|
|
|
+ // Check permissions
|
|
|
|
|
+ UserInfo requester = getUserInfo(requestingUserId);
|
|
|
|
|
+ bool isAdmin = (requester.role == roleToString(UserRole::ADMIN));
|
|
|
|
|
+ bool isOwner = (keyInfo.userId == requestingUserId);
|
|
|
|
|
+
|
|
|
|
|
+ if (!isAdmin && !isOwner) {
|
|
|
|
|
+ return ApiKeyInfo{};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Don't include hash for non-owners
|
|
|
|
|
+ ApiKeyInfo keyInfoCopy = keyInfo;
|
|
|
|
|
+ if (!isOwner) {
|
|
|
|
|
+ keyInfoCopy.keyHash = "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return keyInfoCopy;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void UserManager::updateApiKeyLastUsed(const std::string& keyId) {
|
|
|
|
|
+ auto it = m_apiKeys.find(keyId);
|
|
|
|
|
+ if (it != m_apiKeys.end()) {
|
|
|
|
|
+ it->second.lastUsedAt = getCurrentTimestamp();
|
|
|
|
|
+ saveApiKeyData();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::hasPermission(const std::string& userId, const std::string& permission) {
|
|
|
|
|
+ UserInfo user = getUserInfo(userId);
|
|
|
|
|
+ if (user.id.empty()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return JWTAuth::hasPermission(user.permissions, permission);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::hasAnyPermission(const std::string& userId,
|
|
|
|
|
+ const std::vector<std::string>& permissions) {
|
|
|
|
|
+ UserInfo user = getUserInfo(userId);
|
|
|
|
|
+ if (user.id.empty()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return JWTAuth::hasAnyPermission(user.permissions, permissions);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::string UserManager::roleToString(UserRole role) {
|
|
|
|
|
+ switch (role) {
|
|
|
|
|
+ case UserRole::GUEST: return "guest";
|
|
|
|
|
+ case UserRole::USER: return "user";
|
|
|
|
|
+ case UserRole::ADMIN: return "admin";
|
|
|
|
|
+ case UserRole::SERVICE: return "service";
|
|
|
|
|
+ default: return "unknown";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+UserManager::UserRole UserManager::stringToRole(const std::string& roleStr) {
|
|
|
|
|
+ if (roleStr == "guest") return UserRole::GUEST;
|
|
|
|
|
+ if (roleStr == "user") return UserRole::USER;
|
|
|
|
|
+ if (roleStr == "admin") return UserRole::ADMIN;
|
|
|
|
|
+ if (roleStr == "service") return UserRole::SERVICE;
|
|
|
|
|
+ return UserRole::USER; // Default
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<std::string> UserManager::getDefaultPermissions(UserRole role) {
|
|
|
|
|
+ switch (role) {
|
|
|
|
|
+ case UserRole::GUEST:
|
|
|
|
|
+ return {Permissions::READ};
|
|
|
|
|
+ case UserRole::USER:
|
|
|
|
|
+ return {Permissions::READ, Permissions::GENERATE};
|
|
|
|
|
+ case UserRole::ADMIN:
|
|
|
|
|
+ return {Permissions::READ, Permissions::GENERATE, Permissions::QUEUE_MANAGE,
|
|
|
|
|
+ Permissions::MODEL_MANAGE, Permissions::USER_MANAGE, Permissions::ADMIN};
|
|
|
|
|
+ case UserRole::SERVICE:
|
|
|
|
|
+ return {Permissions::READ, Permissions::GENERATE, Permissions::QUEUE_MANAGE};
|
|
|
|
|
+ default:
|
|
|
|
|
+ return {};
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void UserManager::setAuthMethod(AuthMethod method) {
|
|
|
|
|
+ m_authMethod = method;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+UserManager::AuthMethod UserManager::getAuthMethod() const {
|
|
|
|
|
+ return m_authMethod;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void UserManager::setUnixAuthEnabled(bool enable) {
|
|
|
|
|
+ m_unixAuthEnabled = enable;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::isUnixAuthEnabled() const {
|
|
|
|
|
+ return m_unixAuthEnabled;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::map<std::string, int> UserManager::getStatistics() {
|
|
|
|
|
+ std::map<std::string, int> stats;
|
|
|
|
|
+
|
|
|
|
|
+ stats["total_users"] = m_users.size();
|
|
|
|
|
+ stats["active_users"] = 0;
|
|
|
|
|
+ stats["admin_users"] = 0;
|
|
|
|
|
+ stats["total_api_keys"] = m_apiKeys.size();
|
|
|
|
|
+ stats["active_api_keys"] = 0;
|
|
|
|
|
+ stats["expired_api_keys"] = 0;
|
|
|
|
|
+
|
|
|
|
|
+ int64_t currentTime = getCurrentTimestamp();
|
|
|
|
|
+
|
|
|
|
|
+ for (const auto& pair : m_users) {
|
|
|
|
|
+ const UserInfo& user = pair.second;
|
|
|
|
|
+ if (user.active) {
|
|
|
|
|
+ stats["active_users"]++;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (user.role == roleToString(UserRole::ADMIN)) {
|
|
|
|
|
+ stats["admin_users"]++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (const auto& pair : m_apiKeys) {
|
|
|
|
|
+ const ApiKeyInfo& keyInfo = pair.second;
|
|
|
|
|
+ if (keyInfo.active) {
|
|
|
|
|
+ stats["active_api_keys"]++;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (keyInfo.expiresAt > 0 && currentTime >= keyInfo.expiresAt) {
|
|
|
|
|
+ stats["expired_api_keys"]++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return stats;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::string UserManager::hashPassword(const std::string& password) {
|
|
|
|
|
+ // Simple SHA256 hash for now (in production, use proper password hashing like bcrypt/argon2)
|
|
|
|
|
+ unsigned char hash[SHA256_DIGEST_LENGTH];
|
|
|
|
|
+ SHA256((unsigned char*)password.c_str(), password.length(), hash);
|
|
|
|
|
+
|
|
|
|
|
+ std::stringstream ss;
|
|
|
|
|
+ for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
|
|
|
|
|
+ ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return "sha256:" + ss.str();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::verifyPassword(const std::string& password, const std::string& storedHash) {
|
|
|
|
|
+ // Simple SHA256 verification (in production, use proper password hashing)
|
|
|
|
|
+ if (storedHash.substr(0, 7) != "sha256:") {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ unsigned char hash[SHA256_DIGEST_LENGTH];
|
|
|
|
|
+ SHA256((unsigned char*)password.c_str(), password.length(), hash);
|
|
|
|
|
+
|
|
|
|
|
+ std::stringstream ss;
|
|
|
|
|
+ for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
|
|
|
|
|
+ ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return "sha256:" + ss.str() == storedHash;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::string UserManager::hashApiKey(const std::string& apiKey) {
|
|
|
|
|
+ // Use SHA256 for API key hashing
|
|
|
|
|
+ unsigned char hash[SHA256_DIGEST_LENGTH];
|
|
|
|
|
+ SHA256((unsigned char*)apiKey.c_str(), apiKey.length(), hash);
|
|
|
|
|
+
|
|
|
|
|
+ std::stringstream ss;
|
|
|
|
|
+ for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
|
|
|
|
|
+ ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ss.str();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::string UserManager::generateUserId() {
|
|
|
|
|
+ return "user_" + std::to_string(getCurrentTimestamp()) + "_" +
|
|
|
|
|
+ std::to_string(rand() % 10000);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::string UserManager::generateKeyId() {
|
|
|
|
|
+ return "key_" + std::to_string(getCurrentTimestamp()) + "_" +
|
|
|
|
|
+ std::to_string(rand() % 10000);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::saveUserData() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ json usersJson = json::object();
|
|
|
|
|
+
|
|
|
|
|
+ for (const auto& pair : m_users) {
|
|
|
|
|
+ const UserInfo& user = pair.second;
|
|
|
|
|
+ json userJson = {
|
|
|
|
|
+ {"id", user.id},
|
|
|
|
|
+ {"username", user.username},
|
|
|
|
|
+ {"email", user.email},
|
|
|
|
|
+ {"password_hash", user.passwordHash},
|
|
|
|
|
+ {"role", user.role},
|
|
|
|
|
+ {"permissions", user.permissions},
|
|
|
|
|
+ {"api_keys", user.apiKeys},
|
|
|
|
|
+ {"active", user.active},
|
|
|
|
|
+ {"created_at", user.createdAt},
|
|
|
|
|
+ {"last_login_at", user.lastLoginAt},
|
|
|
|
|
+ {"password_changed_at", user.passwordChangedAt},
|
|
|
|
|
+ {"created_by", user.createdBy}
|
|
|
|
|
+ };
|
|
|
|
|
+ usersJson[user.username] = userJson;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string filename = m_dataDir + "/users.json";
|
|
|
|
|
+ std::ofstream file(filename);
|
|
|
|
|
+ if (!file.is_open()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ file << usersJson.dump(2);
|
|
|
|
|
+ file.close();
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::loadUserData() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ std::string filename = m_dataDir + "/users.json";
|
|
|
|
|
+ std::ifstream file(filename);
|
|
|
|
|
+ if (!file.is_open()) {
|
|
|
|
|
+ return false; // File doesn't exist is OK for first run
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ json usersJson;
|
|
|
|
|
+ file >> usersJson;
|
|
|
|
|
+ file.close();
|
|
|
|
|
+
|
|
|
|
|
+ m_users.clear();
|
|
|
|
|
+
|
|
|
|
|
+ for (auto& item : usersJson.items()) {
|
|
|
|
|
+ const std::string& username = item.key();
|
|
|
|
|
+ json& userJson = item.value();
|
|
|
|
|
+
|
|
|
|
|
+ UserInfo user;
|
|
|
|
|
+ user.id = userJson.value("id", "");
|
|
|
|
|
+ user.username = userJson.value("username", username);
|
|
|
|
|
+ user.email = userJson.value("email", "");
|
|
|
|
|
+ user.passwordHash = userJson.value("password_hash", "");
|
|
|
|
|
+ user.role = userJson.value("role", "user");
|
|
|
|
|
+ user.permissions = userJson.value("permissions", std::vector<std::string>{});
|
|
|
|
|
+ user.apiKeys = userJson.value("api_keys", std::vector<std::string>{});
|
|
|
|
|
+ user.active = userJson.value("active", true);
|
|
|
|
|
+ user.createdAt = userJson.value("created_at", 0);
|
|
|
|
|
+ user.lastLoginAt = userJson.value("last_login_at", 0);
|
|
|
|
|
+ user.passwordChangedAt = userJson.value("password_changed_at", 0);
|
|
|
|
|
+ user.createdBy = userJson.value("created_by", "system");
|
|
|
|
|
+
|
|
|
|
|
+ m_users[username] = user;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::saveApiKeyData() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ json apiKeysJson = json::object();
|
|
|
|
|
+
|
|
|
|
|
+ for (const auto& pair : m_apiKeys) {
|
|
|
|
|
+ const ApiKeyInfo& keyInfo = pair.second;
|
|
|
|
|
+ json keyJson = {
|
|
|
|
|
+ {"key_id", keyInfo.keyId},
|
|
|
|
|
+ {"key_hash", keyInfo.keyHash},
|
|
|
|
|
+ {"name", keyInfo.name},
|
|
|
|
|
+ {"user_id", keyInfo.userId},
|
|
|
|
|
+ {"permissions", keyInfo.permissions},
|
|
|
|
|
+ {"active", keyInfo.active},
|
|
|
|
|
+ {"created_at", keyInfo.createdAt},
|
|
|
|
|
+ {"last_used_at", keyInfo.lastUsedAt},
|
|
|
|
|
+ {"expires_at", keyInfo.expiresAt},
|
|
|
|
|
+ {"created_by", keyInfo.createdBy}
|
|
|
|
|
+ };
|
|
|
|
|
+ apiKeysJson[keyInfo.keyId] = keyJson;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string filename = m_dataDir + "/api_keys.json";
|
|
|
|
|
+ std::ofstream file(filename);
|
|
|
|
|
+ if (!file.is_open()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ file << apiKeysJson.dump(2);
|
|
|
|
|
+ file.close();
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::loadApiKeyData() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ std::string filename = m_dataDir + "/api_keys.json";
|
|
|
|
|
+ std::ifstream file(filename);
|
|
|
|
|
+ if (!file.is_open()) {
|
|
|
|
|
+ return false; // File doesn't exist is OK for first run
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ json apiKeysJson;
|
|
|
|
|
+ file >> apiKeysJson;
|
|
|
|
|
+ file.close();
|
|
|
|
|
+
|
|
|
|
|
+ m_apiKeys.clear();
|
|
|
|
|
+ m_apiKeyMap.clear();
|
|
|
|
|
+
|
|
|
|
|
+ for (auto& item : apiKeysJson.items()) {
|
|
|
|
|
+ const std::string& keyId = item.key();
|
|
|
|
|
+ json& keyJson = item.value();
|
|
|
|
|
+
|
|
|
|
|
+ ApiKeyInfo keyInfo;
|
|
|
|
|
+ keyInfo.keyId = keyJson.value("key_id", keyId);
|
|
|
|
|
+ keyInfo.keyHash = keyJson.value("key_hash", "");
|
|
|
|
|
+ keyInfo.name = keyJson.value("name", "");
|
|
|
|
|
+ keyInfo.userId = keyJson.value("user_id", "");
|
|
|
|
|
+ keyInfo.permissions = keyJson.value("permissions", std::vector<std::string>{});
|
|
|
|
|
+ keyInfo.active = keyJson.value("active", true);
|
|
|
|
|
+ keyInfo.createdAt = keyJson.value("created_at", 0);
|
|
|
|
|
+ keyInfo.lastUsedAt = keyJson.value("last_used_at", 0);
|
|
|
|
|
+ keyInfo.expiresAt = keyJson.value("expires_at", 0);
|
|
|
|
|
+ keyInfo.createdBy = keyJson.value("created_by", "system");
|
|
|
|
|
+
|
|
|
|
|
+ m_apiKeys[keyId] = keyInfo;
|
|
|
|
|
+ m_apiKeyMap[keyInfo.keyHash] = keyId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int64_t UserManager::getCurrentTimestamp() {
|
|
|
|
|
+ return std::chrono::duration_cast<std::chrono::seconds>(
|
|
|
|
|
+ std::chrono::system_clock::now().time_since_epoch()).count();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::validateUsername(const std::string& username) {
|
|
|
|
|
+ if (username.length() < 3 || username.length() > 32) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Username should contain only alphanumeric characters, underscores, and hyphens
|
|
|
|
|
+ std::regex pattern("^[a-zA-Z0-9_-]+$");
|
|
|
|
|
+ return std::regex_match(username, pattern);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::validatePassword(const std::string& password) {
|
|
|
|
|
+ if (password.length() < 8 || password.length() > 128) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Password should contain at least one letter and one digit
|
|
|
|
|
+ bool hasLetter = false;
|
|
|
|
|
+ bool hasDigit = false;
|
|
|
|
|
+
|
|
|
|
|
+ for (char c : password) {
|
|
|
|
|
+ if (std::isalpha(c)) hasLetter = true;
|
|
|
|
|
+ if (std::isdigit(c)) hasDigit = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return hasLetter && hasDigit;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::validateEmail(const std::string& email) {
|
|
|
|
|
+ if (email.length() < 5 || email.length() > 254) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Basic email validation
|
|
|
|
|
+ std::regex pattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
|
|
|
|
|
+ return std::regex_match(email, pattern);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool UserManager::canManageUser(const std::string& requestingUserId, const std::string& targetUserId) {
|
|
|
|
|
+ // Users can always manage themselves
|
|
|
|
|
+ if (requestingUserId == targetUserId) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if requester is admin
|
|
|
|
|
+ UserInfo requester = getUserInfo(requestingUserId);
|
|
|
|
|
+ return requester.role == roleToString(UserRole::ADMIN);
|
|
|
|
|
+}
|