[Server] Serve account self-service updates through the database interface (#7848)

cmdAccountEdit assembled a dynamic UPDATE for the real name, email and country
columns in the socket handler, and cmdAccountImage wrote the avatar blob there
too. Add AccountUpdateRecord, updateUserAccount and setUserAvatar to
Server_DatabaseInterface with fail-closed defaults, implement them with the
previous query text and bind values, and keep the password gate, the email
permission check, the empty-edit early return and the in-memory userInfo updates
in the handler.
This commit is contained in:
BruebachL 2026-11-10 12:16:29 +01:00 • committed by GitHub
parent 8c8dd0c072
commit f7c6e81347
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 75 additions and 30 deletions

View file

@ -774,6 +774,24 @@ public:
virtual void setForcePasswordChange(const QString & /* user */, bool /* force */)
{
}
/** @brief The account fields a self-service edit may change; unset fields are left alone. */
struct AccountUpdateRecord
{
bool hasRealName = false;
QString realName;
bool hasEmail = false;
QString email;
bool hasCountry = false;
QString country;
};
virtual bool updateUserAccount(const QString & /* userName */, const AccountUpdateRecord & /* update */)
{
return false;
}
virtual bool setUserAvatar(int /* userId */, const QByteArray & /* image */)
{
return false;
}
/** @brief Outcome of reading or writing one account field. */
enum class UserFieldResult
{

View file

@ -2244,6 +2244,51 @@ void Servatrice_DatabaseInterface::setForcePasswordChange(const QString &user, b
execSqlQuery(query);
}
bool Servatrice_DatabaseInterface::updateUserAccount(const QString &userName, const AccountUpdateRecord &update)
{
if (!checkSql()) {
return false;
}
QStringList queryList;
if (update.hasRealName) {
queryList << "realname=:realName";
}
if (update.hasEmail) {
queryList << "email=:email";
}
if (update.hasCountry) {
queryList << "country=:country";
}
const QString queryText = QString("update {prefix}_users set %1 where name=:userName").arg(queryList.join(", "));
QSqlQuery *query = prepareQuery(queryText);
if (update.hasRealName) {
query->bindValue(":realName", update.realName);
}
if (update.hasEmail) {
query->bindValue(":email", update.email);
}
if (update.hasCountry) {
query->bindValue(":country", update.country);
}
query->bindValue(":userName", userName);
return execSqlQuery(query);
}
bool Servatrice_DatabaseInterface::setUserAvatar(int userId, const QByteArray &image)
{
if (!checkSql()) {
return false;
}
QSqlQuery *query = prepareQuery("update {prefix}_users set avatar_bmp=:image where id=:id");
query->bindValue(":image", image);
query->bindValue(":id", userId);
return execSqlQuery(query);
}
bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user,
const QString &oldPassword,
bool oldPasswordNeedsHash,

View file

@ -223,6 +223,8 @@ public:
const QString &newPassword,
bool newPasswordNeedsHash) override;
void setForcePasswordChange(const QString &user, bool force) override;
bool updateUserAccount(const QString &userName, const AccountUpdateRecord &update) override;
bool setUserAvatar(int userId, const QByteArray &image) override;
QList<ServerInfo_Ban> getUserBanHistory(const QString &userName) override;
bool addWarning(const QString userName,
const QString adminName,

View file

@ -2903,43 +2903,26 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountEdit(const Comma
}
}
QStringList queryList({});
if (cmd.has_real_name()) {
queryList << "realname=:realName";
}
Server_DatabaseInterface::AccountUpdateRecord update;
update.hasRealName = cmd.has_real_name();
update.realName = realName;
update.hasCountry = cmd.has_country();
update.country = country;
if (cmd.has_email()) {
// a real password is required in order to change the email address
if (usingRealPassword || checkedPassword) {
queryList << "email=:email";
update.hasEmail = true;
update.email = parsedEmailAddress;
} else {
return Response::RespFunctionNotAllowed;
}
}
if (cmd.has_country()) {
queryList << "country=:country";
}
if (queryList.isEmpty()) {
if (!update.hasRealName && !update.hasEmail && !update.hasCountry) {
return Response::RespOk;
}
QString queryText = QString("update {prefix}_users set %1 where name=:userName").arg(queryList.join(", "));
QSqlQuery *query = sqlInterface->prepareQuery(queryText);
if (cmd.has_real_name()) {
auto _realName = nameFromStdString(cmd.real_name());
query->bindValue(":realName", _realName);
}
if (cmd.has_email()) {
const auto _parsedEmailAddress = EmailParser::getParsedEmailAddress(nameFromStdString(cmd.email()));
query->bindValue(":email", _parsedEmailAddress);
}
if (cmd.has_country()) {
auto _country = nameFromStdString(cmd.country());
query->bindValue(":country", _country);
}
query->bindValue(":userName", userName);
if (!sqlInterface->execSqlQuery(query)) {
if (!databaseInterface->updateUserAccount(userName, update)) {
return Response::RespInternalError;
}
@ -2967,10 +2950,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountImage(const Comm
QByteArray image(cmd.image().c_str(), length);
int id = userInfo->id();
QSqlQuery *query = sqlInterface->prepareQuery("update {prefix}_users set avatar_bmp=:image where id=:id");
query->bindValue(":image", image);
query->bindValue(":id", id);
if (!sqlInterface->execSqlQuery(query)) {
if (!databaseInterface->setUserAvatar(id, image)) {
return Response::RespInternalError;
}