#include "common/Auth.h" #include "common/Crypto.h" #include #include namespace visione::auth { namespace { std::string primaryRole(const std::vector &roles) { if (std::ranges::find(roles, "admin") != roles.end()) { return "admin"; } if (std::ranges::find(roles, "operator") != roles.end()) { return "operator"; } return "citizen"; } } // namespace std::optional bearerToken(const drogon::HttpRequestPtr &request) { const auto header = request->getHeader("Authorization"); constexpr std::string_view prefix = "Bearer "; if (!header.starts_with(prefix)) { return std::nullopt; } return header.substr(prefix.size()); } std::optional authenticate(const drogon::HttpRequestPtr &request) { const auto token = bearerToken(request); if (!token) { return std::nullopt; } auto db = drogon::app().getDbClient(); const auto userResult = db->execSqlSync( "SELECT u.id, u.email, u.must_change_password " "FROM api_tokens t " "JOIN users u ON u.id = t.user_id " "WHERE t.token_hash = ? AND t.revoked_at IS NULL " "AND t.expires_at > NOW() AND u.banned = FALSE", crypto::hashSecret(*token)); if (userResult.empty()) { return std::nullopt; } const auto userId = userResult[0]["id"].as(); const auto roleRows = db->execSqlSync( "SELECT r.name FROM user_roles ur " "JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = ? ORDER BY r.name", userId); const auto permissionRows = db->execSqlSync( "SELECT DISTINCT p.name FROM user_roles ur " "JOIN role_permissions rp ON rp.role_id = ur.role_id " "JOIN permissions p ON p.id = rp.permission_id " "WHERE ur.user_id = ? ORDER BY p.name", userId); std::vector roles; roles.reserve(roleRows.size()); for (const auto &row : roleRows) { roles.push_back(row["name"].as()); } if (roles.empty()) { roles.push_back("citizen"); } std::vector permissions; permissions.reserve(permissionRows.size()); for (const auto &row : permissionRows) { permissions.push_back(row["name"].as()); } UserContext context; context.id = userId; context.email = userResult[0]["email"].as(); context.roles = std::move(roles); context.permissions = std::move(permissions); context.role = primaryRole(context.roles); context.mustChangePassword = userResult[0]["must_change_password"].as(); return context; } bool hasRole(const UserContext &user, const std::string &role) { return std::ranges::find(user.roles, role) != user.roles.end(); } bool hasPermission(const UserContext &user, const std::string &permission) { return std::ranges::find(user.permissions, permission) != user.permissions.end(); } bool canManageReports(const UserContext &user) { return isAdmin(user) || hasPermission(user, "reports.manage"); } bool isAdmin(const UserContext &user) { return hasRole(user, "admin") || hasPermission(user, "admin.manage"); } bool mustChangePassword(const UserContext &user) { return user.mustChangePassword; } } // namespace visione::auth