Files

125 lines
3.2 KiB
C++

#include "common/Auth.h"
#include "common/Crypto.h"
#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");
constexpr std::string_view prefix = "Bearer ";
if (!header.starts_with(prefix))
{
return std::nullopt;
}
return header.substr(prefix.size());
}
std::optional<UserContext> 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 "
"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<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);
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 canOperateReports(const UserContext &user)
{
return !isAdmin(user) && canManageReports(user);
}
bool isAdmin(const UserContext &user)
{
return hasRole(user, "admin") || hasPermission(user, "admin.manage");
}
} // namespace visione::auth