mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-28 00:42:19 -07:00
[Server/Client/Protocol] Address PR #7091 review: security, bug, and perf fixes
Security: - Promote RESET_USER_PASSWORD to admin-only dispatch (was moderator-accessible) - Reject password reset on users with equal/higher privilege than caller - Notify affected user via Event_NotifyUser::CUSTOM when password is reset - Add server-side category whitelist for reports - Drop reporter name fallback in comment/details authorization (ID-only) - Force password change: new DB column + login enforcement + client disconnect Bugs: - XSS via QTextEdit::append() → insertPlainText() in report tab and utils - allNotified initialized to true even with empty recipients list - Warning combo box: use currentData() instead of baked-in display text - Report resolution now records who resolved (resolved_by column + audit) Performance: - IP-correlation subquery: add 6-month window + LIMIT 200 - getUserSessions: clamp limit to 500 Non-blocking: - Palette-aware colors in report_utils.cpp (dark/light mode) - Report list pagination: offset/limit fields + total_count in response - SessionCommand enum gap comment for reserved values 1201-1203 Schema: 36→37 (force_password_change), 37→38 (resolved_by) Took 12 minutes Took 16 seconds
This commit is contained in:
parent
c6d8bce81f
commit
f7998deb7a
18 changed files with 181 additions and 49 deletions
|
|
@ -296,6 +296,15 @@ void ConnectionController::onLoginError(int r,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Response::RespPasswordChangeRequired: {
|
||||||
|
QMessageBox::information(
|
||||||
|
dialogParent, tr("Password Change Required"),
|
||||||
|
tr("An administrator has reset your password. Please contact your server administrator to obtain "
|
||||||
|
"your temporary password, then log in and change it via Account -> Change Password."));
|
||||||
|
remoteClient->disconnectFromServer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
case Response::RespServerFull: {
|
case Response::RespServerFull: {
|
||||||
QMessageBox::critical(dialogParent, tr("Server Full"),
|
QMessageBox::critical(dialogParent, tr("Server Full"),
|
||||||
tr("The server has reached its maximum user capacity, please check back later."));
|
tr("The server has reached its maximum user capacity, please check back later."));
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ WarningDialog::WarningDialog(const QString userName, const QString clientID, QWi
|
||||||
warnClientID = new QLineEdit(clientID);
|
warnClientID = new QLineEdit(clientID);
|
||||||
warnClientID->setMaxLength(MAX_NAME_LENGTH);
|
warnClientID->setMaxLength(MAX_NAME_LENGTH);
|
||||||
warningOption = new QComboBox();
|
warningOption = new QComboBox();
|
||||||
warningOption->addItem("");
|
warningOption->addItem("", "");
|
||||||
|
|
||||||
deleteMessages = new QCheckBox(tr("Redact all messages from this user in all rooms"));
|
deleteMessages = new QCheckBox(tr("Redact all messages from this user in all rooms"));
|
||||||
|
|
||||||
|
|
@ -184,7 +184,7 @@ void WarningDialog::okClicked()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (warningOption->currentText().simplified().isEmpty()) {
|
if (warningOption->currentData().toString().simplified().isEmpty()) {
|
||||||
QMessageBox::critical(this, tr("Error"),
|
QMessageBox::critical(this, tr("Error"),
|
||||||
tr("Warning to use can not be blank, please select a valid warning to send."));
|
tr("Warning to use can not be blank, please select a valid warning to send."));
|
||||||
return;
|
return;
|
||||||
|
|
@ -205,7 +205,7 @@ QString WarningDialog::getWarnID() const
|
||||||
|
|
||||||
QString WarningDialog::getReason() const
|
QString WarningDialog::getReason() const
|
||||||
{
|
{
|
||||||
return warningOption->currentText().simplified();
|
return warningOption->currentData().toString().simplified();
|
||||||
}
|
}
|
||||||
|
|
||||||
int WarningDialog::getDeleteMessages() const
|
int WarningDialog::getDeleteMessages() const
|
||||||
|
|
@ -216,9 +216,9 @@ int WarningDialog::getDeleteMessages() const
|
||||||
void WarningDialog::addWarningOption(const QString warning, int startingIl)
|
void WarningDialog::addWarningOption(const QString warning, int startingIl)
|
||||||
{
|
{
|
||||||
if (startingIl > 1) {
|
if (startingIl > 1) {
|
||||||
warningOption->addItem(tr("%1 (IL %2)").arg(warning).arg(startingIl));
|
warningOption->addItem(tr("%1 (IL %2)").arg(warning).arg(startingIl), warning);
|
||||||
} else {
|
} else {
|
||||||
warningOption->addItem(warning);
|
warningOption->addItem(warning, warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
#include <QSignalBlocker>
|
#include <QSignalBlocker>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
#include <QTableWidget>
|
#include <QTableWidget>
|
||||||
|
#include <QTextCursor>
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
@ -835,12 +836,13 @@ void TabReport::userInfoResponse(const Response &response)
|
||||||
for (int i = 0; i < resp.recent_reports_size(); ++i) {
|
for (int i = 0; i < resp.recent_reports_size(); ++i) {
|
||||||
const ServerInfo_Report &r = resp.recent_reports(i);
|
const ServerInfo_Report &r = resp.recent_reports(i);
|
||||||
QDateTime dt = QDateTime::fromSecsSinceEpoch(r.report_time());
|
QDateTime dt = QDateTime::fromSecsSinceEpoch(r.report_time());
|
||||||
userContextRecentReports->append(QString("[%1] #%2 by %3 [%4]: %5")
|
userContextRecentReports->moveCursor(QTextCursor::End);
|
||||||
.arg(dt.toString("yyyy-MM-dd"))
|
userContextRecentReports->insertPlainText(QString("[%1] #%2 by %3 [%4]: %5\n")
|
||||||
.arg(r.report_id())
|
.arg(dt.toString("yyyy-MM-dd"))
|
||||||
.arg(QString::fromStdString(r.reporter_name()))
|
.arg(r.report_id())
|
||||||
.arg(QString::fromStdString(r.status()))
|
.arg(QString::fromStdString(r.reporter_name()))
|
||||||
.arg(QString::fromStdString(r.category())));
|
.arg(QString::fromStdString(r.status()))
|
||||||
|
.arg(QString::fromStdString(r.category())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -894,26 +896,32 @@ void TabReport::statsResponse(const Response &response)
|
||||||
statsCategoriesLabel->setText(tr("By category:"));
|
statsCategoriesLabel->setText(tr("By category:"));
|
||||||
statsDetailText->clear();
|
statsDetailText->clear();
|
||||||
|
|
||||||
statsDetailText->append(tr("=== Top Categories ==="));
|
statsDetailText->moveCursor(QTextCursor::End);
|
||||||
|
statsDetailText->insertPlainText(tr("=== Top Categories ===") + "\n");
|
||||||
for (int i = 0; i < resp.category_counts_size(); ++i) {
|
for (int i = 0; i < resp.category_counts_size(); ++i) {
|
||||||
const ReportCategoryCount &cc = resp.category_counts(i);
|
const ReportCategoryCount &cc = resp.category_counts(i);
|
||||||
QString cat = QString::fromStdString(cc.category());
|
QString cat = QString::fromStdString(cc.category());
|
||||||
cat.replace('_', ' ');
|
cat.replace('_', ' ');
|
||||||
cat = cat.left(1).toUpper() + cat.mid(1);
|
cat = cat.left(1).toUpper() + cat.mid(1);
|
||||||
statsDetailText->append(QString(" %1: %2").arg(cat).arg(cc.count()));
|
statsDetailText->moveCursor(QTextCursor::End);
|
||||||
|
statsDetailText->insertPlainText(QString(" %1: %2\n").arg(cat).arg(cc.count()));
|
||||||
}
|
}
|
||||||
|
|
||||||
statsDetailText->append(tr("\n=== Most Reported Users ==="));
|
statsDetailText->moveCursor(QTextCursor::End);
|
||||||
|
statsDetailText->insertPlainText("\n" + tr("=== Most Reported Users ===") + "\n");
|
||||||
for (int i = 0; i < resp.top_reported_users_size(); ++i) {
|
for (int i = 0; i < resp.top_reported_users_size(); ++i) {
|
||||||
const ReportTopUser &tu = resp.top_reported_users(i);
|
const ReportTopUser &tu = resp.top_reported_users(i);
|
||||||
statsDetailText->append(
|
statsDetailText->moveCursor(QTextCursor::End);
|
||||||
QString(" %1: %2 reports").arg(QString::fromStdString(tu.user_name())).arg(tu.count()));
|
statsDetailText->insertPlainText(
|
||||||
|
QString(" %1: %2 reports\n").arg(QString::fromStdString(tu.user_name())).arg(tu.count()));
|
||||||
}
|
}
|
||||||
|
|
||||||
statsDetailText->append(tr("\n=== Top Reporters ==="));
|
statsDetailText->moveCursor(QTextCursor::End);
|
||||||
|
statsDetailText->insertPlainText("\n" + tr("=== Top Reporters ===") + "\n");
|
||||||
for (int i = 0; i < resp.top_reporters_size(); ++i) {
|
for (int i = 0; i < resp.top_reporters_size(); ++i) {
|
||||||
const ReportTopUser &tu = resp.top_reporters(i);
|
const ReportTopUser &tu = resp.top_reporters(i);
|
||||||
statsDetailText->append(
|
statsDetailText->moveCursor(QTextCursor::End);
|
||||||
QString(" %1: %2 reports filed").arg(QString::fromStdString(tu.user_name())).arg(tu.count()));
|
statsDetailText->insertPlainText(
|
||||||
|
QString(" %1: %2 reports filed\n").arg(QString::fromStdString(tu.user_name())).arg(tu.count()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
#include "report_utils.h"
|
#include "report_utils.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
#include <QBrush>
|
#include <QBrush>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <QPalette>
|
||||||
#include <QTableWidget>
|
#include <QTableWidget>
|
||||||
|
#include <QTextCursor>
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
|
|
||||||
namespace report_utils
|
namespace report_utils
|
||||||
|
|
@ -12,17 +15,21 @@ namespace
|
||||||
{
|
{
|
||||||
QColor reportStatusColor(const QString &status)
|
QColor reportStatusColor(const QString &status)
|
||||||
{
|
{
|
||||||
|
const QColor text = qApp->palette().color(QPalette::Text);
|
||||||
|
const int luminance = (299 * text.red() + 587 * text.green() + 114 * text.blue()) / 1000;
|
||||||
|
const bool dark = luminance < 128;
|
||||||
|
|
||||||
if (status == "open") {
|
if (status == "open") {
|
||||||
return QColor("#e74c3c");
|
return dark ? QColor("#e06060") : QColor("#c0392b");
|
||||||
}
|
}
|
||||||
if (status == "assigned") {
|
if (status == "assigned") {
|
||||||
return QColor("#f39c12");
|
return dark ? QColor("#e0a030") : QColor("#d68910");
|
||||||
}
|
}
|
||||||
if (status == "resolved") {
|
if (status == "resolved") {
|
||||||
return QColor("#27ae60");
|
return dark ? QColor("#50c878") : QColor("#1e8449");
|
||||||
}
|
}
|
||||||
if (status == "dismissed") {
|
if (status == "dismissed") {
|
||||||
return QColor("#95a5a6");
|
return qApp->palette().color(QPalette::PlaceholderText);
|
||||||
}
|
}
|
||||||
return QColor();
|
return QColor();
|
||||||
}
|
}
|
||||||
|
|
@ -103,8 +110,9 @@ void renderReportDetails(QTextEdit *chatLogEdit,
|
||||||
const QString author = QString::fromStdString(c.author_name());
|
const QString author = QString::fromStdString(c.author_name());
|
||||||
const QString text = QString::fromStdString(c.comment_text());
|
const QString text = QString::fromStdString(c.comment_text());
|
||||||
const QString prefix = c.is_moderator() ? moderatorPrefix : nonModeratorPrefix;
|
const QString prefix = c.is_moderator() ? moderatorPrefix : nonModeratorPrefix;
|
||||||
commentsEdit->append(
|
commentsEdit->moveCursor(QTextCursor::End);
|
||||||
QString("[%1] %2 %3:\n%4\n").arg(formatReportTime(c.comment_time()), prefix, author, text));
|
commentsEdit->insertPlainText(
|
||||||
|
QString("[%1] %2 %3:\n%4\n\n").arg(formatReportTime(c.comment_time()), prefix, author, text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,8 @@ enum AuthenticationResult
|
||||||
UsernameInvalid,
|
UsernameInvalid,
|
||||||
RegistrationRequired,
|
RegistrationRequired,
|
||||||
UserIsInactive,
|
UserIsInactive,
|
||||||
ClientIdRequired
|
ClientIdRequired,
|
||||||
|
PasswordChangeRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
class Server : public QObject
|
class Server : public QObject
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,9 @@ public:
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
virtual void setForcePasswordChange(const QString & /* user */, bool /* force */)
|
||||||
|
{
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -564,6 +564,8 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd
|
||||||
return Response::RespClientIdRequired;
|
return Response::RespClientIdRequired;
|
||||||
case UserIsInactive:
|
case UserIsInactive:
|
||||||
return Response::RespAccountNotActivated;
|
return Response::RespAccountNotActivated;
|
||||||
|
case PasswordChangeRequired:
|
||||||
|
return Response::RespPasswordChangeRequired;
|
||||||
default:
|
default:
|
||||||
authState = res;
|
authState = res;
|
||||||
usingRealPassword = needsHash;
|
usingRealPassword = needsHash;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ message AdminCommand {
|
||||||
SHUTDOWN_SERVER = 1001;
|
SHUTDOWN_SERVER = 1001;
|
||||||
RELOAD_CONFIG = 1002;
|
RELOAD_CONFIG = 1002;
|
||||||
ADJUST_MOD = 1003;
|
ADJUST_MOD = 1003;
|
||||||
|
RESET_USER_PASSWORD = 1016;
|
||||||
}
|
}
|
||||||
extensions 100 to max;
|
extensions 100 to max;
|
||||||
}
|
}
|
||||||
|
|
@ -37,3 +38,10 @@ message Command_AdjustMod {
|
||||||
optional bool should_be_mod = 2;
|
optional bool should_be_mod = 2;
|
||||||
optional bool should_be_judge = 3;
|
optional bool should_be_judge = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Command_ResetUserPassword {
|
||||||
|
extend AdminCommand {
|
||||||
|
optional Command_ResetUserPassword ext = 1016;
|
||||||
|
}
|
||||||
|
optional string user_name = 1;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,6 @@ message Command_ReportList {
|
||||||
optional Command_ReportList ext = 1200;
|
optional Command_ReportList ext = 1200;
|
||||||
}
|
}
|
||||||
optional bool unresolved_only = 1;
|
optional bool unresolved_only = 1;
|
||||||
|
optional uint32 offset = 2 [default = 0];
|
||||||
|
optional uint32 limit = 3 [default = 100];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -168,13 +168,6 @@ message Command_GetModeratorLastLogins {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message Command_ResetUserPassword {
|
|
||||||
extend ModeratorCommand {
|
|
||||||
optional Command_ResetUserPassword ext = 1016;
|
|
||||||
}
|
|
||||||
optional string user_name = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Command_RemoveUserAvatar {
|
message Command_RemoveUserAvatar {
|
||||||
extend ModeratorCommand {
|
extend ModeratorCommand {
|
||||||
optional Command_RemoveUserAvatar ext = 1017;
|
optional Command_RemoveUserAvatar ext = 1017;
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ message Response {
|
||||||
RespClientUpdateRequired = 35; // Client is missing features that the server is requiring
|
RespClientUpdateRequired = 35; // Client is missing features that the server is requiring
|
||||||
RespServerFull = 36; // Server user limit reached
|
RespServerFull = 36; // Server user limit reached
|
||||||
RespEmailBlackListed = 37; // Server has blocked the email address provided for registration for some reason
|
RespEmailBlackListed = 37; // Server has blocked the email address provided for registration for some reason
|
||||||
|
RespPasswordChangeRequired = 38; // Server requires the user to change their password before proceeding
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type of response, used to route handling on the client
|
// Type of response, used to route handling on the client
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ message Response_ReportList {
|
||||||
optional Response_ReportList ext = 1210;
|
optional Response_ReportList ext = 1210;
|
||||||
}
|
}
|
||||||
repeated ServerInfo_Report reports = 1;
|
repeated ServerInfo_Report reports = 1;
|
||||||
|
optional uint32 total_count = 2;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ message SessionCommand {
|
||||||
REPLAY_GET_CODE = 1104;
|
REPLAY_GET_CODE = 1104;
|
||||||
REPLAY_SUBMIT_CODE = 1105;
|
REPLAY_SUBMIT_CODE = 1105;
|
||||||
REPORT = 1200;
|
REPORT = 1200;
|
||||||
|
// 1201-1203 reserved: removed during squash
|
||||||
REPORT_MY_LIST = 1204;
|
REPORT_MY_LIST = 1204;
|
||||||
REPORT_ADD_COMMENT = 1205;
|
REPORT_ADD_COMMENT = 1205;
|
||||||
REPORT_DETAILS = 1206;
|
REPORT_DETAILS = 1206;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_reports` (
|
||||||
`resolution_time` datetime NULL,
|
`resolution_time` datetime NULL,
|
||||||
`status` enum('open','assigned','resolved','dismissed') NOT NULL DEFAULT 'open',
|
`status` enum('open','assigned','resolved','dismissed') NOT NULL DEFAULT 'open',
|
||||||
`assigned_to` int(7) unsigned NULL,
|
`assigned_to` int(7) unsigned NULL,
|
||||||
|
`resolved_by` int(7) unsigned NULL,
|
||||||
`resolution_note` text,
|
`resolution_note` text,
|
||||||
`notified` tinyint(1) NOT NULL DEFAULT 0,
|
`notified` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
|
|
@ -25,7 +26,8 @@ CREATE TABLE IF NOT EXISTS `cockatrice_reports` (
|
||||||
INDEX `idx_status_created` (`status`, `created_at`),
|
INDEX `idx_status_created` (`status`, `created_at`),
|
||||||
FOREIGN KEY (`reporter_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
FOREIGN KEY (`reporter_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
FOREIGN KEY (`reported_user_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
FOREIGN KEY (`reported_user_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
FOREIGN KEY (`assigned_to`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
FOREIGN KEY (`assigned_to`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
FOREIGN KEY (`resolved_by`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `cockatrice_report_comments` (
|
CREATE TABLE IF NOT EXISTS `cockatrice_report_comments` (
|
||||||
|
|
@ -44,4 +46,8 @@ CREATE TABLE IF NOT EXISTS `cockatrice_report_comments` (
|
||||||
FOREIGN KEY (`author_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
FOREIGN KEY (`author_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
ALTER TABLE `cockatrice_users`
|
||||||
|
ADD COLUMN `force_password_change` tinyint(1) NOT NULL DEFAULT 0
|
||||||
|
AFTER `passwordLastChangedDate`;
|
||||||
|
|
||||||
UPDATE cockatrice_schema_version SET version=36 WHERE version=35;
|
UPDATE cockatrice_schema_version SET version=36 WHERE version=35;
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_users` (
|
||||||
`privlevelStartDate` datetime NOT NULL,
|
`privlevelStartDate` datetime NOT NULL,
|
||||||
`privlevelEndDate` datetime NOT NULL,
|
`privlevelEndDate` datetime NOT NULL,
|
||||||
`passwordLastChangedDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
`passwordLastChangedDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||||
|
`force_password_change` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
`leftPawnColorOverride` varchar(255),
|
`leftPawnColorOverride` varchar(255),
|
||||||
`rightPawnColorOverride` varchar(255),
|
`rightPawnColorOverride` varchar(255),
|
||||||
`card_art_params` TEXT DEFAULT NULL,
|
`card_art_params` TEXT DEFAULT NULL,
|
||||||
|
|
@ -248,6 +249,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_reports` (
|
||||||
`resolution_time` datetime NULL,
|
`resolution_time` datetime NULL,
|
||||||
`status` enum('open','assigned','resolved','dismissed') NOT NULL DEFAULT 'open',
|
`status` enum('open','assigned','resolved','dismissed') NOT NULL DEFAULT 'open',
|
||||||
`assigned_to` int(7) unsigned NULL,
|
`assigned_to` int(7) unsigned NULL,
|
||||||
|
`resolved_by` int(7) unsigned NULL,
|
||||||
`resolution_note` text,
|
`resolution_note` text,
|
||||||
`notified` tinyint(1) NOT NULL DEFAULT 0,
|
`notified` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
|
|
@ -258,7 +260,8 @@ CREATE TABLE IF NOT EXISTS `cockatrice_reports` (
|
||||||
INDEX `idx_status_created` (`status`, `created_at`),
|
INDEX `idx_status_created` (`status`, `created_at`),
|
||||||
FOREIGN KEY (`reporter_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
FOREIGN KEY (`reporter_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
FOREIGN KEY (`reported_user_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
FOREIGN KEY (`reported_user_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
FOREIGN KEY (`assigned_to`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
FOREIGN KEY (`assigned_to`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
FOREIGN KEY (`resolved_by`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `cockatrice_report_comments` (
|
CREATE TABLE IF NOT EXISTS `cockatrice_report_comments` (
|
||||||
|
|
|
||||||
|
|
@ -340,8 +340,8 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot
|
||||||
return UserIsBanned;
|
return UserIsBanned;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSqlQuery *passwordQuery =
|
QSqlQuery *passwordQuery = prepareQuery(
|
||||||
prepareQuery("select password_sha512, active from {prefix}_users where name = :name");
|
"select password_sha512, active, force_password_change from {prefix}_users where name = :name");
|
||||||
passwordQuery->bindValue(":name", user);
|
passwordQuery->bindValue(":name", user);
|
||||||
if (!execSqlQuery(passwordQuery)) {
|
if (!execSqlQuery(passwordQuery)) {
|
||||||
qCWarning(DatabaseInterfaceLog) << "Login denied: SQL error";
|
qCWarning(DatabaseInterfaceLog) << "Login denied: SQL error";
|
||||||
|
|
@ -351,6 +351,7 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot
|
||||||
if (passwordQuery->next()) {
|
if (passwordQuery->next()) {
|
||||||
const QString correctPasswordSha512 = passwordQuery->value(0).toString();
|
const QString correctPasswordSha512 = passwordQuery->value(0).toString();
|
||||||
const bool userIsActive = passwordQuery->value(1).toBool();
|
const bool userIsActive = passwordQuery->value(1).toBool();
|
||||||
|
const bool forceChange = passwordQuery->value(2).toBool();
|
||||||
if (!userIsActive) {
|
if (!userIsActive) {
|
||||||
qCWarning(DatabaseInterfaceLog) << "Login denied: user not active";
|
qCWarning(DatabaseInterfaceLog) << "Login denied: user not active";
|
||||||
return UserIsInactive;
|
return UserIsInactive;
|
||||||
|
|
@ -362,6 +363,10 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot
|
||||||
hashedPassword = password;
|
hashedPassword = password;
|
||||||
}
|
}
|
||||||
if (correctPasswordSha512 == hashedPassword) {
|
if (correctPasswordSha512 == hashedPassword) {
|
||||||
|
if (forceChange) {
|
||||||
|
qCDebug(DatabaseInterfaceLog) << "Login accepted but password change required";
|
||||||
|
return PasswordChangeRequired;
|
||||||
|
}
|
||||||
qCDebug(DatabaseInterfaceLog) << "Login accepted: password right";
|
qCDebug(DatabaseInterfaceLog) << "Login accepted: password right";
|
||||||
return PasswordRight;
|
return PasswordRight;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1091,6 +1096,18 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user,
|
||||||
return passwordQuery->numRowsAffected() > 0;
|
return passwordQuery->numRowsAffected() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Servatrice_DatabaseInterface::setForcePasswordChange(const QString &user, bool force)
|
||||||
|
{
|
||||||
|
if (!checkSql()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery *query = prepareQuery("UPDATE {prefix}_users SET force_password_change = :force WHERE name = :name");
|
||||||
|
query->bindValue(":force", force ? 1 : 0);
|
||||||
|
query->bindValue(":name", user);
|
||||||
|
execSqlQuery(query);
|
||||||
|
}
|
||||||
|
|
||||||
bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user,
|
bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user,
|
||||||
const QString &oldPassword,
|
const QString &oldPassword,
|
||||||
bool oldPasswordNeedsHash,
|
bool oldPasswordNeedsHash,
|
||||||
|
|
@ -1383,8 +1400,10 @@ QList<ServerInfo_UserAlt> Servatrice_DatabaseInterface::getUserAlts(const QStrin
|
||||||
}
|
}
|
||||||
queryString.append(" OR u.name IN (SELECT DISTINCT s.user_name FROM {prefix}_sessions s "
|
queryString.append(" OR u.name IN (SELECT DISTINCT s.user_name FROM {prefix}_sessions s "
|
||||||
"WHERE s.ip_address IN (SELECT DISTINCT s2.ip_address FROM {prefix}_sessions s2 "
|
"WHERE s.ip_address IN (SELECT DISTINCT s2.ip_address FROM {prefix}_sessions s2 "
|
||||||
"WHERE s2.user_name = :user_name)) "
|
"WHERE s2.user_name = :user_name"
|
||||||
"ORDER BY u.name");
|
" AND s2.start_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH))"
|
||||||
|
" AND s.start_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH)) "
|
||||||
|
"ORDER BY u.name LIMIT 200");
|
||||||
|
|
||||||
QSqlQuery *query = prepareQuery(queryString);
|
QSqlQuery *query = prepareQuery(queryString);
|
||||||
query->bindValue(":user_name", userName);
|
query->bindValue(":user_name", userName);
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,7 @@ public:
|
||||||
bool oldPasswordNeedsHash,
|
bool oldPasswordNeedsHash,
|
||||||
const QString &newPassword,
|
const QString &newPassword,
|
||||||
bool newPasswordNeedsHash) override;
|
bool newPasswordNeedsHash) override;
|
||||||
|
void setForcePasswordChange(const QString &user, bool force);
|
||||||
QList<ServerInfo_Ban> getUserBanHistory(const QString userName);
|
QList<ServerInfo_Ban> getUserBanHistory(const QString userName);
|
||||||
bool
|
bool
|
||||||
addWarning(const QString userName, const QString adminName, const QString warningReason, const QString clientID);
|
addWarning(const QString userName, const QString adminName, const QString warningReason, const QString clientID);
|
||||||
|
|
|
||||||
|
|
@ -311,8 +311,6 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedModeratorCo
|
||||||
return cmdGetUserAlts(cmd.GetExtension(Command_GetUserAlts::ext), rc);
|
return cmdGetUserAlts(cmd.GetExtension(Command_GetUserAlts::ext), rc);
|
||||||
case ModeratorCommand::GET_MODERATOR_LAST_LOGINS:
|
case ModeratorCommand::GET_MODERATOR_LAST_LOGINS:
|
||||||
return cmdGetModeratorLastLogins(cmd.GetExtension(Command_GetModeratorLastLogins::ext), rc);
|
return cmdGetModeratorLastLogins(cmd.GetExtension(Command_GetModeratorLastLogins::ext), rc);
|
||||||
case ModeratorCommand::RESET_USER_PASSWORD:
|
|
||||||
return cmdResetUserPassword(cmd.GetExtension(Command_ResetUserPassword::ext), rc);
|
|
||||||
case ModeratorCommand::REMOVE_USER_AVATAR:
|
case ModeratorCommand::REMOVE_USER_AVATAR:
|
||||||
return cmdRemoveUserAvatar(cmd.GetExtension(Command_RemoveUserAvatar::ext), rc);
|
return cmdRemoveUserAvatar(cmd.GetExtension(Command_RemoveUserAvatar::ext), rc);
|
||||||
default:
|
default:
|
||||||
|
|
@ -332,6 +330,8 @@ AbstractServerSocketInterface::processExtendedAdminCommand(int cmdType, const Ad
|
||||||
return cmdReloadConfig(cmd.GetExtension(Command_ReloadConfig::ext), rc);
|
return cmdReloadConfig(cmd.GetExtension(Command_ReloadConfig::ext), rc);
|
||||||
case AdminCommand::ADJUST_MOD:
|
case AdminCommand::ADJUST_MOD:
|
||||||
return cmdAdjustMod(cmd.GetExtension(Command_AdjustMod::ext), rc);
|
return cmdAdjustMod(cmd.GetExtension(Command_AdjustMod::ext), rc);
|
||||||
|
case AdminCommand::RESET_USER_PASSWORD:
|
||||||
|
return cmdResetUserPassword(cmd.GetExtension(Command_ResetUserPassword::ext), rc);
|
||||||
default:
|
default:
|
||||||
return Response::RespFunctionNotAllowed;
|
return Response::RespFunctionNotAllowed;
|
||||||
}
|
}
|
||||||
|
|
@ -1318,11 +1318,25 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportList(const Comman
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool unresolvedOnly = cmd.unresolved_only();
|
const bool unresolvedOnly = cmd.unresolved_only();
|
||||||
|
const int offset = static_cast<int>(cmd.offset());
|
||||||
|
const int limit = qMin(static_cast<int>(cmd.limit()), 1000);
|
||||||
|
|
||||||
// Columns: 0=id, 1=reporter_name, 2=reported_user_name, 3=game_id,
|
// Columns: 0=id, 1=reporter_name, 2=reported_user_name, 3=game_id,
|
||||||
// 4=category, 5=description, 6=created_at, 7=status,
|
// 4=category, 5=description, 6=created_at, 7=status,
|
||||||
// 8=resolution_note, 9=assigned_mod_name,
|
// 8=resolution_note, 9=assigned_mod_name,
|
||||||
// 10=room_id, 11=replay_id
|
// 10=room_id, 11=replay_id
|
||||||
|
QString whereClause;
|
||||||
|
if (unresolvedOnly) {
|
||||||
|
whereClause = "WHERE r.status = 'open' OR r.status = 'assigned' ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total count query
|
||||||
|
QSqlQuery *countQuery = sqlInterface->prepareQuery("SELECT COUNT(*) FROM {prefix}_reports r " + whereClause);
|
||||||
|
int totalCount = 0;
|
||||||
|
if (sqlInterface->execSqlQuery(countQuery) && countQuery->next()) {
|
||||||
|
totalCount = countQuery->value(0).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
QString queryStr = "SELECT r.id, r.reporter_name, r.reported_user_name, r.game_id, "
|
QString queryStr = "SELECT r.id, r.reporter_name, r.reported_user_name, r.game_id, "
|
||||||
"r.category, r.description, r.created_at, r.status, "
|
"r.category, r.description, r.created_at, r.status, "
|
||||||
"r.resolution_note, u.name AS assigned_mod_name, r.room_id, "
|
"r.resolution_note, u.name AS assigned_mod_name, r.room_id, "
|
||||||
|
|
@ -1335,14 +1349,17 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportList(const Comman
|
||||||
queryStr += "WHERE r.status = 'open' OR r.status = 'assigned' ";
|
queryStr += "WHERE r.status = 'open' OR r.status = 'assigned' ";
|
||||||
}
|
}
|
||||||
|
|
||||||
queryStr += "ORDER BY r.created_at DESC LIMIT 1000";
|
queryStr += "ORDER BY r.created_at DESC LIMIT :limit OFFSET :offset";
|
||||||
|
|
||||||
QSqlQuery *query = sqlInterface->prepareQuery(queryStr);
|
QSqlQuery *query = sqlInterface->prepareQuery(queryStr);
|
||||||
|
query->bindValue(":limit", limit);
|
||||||
|
query->bindValue(":offset", offset);
|
||||||
if (!sqlInterface->execSqlQuery(query)) {
|
if (!sqlInterface->execSqlQuery(query)) {
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
Response_ReportList *re = new Response_ReportList;
|
Response_ReportList *re = new Response_ReportList;
|
||||||
|
re->set_total_count(totalCount);
|
||||||
|
|
||||||
while (query->next()) {
|
while (query->next()) {
|
||||||
ServerInfo_Report *info = re->add_reports();
|
ServerInfo_Report *info = re->add_reports();
|
||||||
|
|
@ -1440,11 +1457,12 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportResolve(const Com
|
||||||
|
|
||||||
QSqlQuery *query = sqlInterface->prepareQuery("UPDATE {prefix}_reports "
|
QSqlQuery *query = sqlInterface->prepareQuery("UPDATE {prefix}_reports "
|
||||||
"SET status = :status, resolution_note = :note, "
|
"SET status = :status, resolution_note = :note, "
|
||||||
"resolution_time = NOW() "
|
"resolution_time = NOW(), resolved_by = :mod_id "
|
||||||
"WHERE id = :id AND status IN ('open', 'assigned')");
|
"WHERE id = :id AND status IN ('open', 'assigned')");
|
||||||
|
|
||||||
query->bindValue(":status", newStatus);
|
query->bindValue(":status", newStatus);
|
||||||
query->bindValue(":note", note);
|
query->bindValue(":note", note);
|
||||||
|
query->bindValue(":mod_id", userInfo->id());
|
||||||
query->bindValue(":id", reportId);
|
query->bindValue(":id", reportId);
|
||||||
|
|
||||||
if (!sqlInterface->execSqlQuery(query)) {
|
if (!sqlInterface->execSqlQuery(query)) {
|
||||||
|
|
@ -1455,6 +1473,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportResolve(const Com
|
||||||
return Response::RespInvalidData;
|
return Response::RespInvalidData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqlInterface->addAuditRecord(QString::number(reportId), this->getAddress(),
|
||||||
|
QString::fromStdString(userInfo->clientid()),
|
||||||
|
dismissed ? "REPORT_DISMISSED" : "REPORT_RESOLVED",
|
||||||
|
QString("Report #%1 %2").arg(reportId).arg(newStatus), true);
|
||||||
|
|
||||||
// Notifying the reporter is best-effort: the resolve already succeeded. If any of the lookup
|
// Notifying the reporter is best-effort: the resolve already succeeded. If any of the lookup
|
||||||
// queries below fail, the report is simply left with notified = 0 and the notification is
|
// queries below fail, the report is simply left with notified = 0 and the notification is
|
||||||
// delivered later via sendPendingReportNotifications.
|
// delivered later via sendPendingReportNotifications.
|
||||||
|
|
@ -1740,7 +1763,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetUserSessions(const C
|
||||||
}
|
}
|
||||||
|
|
||||||
Response_UserSessions *re = new Response_UserSessions;
|
Response_UserSessions *re = new Response_UserSessions;
|
||||||
const QList<ServerInfo_UserSession> sessions = sqlInterface->getUserSessions(userName, cmd.limit());
|
const int limit = qMin(static_cast<int>(cmd.limit()), 500);
|
||||||
|
const QList<ServerInfo_UserSession> sessions = sqlInterface->getUserSessions(userName, limit);
|
||||||
for (const ServerInfo_UserSession &session : sessions) {
|
for (const ServerInfo_UserSession &session : sessions) {
|
||||||
re->add_sessions()->CopyFrom(session);
|
re->add_sessions()->CopyFrom(session);
|
||||||
}
|
}
|
||||||
|
|
@ -1797,13 +1821,47 @@ Response::ResponseCode AbstractServerSocketInterface::cmdResetUserPassword(const
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look up the target user's privilege level to prevent escalation.
|
||||||
|
QSqlQuery *privQuery = sqlInterface->prepareQuery("SELECT admin FROM {prefix}_users WHERE name = :name");
|
||||||
|
privQuery->bindValue(":name", userName);
|
||||||
|
if (!sqlInterface->execSqlQuery(privQuery) || !privQuery->next()) {
|
||||||
|
return Response::RespNameNotFound;
|
||||||
|
}
|
||||||
|
const int targetAdmin = privQuery->value(0).toInt();
|
||||||
|
const bool targetIsAdmin = targetAdmin & 1;
|
||||||
|
const bool targetIsMod = targetAdmin & 2;
|
||||||
|
|
||||||
|
const bool callerIsAdmin = userInfo->user_level() & ServerInfo_User::IsAdmin;
|
||||||
|
|
||||||
|
// A moderator must not reset the password of an admin or another moderator.
|
||||||
|
if (!callerIsAdmin && (targetIsAdmin || targetIsMod)) {
|
||||||
|
return Response::RespAccessDenied;
|
||||||
|
}
|
||||||
|
|
||||||
const QString tempPassword = PasswordHasher::generateRandomSalt();
|
const QString tempPassword = PasswordHasher::generateRandomSalt();
|
||||||
if (!sqlInterface->changeUserPassword(userName, tempPassword, true)) {
|
if (!sqlInterface->changeUserPassword(userName, tempPassword, true)) {
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
sqlInterface->setForcePasswordChange(userName, true);
|
||||||
|
|
||||||
sqlInterface->addAuditRecord(userName, this->getAddress(), QString::fromStdString(userInfo->clientid()),
|
sqlInterface->addAuditRecord(userName, this->getAddress(), QString::fromStdString(userInfo->clientid()),
|
||||||
"PASSWORD_RESET", "Moderator password reset", true);
|
"PASSWORD_RESET", "Admin password reset", true);
|
||||||
|
|
||||||
|
// Notify the affected user if they are currently online.
|
||||||
|
QReadLocker clientsLocker(&servatrice->clientsLock);
|
||||||
|
AbstractServerSocketInterface *targetSession =
|
||||||
|
static_cast<AbstractServerSocketInterface *>(server->getUsers().value(userName));
|
||||||
|
if (targetSession) {
|
||||||
|
Event_NotifyUser event;
|
||||||
|
event.set_type(Event_NotifyUser::CUSTOM);
|
||||||
|
event.set_custom_title(tr("Password Reset").toStdString());
|
||||||
|
event.set_custom_content(tr("An administrator has reset your password. Please log in with the new "
|
||||||
|
"password provided to you and change it immediately.")
|
||||||
|
.toStdString());
|
||||||
|
SessionEvent *se = targetSession->prepareSessionEvent(event);
|
||||||
|
targetSession->sendProtocolItem(*se);
|
||||||
|
delete se;
|
||||||
|
}
|
||||||
|
|
||||||
Response_ResetUserPassword *re = new Response_ResetUserPassword;
|
Response_ResetUserPassword *re = new Response_ResetUserPassword;
|
||||||
re->set_user_name(userName.toStdString());
|
re->set_user_name(userName.toStdString());
|
||||||
|
|
@ -2026,8 +2084,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportDetails(const Com
|
||||||
|
|
||||||
const bool isMod = userInfo->user_level() & ServerInfo_User::IsModerator;
|
const bool isMod = userInfo->user_level() & ServerInfo_User::IsModerator;
|
||||||
const int reporterId = query->value(1).toInt();
|
const int reporterId = query->value(1).toInt();
|
||||||
const QString reporterName = query->value(2).toString();
|
if (reporterId != userInfo->id() && !isMod) {
|
||||||
if (reporterId != userInfo->id() && reporterName != QString::fromStdString(userInfo->name()) && !isMod) {
|
|
||||||
return Response::RespAccessDenied;
|
return Response::RespAccessDenied;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2139,7 +2196,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportAddComment(const
|
||||||
|
|
||||||
bool isMod = userInfo->user_level() & ServerInfo_User::IsModerator;
|
bool isMod = userInfo->user_level() & ServerInfo_User::IsModerator;
|
||||||
|
|
||||||
if (reporterId != userInfo->id() && reporterName != QString::fromStdString(userInfo->name()) && !isMod) {
|
// Only the reporter (by id) or a moderator may comment on a report.
|
||||||
|
if (reporterId != userInfo->id() && !isMod) {
|
||||||
return Response::RespAccessDenied;
|
return Response::RespAccessDenied;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2184,7 +2242,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportAddComment(const
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString ownName = QString::fromStdString(userInfo->name());
|
const QString ownName = QString::fromStdString(userInfo->name());
|
||||||
bool allNotified = true;
|
bool allNotified = !recipients.isEmpty();
|
||||||
QReadLocker clientsLocker(&servatrice->clientsLock);
|
QReadLocker clientsLocker(&servatrice->clientsLock);
|
||||||
for (const QString ¬ifyName : recipients) {
|
for (const QString ¬ifyName : recipients) {
|
||||||
if (notifyName == ownName) {
|
if (notifyName == ownName) {
|
||||||
|
|
@ -2788,6 +2846,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountPassword(const C
|
||||||
return Response::RespWrongPassword;
|
return Response::RespWrongPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
databaseInterface->setForcePasswordChange(userName, false);
|
||||||
|
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2924,6 +2984,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdForgotPasswordReset(con
|
||||||
"PASSWORD_RESET", "", true);
|
"PASSWORD_RESET", "", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqlInterface->setForcePasswordChange(nameFromStdString(cmd.user_name()), false);
|
||||||
sqlInterface->removeForgotPassword(nameFromStdString(cmd.user_name()));
|
sqlInterface->removeForgotPassword(nameFromStdString(cmd.user_name()));
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
@ -3016,6 +3077,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReport(const Command_Re
|
||||||
return Response::RespInvalidData;
|
return Response::RespInvalidData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const QStringList validCategories = {"cheating", "bug_abuse", "verbal_abuse", "other"};
|
||||||
|
if (!validCategories.contains(category.toLower())) {
|
||||||
|
return Response::RespInvalidData;
|
||||||
|
}
|
||||||
|
|
||||||
const int maxReportsPerDay = settingsCache->value("reporting/max_reports_per_day", 10).toInt();
|
const int maxReportsPerDay = settingsCache->value("reporting/max_reports_per_day", 10).toInt();
|
||||||
if (maxReportsPerDay > 0) {
|
if (maxReportsPerDay > 0) {
|
||||||
QSqlQuery *countQuery =
|
QSqlQuery *countQuery =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue