refractor(auth): removed password from everywhere

This commit is contained in:
gabdevele
2026-06-16 10:37:31 +02:00 Unverified
parent 4f80d7f995
commit bb7ed18539
25 changed files with 69 additions and 454 deletions
+4 -5
View File
@@ -36,9 +36,8 @@ has been built.
Local seeded admin: Local seeded admin:
- email: `admin@visione.local` - email: `admin@visione.gov.it`
- password: `ChangeMe123!` - access uses OTP through Mailpit and is for local development only.
- the account has `must_change_password = true` and is for local development only.
If you already had a MySQL volume from the old scaffold, create the local If you already had a MySQL volume from the old scaffold, create the local
database/user once: database/user once:
@@ -55,9 +54,9 @@ docker compose exec -T mysql mysql -uroot -proot -e \
```bash ```bash
curl http://localhost:5555/api/v1/health curl http://localhost:5555/api/v1/health
curl http://localhost:5555/api/v1/ready curl http://localhost:5555/api/v1/ready
curl -X POST http://localhost:5555/api/v1/auth/login/password \ curl -X POST http://localhost:5555/api/v1/auth/otp/request \
-H 'Content-Type: application/json' \ -H 'Content-Type: application/json' \
-d '{"email":"admin@visione.local","password":"ChangeMe123!"}' -d '{"email":"admin@visione.gov.it"}'
``` ```
Use `-DUSE_SYSTEM_DROGON=ON` only if Drogon is already installed and Use `-DUSE_SYSTEM_DROGON=ON` only if Drogon is already installed and
+1 -7
View File
@@ -45,7 +45,7 @@ std::optional<UserContext> authenticate(const drogon::HttpRequestPtr &request)
auto db = drogon::app().getDbClient(); auto db = drogon::app().getDbClient();
const auto userResult = db->execSqlSync( const auto userResult = db->execSqlSync(
"SELECT u.id, u.email, u.must_change_password " "SELECT u.id, u.email "
"FROM api_tokens t " "FROM api_tokens t "
"JOIN users u ON u.id = t.user_id " "JOIN users u ON u.id = t.user_id "
"WHERE t.token_hash = ? AND t.revoked_at IS NULL " "WHERE t.token_hash = ? AND t.revoked_at IS NULL "
@@ -93,7 +93,6 @@ std::optional<UserContext> authenticate(const drogon::HttpRequestPtr &request)
context.roles = std::move(roles); context.roles = std::move(roles);
context.permissions = std::move(permissions); context.permissions = std::move(permissions);
context.role = primaryRole(context.roles); context.role = primaryRole(context.roles);
context.mustChangePassword = userResult[0]["must_change_password"].as<bool>();
return context; return context;
} }
@@ -122,9 +121,4 @@ bool isAdmin(const UserContext &user)
{ {
return hasRole(user, "admin") || hasPermission(user, "admin.manage"); return hasRole(user, "admin") || hasPermission(user, "admin.manage");
} }
bool mustChangePassword(const UserContext &user)
{
return user.mustChangePassword;
}
} // namespace visione::auth } // namespace visione::auth
-2
View File
@@ -15,7 +15,6 @@ struct UserContext
std::string role; std::string role;
std::vector<std::string> roles; std::vector<std::string> roles;
std::vector<std::string> permissions; std::vector<std::string> permissions;
bool mustChangePassword{false};
}; };
std::optional<std::string> bearerToken(const drogon::HttpRequestPtr &request); std::optional<std::string> bearerToken(const drogon::HttpRequestPtr &request);
@@ -25,5 +24,4 @@ bool hasPermission(const UserContext &user, const std::string &permission);
bool canManageReports(const UserContext &user); bool canManageReports(const UserContext &user);
bool canOperateReports(const UserContext &user); bool canOperateReports(const UserContext &user);
bool isAdmin(const UserContext &user); bool isAdmin(const UserContext &user);
bool mustChangePassword(const UserContext &user);
} // namespace visione::auth } // namespace visione::auth
-59
View File
@@ -1,10 +1,6 @@
#include "common/Crypto.h" #include "common/Crypto.h"
#include <drogon/utils/Utilities.h> #include <drogon/utils/Utilities.h>
#include <openssl/evp.h>
#include <array>
#include <iomanip> #include <iomanip>
#include <random> #include <random>
#include <sstream> #include <sstream>
@@ -14,9 +10,6 @@ namespace visione::crypto
{ {
namespace namespace
{ {
constexpr int passwordIterations = 210000;
constexpr size_t passwordHashBytes = 32;
std::string toHex(const unsigned char *data, size_t size) std::string toHex(const unsigned char *data, size_t size)
{ {
std::ostringstream stream; std::ostringstream stream;
@@ -27,22 +20,6 @@ std::string toHex(const unsigned char *data, size_t size)
} }
return stream.str(); return stream.str();
} }
std::string pbkdf2(const std::string &password,
const std::string &salt,
int iterations)
{
std::array<unsigned char, passwordHashBytes> output{};
PKCS5_PBKDF2_HMAC(password.c_str(),
static_cast<int>(password.size()),
reinterpret_cast<const unsigned char *>(salt.data()),
static_cast<int>(salt.size()),
iterations,
EVP_sha256(),
static_cast<int>(output.size()),
output.data());
return toHex(output.data(), output.size());
}
} // namespace } // namespace
std::string sha256(const std::string &value) std::string sha256(const std::string &value)
@@ -70,42 +47,6 @@ std::string randomToken()
return drogon::utils::getUuid() + drogon::utils::getUuid(); return drogon::utils::getUuid() + drogon::utils::getUuid();
} }
std::string hashPassword(const std::string &password)
{
const auto salt = drogon::utils::getUuid() + drogon::utils::getUuid();
return "pbkdf2_sha256$" + std::to_string(passwordIterations) + "$" + salt +
"$" + pbkdf2(password, salt, passwordIterations);
}
bool verifyPassword(const std::string &password, const std::string &hash)
{
constexpr std::string_view pbkdf2Prefix = "pbkdf2_sha256$";
if (!hash.starts_with(pbkdf2Prefix))
{
return false;
}
const auto iterationsStart = pbkdf2Prefix.size();
const auto saltStart = hash.find('$', iterationsStart);
if (saltStart == std::string::npos)
{
return false;
}
const auto digestStart = hash.find('$', saltStart + 1);
if (digestStart == std::string::npos)
{
return false;
}
const auto iterations =
std::stoi(hash.substr(iterationsStart, saltStart - iterationsStart));
const auto salt = hash.substr(saltStart + 1, digestStart - saltStart - 1);
const auto expected = "pbkdf2_sha256$" + std::to_string(iterations) + "$" +
salt + "$" + pbkdf2(password, salt, iterations);
return expected == hash;
}
std::string hashSecret(const std::string &secret) std::string hashSecret(const std::string &secret)
{ {
return sha256("visione:" + secret); return sha256("visione:" + secret);
-3
View File
@@ -7,8 +7,5 @@ namespace visione::crypto
std::string sha256(const std::string &value); std::string sha256(const std::string &value);
std::string randomNumericCode(int digits); std::string randomNumericCode(int digits);
std::string randomToken(); std::string randomToken();
std::string hashPassword(const std::string &password);
bool verifyPassword(const std::string &password, const std::string &hash);
std::string hashSecret(const std::string &secret); std::string hashSecret(const std::string &secret);
} // namespace visione::crypto } // namespace visione::crypto
+15 -28
View File
@@ -33,19 +33,6 @@ std::optional<visione::auth::UserContext> requireUser(
return user; 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, void sendAdmin(Json::Value body,
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
@@ -86,7 +73,7 @@ void AdminController::createCategory(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -100,7 +87,7 @@ void AdminController::createArea(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -114,7 +101,7 @@ void AdminController::createTeam(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -129,7 +116,7 @@ void AdminController::updateCategory(
long long categoryId) long long categoryId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -144,7 +131,7 @@ void AdminController::deleteCategory(
long long categoryId) long long categoryId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -158,7 +145,7 @@ void AdminController::updateArea(
long long areaId) long long areaId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -173,7 +160,7 @@ void AdminController::deleteArea(
long long areaId) long long areaId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -187,7 +174,7 @@ void AdminController::updateTeam(
long long teamId) long long teamId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -202,7 +189,7 @@ void AdminController::deleteTeam(
long long teamId) long long teamId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -215,7 +202,7 @@ void AdminController::overviewStats(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -228,7 +215,7 @@ void AdminController::listModeration(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -246,7 +233,7 @@ void AdminController::updateModeration(
long long flagId) long long flagId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -260,7 +247,7 @@ void AdminController::listUsers(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -274,7 +261,7 @@ void AdminController::updateUserRoles(
long long userId) long long userId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -289,7 +276,7 @@ void AdminController::updateUserBan(
long long userId) long long userId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
-37
View File
@@ -66,43 +66,6 @@ void AuthController::verifyOtp(
serviceResponse(service.verifyOtp(body), std::move(callback)); serviceResponse(service.verifyOtp(body), std::move(callback));
} }
void AuthController::loginWithPassword(
const drogon::HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
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.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( void AuthController::me(
const drogon::HttpRequestPtr &req, const drogon::HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
-12
View File
@@ -15,12 +15,6 @@ class AuthController : public drogon::HttpController<AuthController>
ADD_METHOD_TO(AuthController::verifyOtp, ADD_METHOD_TO(AuthController::verifyOtp,
"/api/v1/auth/otp/verify", "/api/v1/auth/otp/verify",
drogon::Post); drogon::Post);
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); ADD_METHOD_TO(AuthController::me, "/api/v1/me", drogon::Get);
ADD_METHOD_TO(AuthController::editUser, "/api/v1/me", drogon::Patch); ADD_METHOD_TO(AuthController::editUser, "/api/v1/me", drogon::Patch);
METHOD_LIST_END METHOD_LIST_END
@@ -34,12 +28,6 @@ class AuthController : public drogon::HttpController<AuthController>
void verifyOtp( void verifyOtp(
const drogon::HttpRequestPtr &req, const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback); std::function<void(const drogon::HttpResponsePtr &)> &&callback);
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, void me(const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback); std::function<void(const drogon::HttpResponsePtr &)> &&callback);
void editUser(const drogon::HttpRequestPtr &req, void editUser(const drogon::HttpRequestPtr &req,
+21 -34
View File
@@ -36,19 +36,6 @@ std::optional<visione::auth::UserContext> requireUser(
return user; 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, void sendService(Json::Value body,
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
@@ -119,7 +106,7 @@ void ReportController::create(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -139,7 +126,7 @@ void ReportController::list(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -157,7 +144,7 @@ void ReportController::listCategories(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -170,7 +157,7 @@ void ReportController::listTeams(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -183,7 +170,7 @@ void ReportController::assignedReports(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -199,7 +186,7 @@ void ReportController::openReports(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -215,7 +202,7 @@ void ReportController::otherOperatorReports(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -232,7 +219,7 @@ void ReportController::detail(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -246,7 +233,7 @@ void ReportController::interventionSummary(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -260,7 +247,7 @@ void ReportController::teamSuggestions(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -274,7 +261,7 @@ void ReportController::createInterventionLink(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -288,7 +275,7 @@ void ReportController::revokeInterventionLink(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -302,7 +289,7 @@ void ReportController::updateStatus(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -317,7 +304,7 @@ void ReportController::takeReport(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -331,7 +318,7 @@ void ReportController::addMessage(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -346,7 +333,7 @@ void ReportController::listMessages(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -361,7 +348,7 @@ void ReportController::flagMessage(
long long messageId) long long messageId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -377,7 +364,7 @@ void ReportController::assignTeam(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -392,7 +379,7 @@ void ReportController::addFeedback(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -407,7 +394,7 @@ void ReportController::flagReport(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -422,7 +409,7 @@ void ReportController::uploadMedia(
long long reportId) long long reportId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
+4 -17
View File
@@ -32,19 +32,6 @@ std::optional<visione::auth::UserContext> requireUser(
} }
return user; 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;
}
} // namespace } // namespace
void UserController::listNotifications( void UserController::listNotifications(
@@ -52,7 +39,7 @@ void UserController::listNotifications(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -68,7 +55,7 @@ void UserController::markNotificationRead(
long long notificationId) long long notificationId)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -84,7 +71,7 @@ void UserController::markAllNotificationsRead(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
@@ -97,7 +84,7 @@ void UserController::rewards(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
auto user = requireUser(req, callback); auto user = requireUser(req, callback);
if (!user || blockPasswordChangeRequired(*user, callback)) if (!user)
{ {
return; return;
} }
+1 -5
View File
@@ -30,15 +30,11 @@ Authorization: Bearer <token>
## Auth + Profile ## Auth + Profile
- `POST /auth/register` - `POST /auth/register`
citizen registration citizen registration without password
- `POST /auth/otp/request` - `POST /auth/otp/request`
sends OTP through Mailpit sends OTP through Mailpit
- `POST /auth/otp/verify` - `POST /auth/otp/verify`
consumes OTP, returns bearer token consumes OTP, returns bearer token
- `POST /auth/login/password`
password fallback login
- `POST /auth/change-password`
authenticated password change, also clears `must_change_password`
- `GET /me` - `GET /me`
current user profile, roles, permissions, points current user profile, roles, permissions, points
- `PATCH /me` - `PATCH /me`
-2
View File
@@ -25,7 +25,6 @@ generate public read-only intervention links.
## Runtime Model ## Runtime Model
- Auth uses bearer tokens from `api_tokens`. - Auth uses bearer tokens from `api_tokens`.
- OTP login uses `otp_challenges` and Mailpit in local dev. - OTP login uses `otp_challenges` and Mailpit in local dev.
- Password hash uses PBKDF2-HMAC-SHA256. Legacy `sha256$` hashes rejected.
- DB access uses raw parameterized SQL through `drogon::orm::DbClient`. - DB access uses raw parameterized SQL through `drogon::orm::DbClient`.
- No Drogon generated ORM models. No `Mapper<T>`. - No Drogon generated ORM models. No `Mapper<T>`.
@@ -37,7 +36,6 @@ generate public read-only intervention links.
Effective permission helpers: Effective permission helpers:
- `auth::canManageReports(user)`: admin or `reports.manage` - `auth::canManageReports(user)`: admin or `reports.manage`
- `auth::canOperateReports(user)`: non-admin report manager - `auth::canOperateReports(user)`: non-admin report manager
- `auth::mustChangePassword(user)`: blocks protected operational/admin flows
## Workflow Ownership Rules ## Workflow Ownership Rules
- `reports.assigned_operator_id` is single platform owner of report. - `reports.assigned_operator_id` is single platform owner of report.
+1 -1
View File
@@ -14,7 +14,7 @@ Schema truth is `migrations/*.sql`. This file is compact human map of current DB
## Core Auth Tables ## Core Auth Tables
- `users` - `users`
identity, profile, password hash, points, `banned`, `must_change_password` identity, profile, points, `banned`
- `roles` - `roles`
- `permissions` - `permissions`
- `role_permissions` - `role_permissions`
+1 -3
View File
@@ -29,12 +29,10 @@ public revocable link generated by operator/admin.
## Current Auth Model ## Current Auth Model
- Registration creates `citizen`. - Registration creates `citizen`.
- Login paths: - Login path:
- OTP email via Mailpit - OTP email via Mailpit
- password fallback
- Auth token: bearer token from `api_tokens` - Auth token: bearer token from `api_tokens`
- Banned users cannot authenticate. - Banned users cannot authenticate.
- `must_change_password` blocks most protected operator/admin flows until changed.
## Current Report Workflow ## Current Report Workflow
### Citizen flow ### Citizen flow
-2
View File
@@ -29,9 +29,7 @@ moderate abuse, and inspect operational stats.
### Auth ### Auth
- citizen registration - citizen registration
- OTP login through Mailpit - OTP login through Mailpit
- password login fallback
- bearer token auth - bearer token auth
- forced first password change for seed admin
- profile read + limited self-edit (`first_name`, `last_name`, `phone`) - profile read + limited self-edit (`first_name`, `last_name`, `phone`)
### Reports ### Reports
+3 -4
View File
@@ -17,9 +17,9 @@ Dopodichè ho elaborato una serie di feature che il sistema deve implementare an
Feature Segnalazioni urbane Feature Segnalazioni urbane
– Lo sviluppo del della webapp avverrà su una istanza self hosted di Gitea come gesto di ribellione nei confronti di github che userà i dati sulle repo per trainare i modelli di IA (lo faceva già prima ma ora lo dice) – Lo sviluppo del della webapp avverrà su una istanza self hosted di Gitea come gesto di ribellione nei confronti di github che userà i dati sulle repo per trainare i modelli di IA (lo faceva già prima ma ora lo dice)
– Il progetto sarà diviso tra frontend e backend. Frontend in probabilmente Vue.js e backend in C++, nello specifico Dragon è stato scelto come framework. Abbiamo scelto di sviluppare una webapp perchè il punto importante di un progetto del genere è la versatilità, la compatibilità su tutti i sistemi e la velocità. – Il progetto sarà diviso tra frontend e backend. Frontend in probabilmente Vue.js e backend in C++, nello specifico Dragon è stato scelto come framework. Abbiamo scelto di sviluppare una webapp perchè il punto importante di un progetto del genere è la versatilità, la compatibilità su tutti i sistemi e la velocità.
– Deve esistere un account amministratore generato dal sistema, appena ci si logga la prima volta ci chiederà di cambiare password, e questo profilo lo dovrà teoricamente avere solo chi il tecnico che gestisce la piattaforma di quel comune, dato che da questo profilo si può fare tutto. E’ diverso da un account operatore perchè l’operatore ha meno permessi rispetto ad un admin, l’admin può decidere chi sono gli operatori. (NOTA: eventualmente ci possono essere i permessi customizzabili, quindi l’admin può creare anche altri utenti che hanno permessi in più) – Deve esistere un account amministratore generato dal sistema. Questo profilo lo dovrà teoricamente avere solo chi il tecnico che gestisce la piattaforma di quel comune, dato che da questo profilo si può fare tutto. E’ diverso da un account operatore perchè l’operatore ha meno permessi rispetto ad un admin, l’admin può decidere chi sono gli operatori. (NOTA: eventualmente ci possono essere i permessi customizzabili, quindi l’admin può creare anche altri utenti che hanno permessi in più)
– Deve essere possibile registrarsi alla piattaforma con tutti i quanti i dati anagrafici possibili e immaginabili, sarebbe figo avere un sistema che dato il codice fiscale ti auto completa i dati deducibili da quest’ultimo. Integrazione frontend con google maps per l’indirizzo. Deve essere possibile anche collegare CIE. Nella registrazione si richiede verifica a due fattori – Deve essere possibile registrarsi alla piattaforma con tutti i quanti i dati anagrafici possibili e immaginabili, sarebbe figo avere un sistema che dato il codice fiscale ti auto completa i dati deducibili da quest’ultimo. Integrazione frontend con google maps per l’indirizzo. Deve essere possibile anche collegare CIE. Nella registrazione si richiede verifica a due fattori
– Il login deve essere effettuato con email o CIE. Nel caso di email la modalità di default non richiede una password ma un OTP, in quanto dimostrato essere più sicuro. (La password è comunque obbligatoria da settare) – Il login deve essere effettuato con email o CIE. Nel caso di email la modalità di default usa OTP, in quanto dimostrato essere più sicuro.
– Una volta registrati, se siamo un operatore (comunale, poliziotto, pompiere o chiunque) sarà un admin o un’altro operatore con abbastanza permessi ad assegnarci a quel ruolo e sbloccare i permessi per accedere ad altre parti della piattaforma. – Una volta registrati, se siamo un operatore (comunale, poliziotto, pompiere o chiunque) sarà un admin o un’altro operatore con abbastanza permessi ad assegnarci a quel ruolo e sbloccare i permessi per accedere ad altre parti della piattaforma.
– Admin/operatori vari devono poter creare le varie zone in base al perimetro, se questo è fattibile tramite una mappa sarebbe l’ideale, assegnare i vari nomi ecc.. Questo serve sia per le segnalazioni e per l’assegnazione dell’agente/squadra che deve intervenire sia per fini statistici – Admin/operatori vari devono poter creare le varie zone in base al perimetro, se questo è fattibile tramite una mappa sarebbe l’ideale, assegnare i vari nomi ecc.. Questo serve sia per le segnalazioni e per l’assegnazione dell’agente/squadra che deve intervenire sia per fini statistici
– Al sito non si registrano solo gli agenti che intervengono o gli operatori ma anche i cittadini, che possono effettuare delle segnalazioni. Le segnalazioni devono avere una foto, la posizione con coordinate geografiche e via con numero. Il sistema automaticamente capisce a che zona del comune fa parte – Al sito non si registrano solo gli agenti che intervengono o gli operatori ma anche i cittadini, che possono effettuare delle segnalazioni. Le segnalazioni devono avere una foto, la posizione con coordinate geografiche e via con numero. Il sistema automaticamente capisce a che zona del comune fa parte
@@ -69,7 +69,6 @@ erDiagram
string nome string nome
string cognome string cognome
string email UK string email UK
string password_hash
string codice_fiscale UK string codice_fiscale UK
date data_nascita date data_nascita
string luogo_nascita string luogo_nascita
@@ -165,4 +164,4 @@ erDiagram
int utente_id FK int utente_id FK
text testo text testo
datetime data_invio datetime data_invio
} }
+1 -134
View File
@@ -66,9 +66,6 @@
"last_name": { "last_name": {
"type": "string" "type": "string"
}, },
"must_change_password": {
"type": "boolean"
},
"phone": { "phone": {
"type": "string" "type": "string"
}, },
@@ -191,25 +188,6 @@
], ],
"type": "object" "type": "object"
}, },
"ChangePasswordRequest": {
"additionalProperties": false,
"properties": {
"current_password": {
"format": "password",
"type": "string"
},
"new_password": {
"format": "password",
"minLength": 10,
"type": "string"
}
},
"required": [
"current_password",
"new_password"
],
"type": "object"
},
"CreateAreaRequest": { "CreateAreaRequest": {
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
@@ -347,9 +325,6 @@
"last_name": { "last_name": {
"type": "string" "type": "string"
}, },
"must_change_password": {
"type": "boolean"
},
"permissions": { "permissions": {
"items": { "items": {
"type": "string" "type": "string"
@@ -379,7 +354,6 @@
"email", "email",
"fiscal_code", "fiscal_code",
"points", "points",
"must_change_password",
"role", "role",
"roles", "roles",
"permissions" "permissions"
@@ -1126,24 +1100,6 @@
], ],
"type": "object" "type": "object"
}, },
"PasswordLoginRequest": {
"additionalProperties": false,
"properties": {
"email": {
"format": "email",
"type": "string"
},
"password": {
"format": "password",
"type": "string"
}
},
"required": [
"email",
"password"
],
"type": "object"
},
"ReadyResponse": { "ReadyResponse": {
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
@@ -1207,11 +1163,6 @@
"last_name": { "last_name": {
"type": "string" "type": "string"
}, },
"password": {
"format": "password",
"minLength": 10,
"type": "string"
},
"phone": { "phone": {
"type": "string" "type": "string"
} }
@@ -1220,7 +1171,6 @@
"first_name", "first_name",
"last_name", "last_name",
"email", "email",
"password",
"fiscal_code" "fiscal_code"
], ],
"type": "object" "type": "object"
@@ -2434,89 +2384,6 @@
] ]
} }
}, },
"/api/v1/auth/change-password": {
"post": {
"description": "Changes the authenticated user's password.",
"operationId": "AuthController.changePassword",
"parameters": [ ],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChangePasswordRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
},
"description": "Password change result."
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
}
},
"security": [
{
"bearerAuth": [ ]
}
],
"summary": "Change password",
"tags": [
"Auth"
]
}
},
"/api/v1/auth/login/password": {
"post": {
"description": "Password-based login fallback for local development.",
"operationId": "AuthController.loginWithPassword",
"parameters": [ ],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PasswordLoginRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TokenResponse"
}
}
},
"description": "Bearer token when credentials are valid."
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
},
"summary": "Password login",
"tags": [
"Auth"
]
}
},
"/api/v1/auth/otp/request": { "/api/v1/auth/otp/request": {
"post": { "post": {
"description": "Sends a one-time code by email through Mailpit locally.", "description": "Sends a one-time code by email through Mailpit locally.",
@@ -2591,7 +2458,7 @@
}, },
"/api/v1/auth/register": { "/api/v1/auth/register": {
"post": { "post": {
"description": "Creates a citizen account with personal data and password.", "description": "Creates a citizen account with personal data.",
"operationId": "AuthController.registerCitizen", "operationId": "AuthController.registerCitizen",
"parameters": [ ], "parameters": [ ],
"requestBody": { "requestBody": {
+2 -8
View File
@@ -3,14 +3,12 @@ CREATE TABLE IF NOT EXISTS users (
first_name VARCHAR(100) NOT NULL, first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
fiscal_code VARCHAR(16) NOT NULL, fiscal_code VARCHAR(16) NOT NULL,
birth_date DATE NULL, birth_date DATE NULL,
birth_place VARCHAR(150) NULL, birth_place VARCHAR(150) NULL,
phone VARCHAR(30) NULL, phone VARCHAR(30) NULL,
points INT NOT NULL DEFAULT 0, points INT NOT NULL DEFAULT 0,
banned BOOLEAN NOT NULL DEFAULT FALSE, banned BOOLEAN NOT NULL DEFAULT FALSE,
must_change_password BOOLEAN NOT NULL DEFAULT FALSE,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY users_email_unique (email), UNIQUE KEY users_email_unique (email),
@@ -223,18 +221,14 @@ INSERT IGNORE INTO users (
first_name, first_name,
last_name, last_name,
email, email,
password_hash,
fiscal_code, fiscal_code,
phone, phone
must_change_password
) VALUES ( ) VALUES (
'Gennario', 'Gennario',
'Vessio', 'Vessio',
'[email protected]', '[email protected]',
'pbkdf2_sha256$210000$a2c43ef09bd24ea883023e5f57fa6255d8dd317e312a40ddaa1bd3ccd83dbfa5$26D0C9000B603A424C7FB0DA3BE0CDC1798A9CBA34F391F9B8E4C0E1E4A58806',
'VSSGNR94M08A662P', 'VSSGNR94M08A662P',
'380 6180216', '380 6180216'
TRUE
); );
INSERT IGNORE INTO user_roles (user_id, role_id) INSERT IGNORE INTO user_roles (user_id, role_id)
+1 -3
View File
@@ -3,7 +3,5 @@ SET email = '[email protected]',
first_name = 'Gennario', first_name = 'Gennario',
last_name = 'Vessio', last_name = 'Vessio',
fiscal_code = 'VSSGNR94M08A662P', fiscal_code = 'VSSGNR94M08A662P',
phone = '380 6180216', phone = '380 6180216'
password_hash = 'pbkdf2_sha256$210000$a2c43ef09bd24ea883023e5f57fa6255d8dd317e312a40ddaa1bd3ccd83dbfa5$26D0C9000B603A424C7FB0DA3BE0CDC1798A9CBA34F391F9B8E4C0E1E4A58806',
must_change_password = TRUE
WHERE email IN ('[email protected]', '[email protected]'); WHERE email IN ('[email protected]', '[email protected]');
+1 -3
View File
@@ -3,7 +3,5 @@ SET email = '[email protected]',
first_name = 'Gennario', first_name = 'Gennario',
last_name = 'Vessio', last_name = 'Vessio',
fiscal_code = 'VSSGNR94M08A662P', fiscal_code = 'VSSGNR94M08A662P',
phone = '380 6180216', phone = '380 6180216'
password_hash = 'pbkdf2_sha256$210000$a2c43ef09bd24ea883023e5f57fa6255d8dd317e312a40ddaa1bd3ccd83dbfa5$26D0C9000B603A424C7FB0DA3BE0CDC1798A9CBA34F391F9B8E4C0E1E4A58806',
must_change_password = TRUE
WHERE email IN ('[email protected]', '[email protected]'); WHERE email IN ('[email protected]', '[email protected]');
+3
View File
@@ -0,0 +1,3 @@
ALTER TABLE users
DROP COLUMN password_hash,
DROP COLUMN must_change_password;
+3 -4
View File
@@ -80,8 +80,7 @@ const JsonRowSchema &adminUserSchema()
static const JsonRowSchema schema{ static const JsonRowSchema schema{
{"id", JsonFieldType::UInt64}, {"id", JsonFieldType::UInt64},
{"points", JsonFieldType::Int64}, {"points", JsonFieldType::Int64},
{"banned", JsonFieldType::Bool}, {"banned", JsonFieldType::Bool}};
{"must_change_password", JsonFieldType::Bool}};
return schema; return schema;
} }
@@ -591,13 +590,13 @@ Json::Value AdminService::listUsers(const auth::UserContext &user)
response["users"] = repositories::resultToJson( response["users"] = repositories::resultToJson(
drogon::app().getDbClient()->execSqlSync( drogon::app().getDbClient()->execSqlSync(
"SELECT u.id, u.first_name, u.last_name, u.email, u.fiscal_code, " "SELECT u.id, u.first_name, u.last_name, u.email, u.fiscal_code, "
"u.phone, u.points, u.banned, u.must_change_password, " "u.phone, u.points, u.banned, "
"GROUP_CONCAT(r.name ORDER BY r.name SEPARATOR ',') AS roles " "GROUP_CONCAT(r.name ORDER BY r.name SEPARATOR ',') AS roles "
"FROM users u " "FROM users u "
"LEFT JOIN user_roles ur ON ur.user_id = u.id " "LEFT JOIN user_roles ur ON ur.user_id = u.id "
"LEFT JOIN roles r ON r.id = ur.role_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, " "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 " "u.phone, u.points, u.banned "
"ORDER BY u.created_at DESC LIMIT 200"), "ORDER BY u.created_at DESC LIMIT 200"),
adminUserSchema()); adminUserSchema());
return response; return response;
+7 -65
View File
@@ -1,9 +1,10 @@
#include "services/AuthService.h" #include "services/AuthService.h"
#include "common/Crypto.h"
#include "common/Email.h" #include "common/Email.h"
#include "common/Validation.h" #include "common/Validation.h"
#include "common/Crypto.h"
#include <drogon/drogon.h> #include <drogon/drogon.h>
namespace visione::services namespace visione::services
@@ -62,7 +63,7 @@ Json::Value issueToken(long long userId)
Json::Value AuthService::registerCitizen(const Json::Value &body) Json::Value AuthService::registerCitizen(const Json::Value &body)
{ {
const auto missing = validation::missingStrings( const auto missing = validation::missingStrings(
body, {"first_name", "last_name", "email", "password", "fiscal_code"}); body, {"first_name", "last_name", "email", "fiscal_code"});
if (!missing.empty()) if (!missing.empty())
{ {
auto response = failure("missing_fields"); auto response = failure("missing_fields");
@@ -96,12 +97,11 @@ Json::Value AuthService::registerCitizen(const Json::Value &body)
db->execSqlSync( db->execSqlSync(
"INSERT INTO users " "INSERT INTO users "
"(first_name, last_name, email, password_hash, fiscal_code, birth_date, " "(first_name, last_name, email, fiscal_code, birth_date, "
"birth_place, phone) VALUES (?, ?, ?, ?, ?, NULLIF(?, ''), ?, ?)", "birth_place, phone) VALUES (?, ?, ?, ?, NULLIF(?, ''), ?, ?)",
body["first_name"].asString(), body["first_name"].asString(),
body["last_name"].asString(), body["last_name"].asString(),
email, email,
crypto::hashPassword(body["password"].asString()),
fiscalCode, fiscalCode,
body.get("birth_date", "").asString(), body.get("birth_date", "").asString(),
body.get("birth_place", "").asString(), body.get("birth_place", "").asString(),
@@ -183,68 +183,12 @@ Json::Value AuthService::verifyOtp(const Json::Value &body)
return issueToken(challenge[0]["user_id"].as<long long>()); return issueToken(challenge[0]["user_id"].as<long long>());
} }
Json::Value AuthService::loginWithPassword(const Json::Value &body)
{
const auto missing = validation::missingStrings(body, {"email", "password"});
if (!missing.empty())
{
return failure("missing_fields");
}
auto db = drogon::app().getDbClient();
const auto user = db->execSqlSync(
"SELECT id, password_hash FROM users WHERE email = ? AND banned = FALSE "
"LIMIT 1",
body["email"].asString());
if (user.empty() ||
!crypto::verifyPassword(body["password"].asString(),
user[0]["password_hash"].as<std::string>()))
{
return failure("invalid_credentials");
}
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) Json::Value AuthService::currentUser(const auth::UserContext &user)
{ {
auto db = drogon::app().getDbClient(); auto db = drogon::app().getDbClient();
const auto result = db->execSqlSync( const auto result = db->execSqlSync(
"SELECT id, first_name, last_name, email, fiscal_code, phone, points, " "SELECT id, first_name, last_name, email, fiscal_code, phone, points "
"must_change_password FROM users WHERE id = ?", "FROM users WHERE id = ?",
user.id); user.id);
auto response = success(); auto response = success();
@@ -257,8 +201,6 @@ Json::Value AuthService::currentUser(const auth::UserContext &user)
response["user"]["fiscal_code"] = result[0]["fiscal_code"].as<std::string>(); response["user"]["fiscal_code"] = result[0]["fiscal_code"].as<std::string>();
response["user"]["phone"] = result[0]["phone"].as<std::string>(); response["user"]["phone"] = result[0]["phone"].as<std::string>();
response["user"]["points"] = result[0]["points"].as<int>(); response["user"]["points"] = result[0]["points"].as<int>();
response["user"]["must_change_password"] =
result[0]["must_change_password"].as<bool>();
response["user"]["role"] = user.role; response["user"]["role"] = user.role;
for (const auto &role : user.roles) for (const auto &role : user.roles)
{ {
-2
View File
@@ -12,8 +12,6 @@ class AuthService
Json::Value registerCitizen(const Json::Value &body); Json::Value registerCitizen(const Json::Value &body);
Json::Value requestOtp(const Json::Value &body); Json::Value requestOtp(const Json::Value &body);
Json::Value verifyOtp(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); Json::Value currentUser(const auth::UserContext &user);
Json::Value editUser(const auth::UserContext & user, Json::Value::const_iterator::reference body); Json::Value editUser(const auth::UserContext & user, Json::Value::const_iterator::reference body);
}; };
-14
View File
@@ -65,7 +65,6 @@ DROGON_TEST(SqlScalarSerializationKeepsTypes)
Json::Value value(Json::objectValue); Json::Value value(Json::objectValue);
value["id"] = Json::UInt64(7); value["id"] = Json::UInt64(7);
value["banned"] = false; value["banned"] = false;
value["must_change_password"] = true;
value["points"] = Json::Int64(42); value["points"] = Json::Int64(42);
value["phone"] = "380 6180216"; value["phone"] = "380 6180216";
return value; return value;
@@ -73,7 +72,6 @@ DROGON_TEST(SqlScalarSerializationKeepsTypes)
CHECK(json["id"].isUInt64()); CHECK(json["id"].isUInt64());
CHECK(json["banned"].isBool()); CHECK(json["banned"].isBool());
CHECK(json["must_change_password"].isBool());
CHECK(json["points"].isInt64()); CHECK(json["points"].isInt64());
CHECK(json["phone"].isString()); CHECK(json["phone"].isString());
} }
@@ -163,18 +161,6 @@ DROGON_TEST(GeoJsonAcceptsContainedPolygon)
CHECK(!visione::geojson::polygonInsideGeometry(candidate, container)); CHECK(!visione::geojson::polygonInsideGeometry(candidate, container));
} }
DROGON_TEST(PasswordHashSupportsPbkdf2Only)
{
const auto hash = visione::crypto::hashPassword("CorrectHorse123!");
CHECK(hash.rfind("pbkdf2_sha256$", 0) == 0);
CHECK(visione::crypto::verifyPassword("CorrectHorse123!", hash));
CHECK(!visione::crypto::verifyPassword("wrong", hash));
const std::string legacy =
"sha256$local-dev-salt$CD4FF6AC8C9685D08E55AF693970F15F9BFCD4C0DC9F4D3001D202C9DB00291F";
CHECK(!visione::crypto::verifyPassword("ChangeMe123!", legacy));
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
std::promise<void> startedPromise; std::promise<void> startedPromise;