131 lines
3.4 KiB
C++
131 lines
3.4 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, 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<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 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");
|
|
}
|
|
|
|
bool mustChangePassword(const UserContext &user)
|
|
{
|
|
return user.mustChangePassword;
|
|
}
|
|
} // namespace visione::auth
|