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
|
||||
|
||||
|
||||
+6
-1
@@ -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
|
||||
|
||||
|
||||
@@ -20,6 +20,19 @@ std::optional<visione::auth::UserContext> requireUser(
|
||||
return user;
|
||||
}
|
||||
|
||||
bool blockPasswordChangeRequired(
|
||||
const visione::auth::UserContext &user,
|
||||
std::function<void(const HttpResponsePtr &)> &callback)
|
||||
{
|
||||
if (!visione::auth::mustChangePassword(user))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
callback(visione::http::error("password_change_required",
|
||||
drogon::k403Forbidden));
|
||||
return true;
|
||||
}
|
||||
|
||||
void sendAdmin(Json::Value body,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
@@ -28,6 +41,7 @@ void sendAdmin(Json::Value body,
|
||||
callback(visione::http::json(std::move(body)));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto error = body.get("error", "").asString();
|
||||
const auto status = error == "forbidden" ? drogon::k403Forbidden
|
||||
: drogon::k400BadRequest;
|
||||
@@ -48,7 +62,7 @@ void AdminController::createCategory(
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -70,7 +84,7 @@ void AdminController::createArea(
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -92,7 +106,7 @@ void AdminController::createTeam(
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -106,7 +120,7 @@ void AdminController::overviewStats(
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -114,3 +128,46 @@ void AdminController::overviewStats(
|
||||
sendAdmin(service.overviewStats(*user), std::move(callback));
|
||||
}
|
||||
|
||||
void AdminController::listUsers(
|
||||
const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
visione::services::AdminService service;
|
||||
sendAdmin(service.listUsers(*user), std::move(callback));
|
||||
}
|
||||
|
||||
void AdminController::updateUserRoles(
|
||||
const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||
long long userId)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
visione::services::AdminService service;
|
||||
sendAdmin(service.updateUserRoles(*user, userId, visione::http::requireJson(req)),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void AdminController::updateUserBan(
|
||||
const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||
long long userId)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
visione::services::AdminService service;
|
||||
sendAdmin(service.updateUserBan(*user, userId, visione::http::requireJson(req)),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,13 @@ class AdminController : public drogon::HttpController<AdminController>
|
||||
ADD_METHOD_TO(AdminController::overviewStats,
|
||||
"/api/v1/admin/stats/overview",
|
||||
drogon::Get);
|
||||
ADD_METHOD_TO(AdminController::listUsers, "/api/v1/admin/users", drogon::Get);
|
||||
ADD_METHOD_TO(AdminController::updateUserRoles,
|
||||
"/api/v1/admin/users/{1}/roles",
|
||||
drogon::Patch);
|
||||
ADD_METHOD_TO(AdminController::updateUserBan,
|
||||
"/api/v1/admin/users/{1}/ban",
|
||||
drogon::Patch);
|
||||
METHOD_LIST_END
|
||||
|
||||
void listCategories(
|
||||
@@ -42,5 +49,16 @@ class AdminController : public drogon::HttpController<AdminController>
|
||||
void overviewStats(
|
||||
const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||
void listUsers(
|
||||
const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||
void updateUserRoles(
|
||||
const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||
long long userId);
|
||||
void updateUserBan(
|
||||
const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||
long long userId);
|
||||
};
|
||||
|
||||
|
||||
@@ -81,6 +81,28 @@ void AuthController::loginWithPassword(
|
||||
serviceResponse(service.loginWithPassword(body), std::move(callback));
|
||||
}
|
||||
|
||||
void AuthController::changePassword(
|
||||
const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
const auto user = visione::auth::authenticate(req);
|
||||
if (!user)
|
||||
{
|
||||
callback(visione::http::error("unauthorized", drogon::k401Unauthorized));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto body = visione::http::requireJson(req);
|
||||
if (body.isNull())
|
||||
{
|
||||
callback(visione::http::error("invalid_json", drogon::k400BadRequest));
|
||||
return;
|
||||
}
|
||||
|
||||
visione::services::AuthService service;
|
||||
serviceResponse(service.changePassword(*user, body), std::move(callback));
|
||||
}
|
||||
|
||||
void AuthController::me(
|
||||
const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
@@ -95,4 +117,3 @@ void AuthController::me(
|
||||
visione::services::AuthService service;
|
||||
callback(visione::http::json(service.currentUser(*user)));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ class AuthController : public drogon::HttpController<AuthController>
|
||||
ADD_METHOD_TO(AuthController::loginWithPassword,
|
||||
"/api/v1/auth/login/password",
|
||||
drogon::Post);
|
||||
ADD_METHOD_TO(AuthController::changePassword,
|
||||
"/api/v1/auth/change-password",
|
||||
drogon::Post);
|
||||
ADD_METHOD_TO(AuthController::me, "/api/v1/me", drogon::Get);
|
||||
METHOD_LIST_END
|
||||
|
||||
@@ -33,7 +36,9 @@ class AuthController : public drogon::HttpController<AuthController>
|
||||
void loginWithPassword(
|
||||
const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||
void changePassword(
|
||||
const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||
void me(const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -36,6 +36,19 @@ std::optional<visione::auth::UserContext> requireUser(
|
||||
return user;
|
||||
}
|
||||
|
||||
bool blockPasswordChangeRequired(
|
||||
const visione::auth::UserContext &user,
|
||||
std::function<void(const HttpResponsePtr &)> &callback)
|
||||
{
|
||||
if (!visione::auth::mustChangePassword(user))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
callback(visione::http::error("password_change_required",
|
||||
drogon::k403Forbidden));
|
||||
return true;
|
||||
}
|
||||
|
||||
void sendService(Json::Value body,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
@@ -56,7 +69,7 @@ void ReportController::create(
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -76,7 +89,7 @@ void ReportController::list(
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -95,7 +108,7 @@ void ReportController::detail(
|
||||
long long reportId)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -109,7 +122,7 @@ void ReportController::updateStatus(
|
||||
long long reportId)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -124,7 +137,7 @@ void ReportController::addMessage(
|
||||
long long reportId)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -139,7 +152,7 @@ void ReportController::listMessages(
|
||||
long long reportId)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -153,7 +166,7 @@ void ReportController::assignTeam(
|
||||
long long reportId)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -168,7 +181,7 @@ void ReportController::addFeedback(
|
||||
long long reportId)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -183,7 +196,7 @@ void ReportController::uploadPhotos(
|
||||
long long reportId)
|
||||
{
|
||||
auto user = requireUser(req, callback);
|
||||
if (!user)
|
||||
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ Base path: `/api/v1`.
|
||||
- `POST /auth/otp/request`: send an OTP email through MailHog.
|
||||
- `POST /auth/otp/verify`: verify OTP and create an API token.
|
||||
- `POST /auth/login/password`: password login fallback.
|
||||
- `POST /auth/change-password`: change the authenticated user's password.
|
||||
- `GET /me`: return the authenticated user.
|
||||
|
||||
Protected endpoints use:
|
||||
@@ -35,3 +36,6 @@ Authorization: Bearer <token>
|
||||
- `GET|POST /admin/areas`
|
||||
- `GET|POST /admin/teams`
|
||||
- `GET /admin/stats/overview`
|
||||
- `GET /admin/users`
|
||||
- `PATCH /admin/users/{id}/roles`
|
||||
- `PATCH /admin/users/{id}/ban`
|
||||
|
||||
@@ -11,6 +11,9 @@ assign and resolve interventions while preserving history and basic analytics.
|
||||
reports and messages citizens.
|
||||
- `admin`: manages users, roles, reference data, teams, areas and all reports.
|
||||
|
||||
The system admin seed must change password before using protected operational
|
||||
or administrative APIs.
|
||||
|
||||
## Report Statuses
|
||||
- `OPEN`: created and waiting for review.
|
||||
- `UNDER_REVIEW`: an operator is checking the report.
|
||||
@@ -33,4 +36,3 @@ assign and resolve interventions while preserving history and basic analytics.
|
||||
- Route optimization.
|
||||
- Public open data export.
|
||||
- Full Docker packaging of the backend.
|
||||
|
||||
|
||||
@@ -137,5 +137,99 @@ Json::Value AdminService::overviewStats(const auth::UserContext &user)
|
||||
"FROM reports WHERE status IN ('RESOLVED', 'CLOSED')"));
|
||||
return response;
|
||||
}
|
||||
} // namespace visione::services
|
||||
|
||||
Json::Value AdminService::listUsers(const auth::UserContext &user)
|
||||
{
|
||||
if (!auth::isAdmin(user))
|
||||
{
|
||||
return failure("forbidden");
|
||||
}
|
||||
|
||||
auto response = success();
|
||||
response["users"] = repositories::resultToJson(
|
||||
drogon::app().getDbClient()->execSqlSync(
|
||||
"SELECT u.id, u.first_name, u.last_name, u.email, u.fiscal_code, "
|
||||
"u.phone, u.points, u.banned, u.must_change_password, "
|
||||
"GROUP_CONCAT(r.name ORDER BY r.name SEPARATOR ',') AS roles "
|
||||
"FROM users u "
|
||||
"LEFT JOIN user_roles ur ON ur.user_id = u.id "
|
||||
"LEFT JOIN roles r ON r.id = ur.role_id "
|
||||
"GROUP BY u.id, u.first_name, u.last_name, u.email, u.fiscal_code, "
|
||||
"u.phone, u.points, u.banned, u.must_change_password "
|
||||
"ORDER BY u.created_at DESC LIMIT 200"));
|
||||
return response;
|
||||
}
|
||||
|
||||
Json::Value AdminService::updateUserRoles(const auth::UserContext &user,
|
||||
long long targetUserId,
|
||||
const Json::Value &body)
|
||||
{
|
||||
if (!auth::isAdmin(user) || !body["roles"].isArray())
|
||||
{
|
||||
return failure("invalid_roles");
|
||||
}
|
||||
|
||||
auto db = drogon::app().getDbClient();
|
||||
const auto target =
|
||||
db->execSqlSync("SELECT id FROM users WHERE id = ?", targetUserId);
|
||||
if (target.empty())
|
||||
{
|
||||
return failure("user_not_found");
|
||||
}
|
||||
|
||||
db->execSqlSync("DELETE FROM user_roles WHERE user_id = ?", targetUserId);
|
||||
for (const auto &role : body["roles"])
|
||||
{
|
||||
if (!role.isString())
|
||||
{
|
||||
return failure("invalid_roles");
|
||||
}
|
||||
|
||||
const auto roleRows =
|
||||
db->execSqlSync("SELECT id FROM roles WHERE name = ?", role.asString());
|
||||
if (roleRows.empty())
|
||||
{
|
||||
return failure("role_not_found");
|
||||
}
|
||||
db->execSqlSync(
|
||||
"INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)",
|
||||
targetUserId,
|
||||
roleRows[0]["id"].as<long long>());
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
Json::Value AdminService::updateUserBan(const auth::UserContext &user,
|
||||
long long targetUserId,
|
||||
const Json::Value &body)
|
||||
{
|
||||
if (!auth::isAdmin(user) || !body["banned"].isBool())
|
||||
{
|
||||
return failure("invalid_ban_payload");
|
||||
}
|
||||
if (targetUserId == user.id)
|
||||
{
|
||||
return failure("cannot_ban_self");
|
||||
}
|
||||
|
||||
auto db = drogon::app().getDbClient();
|
||||
const auto target =
|
||||
db->execSqlSync("SELECT id FROM users WHERE id = ?", targetUserId);
|
||||
if (target.empty())
|
||||
{
|
||||
return failure("user_not_found");
|
||||
}
|
||||
|
||||
db->execSqlSync("UPDATE users SET banned = ? WHERE id = ?",
|
||||
body["banned"].asBool(),
|
||||
targetUserId);
|
||||
if (body["banned"].asBool())
|
||||
{
|
||||
db->execSqlSync(
|
||||
"UPDATE api_tokens SET revoked_at = NOW() "
|
||||
"WHERE user_id = ? AND revoked_at IS NULL",
|
||||
targetUserId);
|
||||
}
|
||||
return success();
|
||||
}
|
||||
} // namespace visione::services
|
||||
|
||||
@@ -16,6 +16,12 @@ class AdminService
|
||||
Json::Value listTeams();
|
||||
Json::Value createTeam(const auth::UserContext &user, const Json::Value &body);
|
||||
Json::Value overviewStats(const auth::UserContext &user);
|
||||
Json::Value listUsers(const auth::UserContext &user);
|
||||
Json::Value updateUserRoles(const auth::UserContext &user,
|
||||
long long targetUserId,
|
||||
const Json::Value &body);
|
||||
Json::Value updateUserBan(const auth::UserContext &user,
|
||||
long long targetUserId,
|
||||
const Json::Value &body);
|
||||
};
|
||||
} // namespace visione::services
|
||||
|
||||
|
||||
+42
-1
@@ -205,6 +205,40 @@ Json::Value AuthService::loginWithPassword(const Json::Value &body)
|
||||
return issueToken(user[0]["id"].as<long long>());
|
||||
}
|
||||
|
||||
Json::Value AuthService::changePassword(const auth::UserContext &user,
|
||||
const Json::Value &body)
|
||||
{
|
||||
const auto missing =
|
||||
validation::missingStrings(body, {"current_password", "new_password"});
|
||||
if (!missing.empty())
|
||||
{
|
||||
return failure("missing_fields");
|
||||
}
|
||||
if (body["new_password"].asString().size() < 10)
|
||||
{
|
||||
return failure("weak_password");
|
||||
}
|
||||
|
||||
auto db = drogon::app().getDbClient();
|
||||
const auto current = db->execSqlSync(
|
||||
"SELECT password_hash FROM users WHERE id = ? AND banned = FALSE",
|
||||
user.id);
|
||||
if (current.empty() ||
|
||||
!crypto::verifyPassword(
|
||||
body["current_password"].asString(),
|
||||
current[0]["password_hash"].as<std::string>()))
|
||||
{
|
||||
return failure("invalid_credentials");
|
||||
}
|
||||
|
||||
db->execSqlSync(
|
||||
"UPDATE users SET password_hash = ?, must_change_password = FALSE "
|
||||
"WHERE id = ?",
|
||||
crypto::hashPassword(body["new_password"].asString()),
|
||||
user.id);
|
||||
return success();
|
||||
}
|
||||
|
||||
Json::Value AuthService::currentUser(const auth::UserContext &user)
|
||||
{
|
||||
auto db = drogon::app().getDbClient();
|
||||
@@ -226,8 +260,15 @@ Json::Value AuthService::currentUser(const auth::UserContext &user)
|
||||
response["user"]["must_change_password"] =
|
||||
result[0]["must_change_password"].as<bool>();
|
||||
response["user"]["role"] = user.role;
|
||||
for (const auto &role : user.roles)
|
||||
{
|
||||
response["user"]["roles"].append(role);
|
||||
}
|
||||
for (const auto &permission : user.permissions)
|
||||
{
|
||||
response["user"]["permissions"].append(permission);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
} // namespace visione::services
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class AuthService
|
||||
Json::Value requestOtp(const Json::Value &body);
|
||||
Json::Value verifyOtp(const Json::Value &body);
|
||||
Json::Value loginWithPassword(const Json::Value &body);
|
||||
Json::Value changePassword(const auth::UserContext &user, const Json::Value &body);
|
||||
Json::Value currentUser(const auth::UserContext &user);
|
||||
};
|
||||
} // namespace visione::services
|
||||
|
||||
|
||||
Reference in New Issue
Block a user