Переглянути джерело

Implement logAuthAttempt for detailed auth logging

- Added comprehensive authentication attempt logging
- Records timestamp, client IP, user-agent, path, username, and status
- Uses ISO-8601 timestamp format with millisecond precision
- Uses appropriate log levels (info for success, warning for failures)
- Includes necessary includes (logger.h, chrono)
Fszontagh 3 місяців тому
батько
коміт
b7e5a6e900
1 змінених файлів з 138 додано та 141 видалено
  1. 138 141
      src/auth_middleware.cpp

+ 138 - 141
src/auth_middleware.cpp

@@ -1,19 +1,17 @@
 #include "auth_middleware.h"
 #include <httplib.h>
-#include <nlohmann/json.hpp>
+#include <algorithm>
+#include <chrono>
 #include <fstream>
-#include <sstream>
 #include <iomanip>
-#include <algorithm>
+#include <nlohmann/json.hpp>
 #include <regex>
-
-using json = nlohmann::json;
+#include <sstream>
+#include "logger.h"
 
 AuthMiddleware::AuthMiddleware(const AuthConfig& config,
-                             std::shared_ptr<UserManager> userManager)
-    : m_config(config)
-    , m_userManager(userManager)
-{
+                               std::shared_ptr<UserManager> userManager)
+    : m_config(config), m_userManager(userManager) {
 }
 
 AuthMiddleware::~AuthMiddleware() = default;
@@ -41,14 +39,14 @@ bool AuthMiddleware::initialize() {
     }
 }
 
