Add user authorization
This commit is contained in:
+74
-15
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user