Add user authorization

This commit is contained in:
gabdevele
2026-06-02 20:01:56 +02:00 Unverified
parent 795383a98c
commit 54aa4cb73e
13 changed files with 361 additions and 36 deletions
+74 -15
View File
@@ -4,8 +4,26 @@
#include <drogon/drogon.h>
#include <algorithm>
namespace visione::auth
{
namespace
{
std::string primaryRole(const std::vector<std::string> &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<std::string> bearerToken(const drogon::HttpRequestPtr &request)
{
const auto header = request->getHeader("Authorization");
@@ -26,41 +44,82 @@ std::optional<UserContext> authenticate(const drogon::HttpRequestPtr &request)
}
auto db = drogon::app().getDbClient();
const auto result = db->execSqlSync(
"SELECT u.id, u.email, COALESCE(MIN(r.name), 'citizen') AS role "
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 "
"LEFT JOIN user_roles ur ON ur.user_id = u.id "
"LEFT JOIN roles r ON r.id = ur.role_id "
"WHERE t.token_hash = ? AND t.revoked_at IS NULL "
"AND t.expires_at > NOW() AND u.banned = FALSE "
"GROUP BY u.id, u.email",
"AND t.expires_at > NOW() AND u.banned = FALSE",
crypto::hashSecret(*token));
if (result.empty())
if (userResult.empty())
{
return std::nullopt;
}
const auto &row = result[0];
return UserContext{row["id"].as<long long>(),
row["email"].as<std::string>(),
row["role"].as<std::string>()};
const auto userId = userResult[0]["id"].as<long long>();
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<std::string> roles;
roles.reserve(roleRows.size());
for (const auto &row : roleRows)
{
roles.push_back(row["name"].as<std::string>());
}
if (roles.empty())
{
roles.push_back("citizen");
}
std::vector<std::string> permissions;
permissions.reserve(permissionRows.size());
for (const auto &row : permissionRows)
{
permissions.push_back(row["name"].as<std::string>());
}
UserContext context;
context.id = userId;
context.email = userResult[0]["email"].as<std::string>();
context.roles = std::move(roles);
context.permissions = std::move(permissions);
context.role = primaryRole(context.roles);
context.mustChangePassword = userResult[0]["must_change_password"].as<bool>();
return context;
}
bool hasRole(const UserContext &user, const std::string &role)
{
return user.role == 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 user.role == "admin" || user.role == "operator";
return isAdmin(user) || hasPermission(user, "reports.manage");
}
bool isAdmin(const UserContext &user)
{
return user.role == "admin";
return hasRole(user, "admin") || hasPermission(user, "admin.manage");
}
bool mustChangePassword(const UserContext &user)
{
return user.mustChangePassword;
}
} // namespace visione::auth
+6 -1
View File
@@ -4,6 +4,7 @@
#include <optional>
#include <string>
#include <vector>
namespace visione::auth
{
@@ -12,12 +13,16 @@ struct UserContext
long long id{0};
std::string email;
std::string role;
std::vector<std::string> roles;
std::vector<std::string> permissions;
bool mustChangePassword{false};
};
std::optional<std::string> bearerToken(const drogon::HttpRequestPtr &request);
std::optional<UserContext> authenticate(const drogon::HttpRequestPtr &request);
bool hasRole(const UserContext &user, const std::string &role);
bool hasPermission(const UserContext &user, const std::string &permission);
bool canManageReports(const UserContext &user);
bool isAdmin(const UserContext &user);
bool mustChangePassword(const UserContext &user);
} // namespace visione::auth