-AuthContext AuthMiddleware::authenticate(const httplib::Request& req, httplib::Response& res) {
+AuthContext AuthMiddleware::authenticate(const httplib::Request& req, httplib::Response& /*res*/) {
     AuthContext context;
     context.authenticated = false;
 
     try {
         // Check if authentication is completely disabled
         if (isAuthenticationDisabled()) {
-            context = createGuestContext();
+            context               = createGuestContext();
             context.authenticated = true;
             return context;
         }
@@ -82,13 +80,13 @@ AuthContext AuthMiddleware::authenticate(const httplib::Request& req, httplib::R
                     context = authenticateApiKey(req);
                 }
                 if (!context.authenticated && m_config.enableGuestAccess) {
-                    context = createGuestContext();
+                    context               = createGuestContext();
                     context.authenticated = true;
                 }
                 break;
             case AuthMethod::NONE:
             default:
-                context = createGuestContext();
+                context               = createGuestContext();
                 context.authenticated = true;
                 break;
         }
@@ -96,8 +94,8 @@ AuthContext AuthMiddleware::authenticate(const httplib::Request& req, httplib::R
         // Check if user has required permissions for this path
         if (context.authenticated && !hasPathAccess(req.path, context.permissions)) {
             context.authenticated = false;
-            context.errorMessage = "Insufficient permissions for this endpoint";
-            context.errorCode = "INSUFFICIENT_PERMISSIONS";
+            context.errorMessage  = "Insufficient permissions for this endpoint";
+            context.errorCode     = "INSUFFICIENT_PERMISSIONS";
         }
 
         // Log authentication attempt
@@ -105,8 +103,8 @@ AuthContext AuthMiddleware::authenticate(const httplib::Request& req, httplib::R
 
     } catch (const std::exception& e) {
         context.authenticated = false;
-        context.errorMessage = "Authentication error: " + std::string(e.what());
-        context.errorCode = "AUTH_ERROR";
+        context.errorMessage  = "Authentication error: " + std::string(e.what());
+        context.errorCode     = "AUTH_ERROR";
     }
 
     return context;
@@ -145,10 +143,8 @@ bool AuthMiddleware::hasPathAccess(const std::string& path,
 
     // Check user paths
     if (requiresUserAccess(path)) {
-        return JWTAuth::hasAnyPermission(permissions, {
-            UserManager::Permissions::USER_MANAGE,
-            UserManager::Permissions::ADMIN
-        });
+        return JWTAuth::hasAnyPermission(permissions, {UserManager::Permissions::USER_MANAGE,
+                                                       UserManager::Permissions::ADMIN});
     }
 
     // Default: allow access if authenticated
@@ -156,7 +152,7 @@ bool AuthMiddleware::hasPathAccess(const std::string& path,
 }
 
 AuthMiddleware::AuthHandler AuthMiddleware::createMiddleware(AuthHandler handler) {
-    return [this, handler](const httplib::Request& req, httplib::Response& res, const AuthContext& context) {
+    return [this, handler](const httplib::Request& req, httplib::Response& res, const AuthContext& /*context*/) {
         // Authenticate request
         AuthContext authContext = authenticate(req, res);
 
@@ -172,39 +168,27 @@ AuthMiddleware::AuthHandler AuthMiddleware::createMiddleware(AuthHandler handler
 }
 
 void AuthMiddleware::sendAuthError(httplib::Response& res,
-                                  const std::string& message,
-                                  const std::string& errorCode,
-                                  int statusCode) {
-    json error = {
-        {"error", {
-            {"message", message},
-            {"code", errorCode},
-            {"timestamp", std::chrono::duration_cast<std::chrono::seconds>(
-                std::chrono::system_clock::now().time_since_epoch()).count()}
-        }}
-    };
+                                   const std::string& message,
+                                   const std::string& errorCode,
+                                   int statusCode) {
+    nlohmann::json error = {
+        {"error", {{"message", message}, {"code", errorCode}, {"timestamp", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count()}}}};
 
-    res.set_header("Content-Type", "application/json");
+    res.set_header("Content-Type", "application/nlohmann::json");
     res.set_header("WWW-Authenticate", "Bearer realm=\"" + m_config.authRealm + "\"");
     res.status = statusCode;
-    res.body = error.dump();
+    res.body   = error.dump();
 }
 
 void AuthMiddleware::sendAuthzError(httplib::Response& res,
-                                   const std::string& message,
-                                   const std::string& errorCode) {
-    json error = {
-        {"error", {
-            {"message", message},
-            {"code", errorCode},
-            {"timestamp", std::chrono::duration_cast<std::chrono::seconds>(
-                std::chrono::system_clock::now().time_since_epoch()).count()}
-        }}
-    };
+                                    const std::string& message,
+                                    const std::string& errorCode) {
+    nlohmann::json error = {
+        {"error", {{"message", message}, {"code", errorCode}, {"timestamp", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count()}}}};
 
-    res.set_header("Content-Type", "application/json");
+    res.set_header("Content-Type", "application/nlohmann::json");
     res.status = 403;
-    res.body = error.dump();
+    res.body   = error.dump();
 }
 
 void AuthMiddleware::addPublicPath(const std::string& path) {
@@ -225,7 +209,7 @@ AuthContext AuthMiddleware::authenticateApiKey(const httplib::Request& req) {
 
     if (!m_userManager) {
         context.errorMessage = "User manager not available";
-        context.errorCode = "USER_MANAGER_UNAVAILABLE";
+        context.errorCode    = "USER_MANAGER_UNAVAILABLE";
         return context;
     }
 
@@ -233,7 +217,7 @@ AuthContext AuthMiddleware::authenticateApiKey(const httplib::Request& req) {
     std::string apiKey = extractToken(req, "X-API-Key");
     if (apiKey.empty()) {
         context.errorMessage = "Missing API key";
-        context.errorCode = "MISSING_API_KEY";
+        context.errorCode    = "MISSING_API_KEY";
         return context;
     }
 
@@ -241,17 +225,17 @@ AuthContext AuthMiddleware::authenticateApiKey(const httplib::Request& req) {
     auto result = m_userManager->authenticateApiKey(apiKey);
     if (!result.success) {
         context.errorMessage = result.errorMessage;
-        context.errorCode = result.errorCode;
+        context.errorCode    = result.errorCode;
         return context;
     }
 
     // API key is valid
     context.authenticated = true;
-    context.userId = result.userId;
-    context.username = result.username;
-    context.role = result.role;
-    context.permissions = result.permissions;
-    context.authMethod = "API_KEY";
+    context.userId        = result.userId;
+    context.username      = result.username;
+    context.role          = result.role;
+    context.permissions   = result.permissions;
+    context.authMethod    = "API_KEY";
 
     return context;
 }
@@ -262,14 +246,14 @@ AuthContext AuthMiddleware::authenticateUnix(const httplib::Request& req) {
 
     if (!m_userManager) {
         context.errorMessage = "User manager not available";
-        context.errorCode = "USER_MANAGER_UNAVAILABLE";
+        context.errorCode    = "USER_MANAGER_UNAVAILABLE";
         return context;
     }
 
     // Check if Unix authentication is the configured method
     if (m_config.authMethod != AuthMethod::UNIX) {
         context.errorMessage = "Unix authentication not available";
-        context.errorCode = "UNIX_AUTH_UNAVAILABLE";
+        context.errorCode    = "UNIX_AUTH_UNAVAILABLE";
         return context;
     }
 
@@ -279,12 +263,12 @@ AuthContext AuthMiddleware::authenticateUnix(const httplib::Request& req) {
 
     // Try to extract from JSON body (for login API)
     std::string contentType = req.get_header_value("Content-Type");
-    if (contentType.find("application/json") != std::string::npos) {
+    if (contentType.find("application/nlohmann::json") != std::string::npos) {
         try {
-            json body = json::parse(req.body);
-            username = body.value("username", "");
-            password = body.value("password", "");
-        } catch (const json::exception& e) {
+            nlohmann::json body = nlohmann::json::parse(req.body);
+            username            = body.value("username", "");
+            password            = body.value("password", "");
+        } catch (const nlohmann::json::exception& e) {
             // Invalid JSON, continue with other methods
         }
     }
@@ -297,7 +281,7 @@ AuthContext AuthMiddleware::authenticateUnix(const httplib::Request& req) {
         if (username.empty()) {
             std::string authHeader = req.get_header_value("Authorization");
             if (!authHeader.empty() && authHeader.find("Bearer ") == 0) {
-                std::string token = authHeader.substr(7); // Remove "Bearer "
+                std::string token = authHeader.substr(7);  // Remove "Bearer "
                 // Check if this is a Unix token
                 if (token.find("unix_token_") == 0) {
                     // Extract username from token
@@ -317,13 +301,13 @@ AuthContext AuthMiddleware::authenticateUnix(const httplib::Request& req) {
         std::string path = req.path;
         if (path.find("/ui/") == 0 || path == "/ui") {
             // This is a UI request, let it proceed to show the login page
-            context = createGuestContext();
-            context.authenticated = false; // Ensure it's false to trigger login page
+            context               = createGuestContext();
+            context.authenticated = false;  // Ensure it's false to trigger login page
             return context;
         } else {
             // This is an API request, return error
             context.errorMessage = "Missing Unix username";
-            context.errorCode = "MISSING_UNIX_USER";
+            context.errorCode    = "MISSING_UNIX_USER";
             return context;
         }
     }
@@ -332,17 +316,17 @@ AuthContext AuthMiddleware::authenticateUnix(const httplib::Request& req) {
     auto result = m_userManager->authenticateUnix(username, password);
     if (!result.success) {
         context.errorMessage = result.errorMessage;
-        context.errorCode = result.errorCode;
+        context.errorCode    = result.errorCode;
         return context;
     }
 
     // Unix authentication successful
     context.authenticated = true;
-    context.userId = result.userId;
-    context.username = result.username;
-    context.role = result.role;
-    context.permissions = result.permissions;
-    context.authMethod = "UNIX";
+    context.userId        = result.userId;
+    context.username      = result.username;
+    context.role          = result.role;
+    context.permissions   = result.permissions;
+    context.authMethod    = "UNIX";
 
     return context;
 }
@@ -353,14 +337,14 @@ AuthContext AuthMiddleware::authenticatePam(const httplib::Request& req) {
 
     if (!m_userManager) {
         context.errorMessage = "User manager not available";
-        context.errorCode = "USER_MANAGER_UNAVAILABLE";
+        context.errorCode    = "USER_MANAGER_UNAVAILABLE";
         return context;
     }
 
     // Check if PAM authentication is the configured method
     if (m_config.authMethod != AuthMethod::PAM) {
         context.errorMessage = "PAM authentication not available";
-        context.errorCode = "PAM_AUTH_UNAVAILABLE";
+        context.errorCode    = "PAM_AUTH_UNAVAILABLE";
         return context;
     }
 
@@ -371,12 +355,12 @@ AuthContext AuthMiddleware::authenticatePam(const httplib::Request& req) {
 
     // Try to extract from JSON body (for login API)
     std::string contentType = req.get_header_value("Content-Type");
-    if (contentType.find("application/json") != std::string::npos) {
+    if (contentType.find("application/nlohmann::json") != std::string::npos) {
         try {
-            json body = json::parse(req.body);
-            username = body.value("username", "");
-            password = body.value("password", "");
-        } catch (const json::exception& e) {
+            nlohmann::json body = nlohmann::json::parse(req.body);
+            username            = body.value("username", "");
+            password            = body.value("password", "");
+        } catch (const nlohmann::json::exception& e) {
             // Invalid JSON
         }
     }
@@ -386,7 +370,7 @@ AuthContext AuthMiddleware::authenticatePam(const httplib::Request& req) {
         std::string authHeader = req.get_header_value("Authorization");
         if (!authHeader.empty() && authHeader.find("Basic ") == 0) {
             // Decode basic auth
-            std::string basicAuth = authHeader.substr(6); // Remove "Basic "
+            std::string basicAuth = authHeader.substr(6);  // Remove "Basic "
             // Note: In a real implementation, you'd decode base64 here
             // For now, we'll expect the credentials to be in the JSON body
         }
@@ -399,13 +383,13 @@ AuthContext AuthMiddleware::authenticatePam(const httplib::Request& req) {
         std::string path = req.path;
         if (path.find("/ui/") == 0 || path == "/ui") {
             // This is a UI request, let it proceed to show the login page
-            context = createGuestContext();
-            context.authenticated = false; // Ensure it's false to trigger login page
+            context               = createGuestContext();
+            context.authenticated = false;  // Ensure it's false to trigger login page
             return context;
         } else {
             // This is an API request, return error
             context.errorMessage = "Missing PAM credentials";
-            context.errorCode = "MISSING_PAM_CREDENTIALS";
+            context.errorCode    = "MISSING_PAM_CREDENTIALS";
             return context;
         }
     }
@@ -414,17 +398,17 @@ AuthContext AuthMiddleware::authenticatePam(const httplib::Request& req) {
     auto result = m_userManager->authenticatePam(username, password);
     if (!result.success) {
         context.errorMessage = result.errorMessage;
-        context.errorCode = result.errorCode;
+        context.errorCode    = result.errorCode;
         return context;
     }
 
     // PAM authentication successful
     context.authenticated = true;
-    context.userId = result.userId;
-    context.username = result.username;
-    context.role = result.role;
-    context.permissions = result.permissions;
-    context.authMethod = "PAM";
+    context.userId        = result.userId;
+    context.username      = result.username;
+    context.role          = result.role;
+    context.permissions   = result.permissions;
+    context.authMethod    = "PAM";
 
     return context;
 }
@@ -442,25 +426,40 @@ std::string AuthMiddleware::extractToken(const httplib::Request& req, const std:
 AuthContext AuthMiddleware::createGuestContext() const {
     AuthContext context;
     context.authenticated = false;
-    context.userId = "guest";
-    context.username = "guest";
-    context.role = "guest";
-    context.permissions = UserManager::getDefaultPermissions(UserManager::UserRole::GUEST);
-    context.authMethod = "none";
+    context.userId        = "guest";
+    context.username      = "guest";
+    context.role          = "guest";
+    context.permissions   = UserManager::getDefaultPermissions(UserManager::UserRole::GUEST);
+    context.authMethod    = "none";
     return context;
 }
 
 void AuthMiddleware::logAuthAttempt(const httplib::Request& req,
                                     const AuthContext& context,
                                     bool success) const {
-    // In a real implementation, this would log to a file or logging system
-    std::string clientIp = getClientIp(req);
+    auto now = std::chrono::system_clock::now();
+    auto time_t_now = std::chrono::system_clock::to_time_t(now);
+    std::stringstream timestamp_ss;
+    timestamp_ss << std::put_time(std::gmtime(&time_t_now), "%Y-%m-%dT%H:%M:%S");
+    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
+    timestamp_ss << "." << std::setfill('0') << std::setw(3) << ms.count() << "Z";
+
+    std::string clientIp  = getClientIp(req);
     std::string userAgent = getUserAgent(req);
+    std::string username = context.authenticated ? context.username : "unknown";
+    std::string status = success ? "success" : "failure";
+
+    std::string message = "Authentication " + status + " - " +
+                         "timestamp=" + timestamp_ss.str() + ", " +
+                         "ip=" + (clientIp.empty() ? "unknown" : clientIp) + ", " +
+                         "username=" + (username.empty() ? "unknown" : username) + ", " +
+                         "path=" + req.path + ", " +
+                         "user-agent=" + (userAgent.empty() ? "unknown" : userAgent);
 
     if (success) {
-        // Log successful authentication
+        Logger::getInstance().info(message);
     } else {
-        // Log failed authentication attempt
+        Logger::getInstance().warning(message);
     }
 }
 
@@ -490,7 +489,7 @@ bool AuthMiddleware::validateConfig(const AuthConfig& config) {
             // Will be auto-generated
         }
         if (config.jwtExpirationMinutes <= 0 || config.jwtExpirationMinutes > 1440) {
-            return false; // Max 24 hours
+            return false;  // Max 24 hours
         }
     }
 
@@ -537,8 +536,7 @@ void AuthMiddleware::initializeDefaultPaths() {
         m_config.adminPaths = {
             "/api/users",
             "/api/auth/users",
-            "/api/system/restart"
-        };
+            "/api/system/restart"};
     }
 
     // Add default user paths
@@ -556,8 +554,7 @@ void AuthMiddleware::initializeDefaultPaths() {
             "/api/models/directories",
             "/api/samplers",
             "/api/schedulers",
-            "/api/parameters"
-        };
+            "/api/parameters"};
     }
 }
 
@@ -571,7 +568,7 @@ AuthContext AuthMiddleware::authenticateJwt(const httplib::Request& req) {
 
     if (!m_jwtAuth) {
         context.errorMessage = "JWT authentication not available";
-        context.errorCode = "JWT_AUTH_UNAVAILABLE";
+        context.errorCode    = "JWT_AUTH_UNAVAILABLE";
         return context;
     }
 
@@ -579,7 +576,7 @@ AuthContext AuthMiddleware::authenticateJwt(const httplib::Request& req) {
     std::string token = extractToken(req, "Authorization");
     if (token.empty()) {
         context.errorMessage = "Missing JWT token";
-        context.errorCode = "MISSING_JWT_TOKEN";
+        context.errorCode    = "MISSING_JWT_TOKEN";
         return context;
     }
 
@@ -587,17 +584,17 @@ AuthContext AuthMiddleware::authenticateJwt(const httplib::Request& req) {
     auto result = m_jwtAuth->validateToken(token);
     if (!result.success) {
         context.errorMessage = result.errorMessage;
-        context.errorCode = "INVALID_JWT_TOKEN";
+        context.errorCode    = "INVALID_JWT_TOKEN";
         return context;
     }
 
     // JWT is valid
     context.authenticated = true;
-    context.userId = result.userId;
-    context.username = result.username;
-    context.role = result.role;
-    context.permissions = result.permissions;
-    context.authMethod = "JWT";
+    context.userId        = result.userId;
+    context.username      = result.username;
+    context.role          = result.role;
+    context.permissions   = result.permissions;
+    context.authMethod    = "JWT";
 
     return context;
 }
@@ -647,38 +644,38 @@ void AuthMiddleware::updateConfig(const AuthConfig& config) {
 // Factory functions
 namespace AuthMiddlewareFactory {
 
-std::unique_ptr<AuthMiddleware> createDefault(std::shared_ptr<UserManager> userManager,
-                                               const std::string& dataDir) {
-    AuthConfig config;
-    config.authMethod = AuthMethod::NONE;
-    config.enableGuestAccess = true;
-    config.jwtSecret = "";
-    config.jwtExpirationMinutes = 60;
-    config.authRealm = "stable-diffusion-rest";
+    std::unique_ptr<AuthMiddleware> createDefault(std::shared_ptr<UserManager> userManager,
+                                                  const std::string& /*dataDir*/) {
+        AuthConfig config;
+        config.authMethod           = AuthMethod::NONE;
+        config.enableGuestAccess    = true;
+        config.jwtSecret            = "";
+        config.jwtExpirationMinutes = 60;
+        config.authRealm            = "stable-diffusion-rest";
 
-    return std::make_unique<AuthMiddleware>(config, userManager);
-}
+        return std::make_unique<AuthMiddleware>(config, userManager);
+    }
 
-std::unique_ptr<AuthMiddleware> createJwtOnly(std::shared_ptr<UserManager> userManager,
-                                               const std::string& jwtSecret,
-                                               int jwtExpirationMinutes) {
-    AuthConfig config;
-    config.authMethod = AuthMethod::JWT;
-    config.enableGuestAccess = false;
-    config.jwtSecret = jwtSecret;
-    config.jwtExpirationMinutes = jwtExpirationMinutes;
-    config.authRealm = "stable-diffusion-rest";
-
-    return std::make_unique<AuthMiddleware>(config, userManager);
-}
+    std::unique_ptr<AuthMiddleware> createJwtOnly(std::shared_ptr<UserManager> userManager,
+                                                  const std::string& jwtSecret,
+                                                  int jwtExpirationMinutes) {
+        AuthConfig config;
+        config.authMethod           = AuthMethod::JWT;
+        config.enableGuestAccess    = false;
+        config.jwtSecret            = jwtSecret;
+        config.jwtExpirationMinutes = jwtExpirationMinutes;
+        config.authRealm            = "stable-diffusion-rest";
+
+        return std::make_unique<AuthMiddleware>(config, userManager);
+    }
 
-std::unique_ptr<AuthMiddleware> createApiKeyOnly(std::shared_ptr<UserManager> userManager) {
-    AuthConfig config;
-    config.authMethod = AuthMethod::API_KEY;
-    config.enableGuestAccess = false;
-    config.authRealm = "stable-diffusion-rest";
+    std::unique_ptr<AuthMiddleware> createApiKeyOnly(std::shared_ptr<UserManager> userManager) {
+        AuthConfig config;
+        config.authMethod        = AuthMethod::API_KEY;
+        config.enableGuestAccess = false;
+        config.authRealm         = "stable-diffusion-rest";
 
-    return std::make_unique<AuthMiddleware>(config, userManager);
-}
+        return std::make_unique<AuthMiddleware>(config, userManager);
+    }
 
-} // namespace AuthMiddlewareFactory
+}  // namespace AuthMiddlewareFactory