mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-25 16:13:54 -07:00
use hashed passwords in all commands (#4493)
* protocol changes * server changes * client changes for password reset and registration * add hashed password to change password in client * always use hashed password to log in * add warning to client when using plain text password * require real password for changing email on server this is backwards compatible as users logged in with a real password on older clients will not need this, only users logged in with a hashed password * implement password dialog when changing email * require min password length * use qstringlist to build query instead * use clear instead of = "" * add max to password dialog * use proper const ness in abstractclient * reject too long passwords instead of trimming
This commit is contained in:
parent
fcafcb340a
commit
2fc85e0c08
17 changed files with 330 additions and 96 deletions
|
|
@ -22,7 +22,8 @@
|
|||
|
||||
#include <google/protobuf/descriptor.h>
|
||||
|
||||
AbstractClient::AbstractClient(QObject *parent) : QObject(parent), nextCmdId(0), status(StatusDisconnected)
|
||||
AbstractClient::AbstractClient(QObject *parent)
|
||||
: QObject(parent), nextCmdId(0), status(StatusDisconnected), serverSupportsPasswordHash(false)
|
||||
{
|
||||
qRegisterMetaType<QVariant>("QVariant");
|
||||
qRegisterMetaType<CommandContainer>("CommandContainer");
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ protected slots:
|
|||
protected:
|
||||
QMap<int, PendingCommand *> pendingCommands;
|
||||
QString userName, password, email, country, realName, token;
|
||||
bool serverSupportsPasswordHash;
|
||||
void setStatus(ClientStatus _status);
|
||||
int getNewCmdId()
|
||||
{
|
||||
|
|
@ -107,7 +108,11 @@ public:
|
|||
void sendCommand(const CommandContainer &cont);
|
||||
void sendCommand(PendingCommand *pend);
|
||||
|
||||
const QString getUserName()
|
||||
bool getServerSupportsPasswordHash() const
|
||||
{
|
||||
return serverSupportsPasswordHash;
|
||||
}
|
||||
const QString &getUserName() const
|
||||
{
|
||||
return userName;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,11 @@ DlgEditPassword::DlgEditPassword(QWidget *parent) : QDialog(parent)
|
|||
|
||||
void DlgEditPassword::actOk()
|
||||
{
|
||||
if (newPasswordEdit->text() != newPasswordEdit2->text()) {
|
||||
// TODO this stuff should be using qvalidators
|
||||
if (newPasswordEdit->text().length() < 8) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Your password is too short."));
|
||||
return;
|
||||
} else if (newPasswordEdit->text() != newPasswordEdit2->text()) {
|
||||
QMessageBox::warning(this, tr("Error"), tr("The new passwords don't match."));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,11 @@ void DlgForgotPasswordReset::actOk()
|
|||
return;
|
||||
}
|
||||
|
||||
if (newpasswordEdit->text() != newpasswordverifyEdit->text()) {
|
||||
// TODO this stuff should be using qvalidators
|
||||
if (newpasswordEdit->text().length() < 8) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Your password is too short."));
|
||||
return;
|
||||
} else if (newpasswordEdit->text() != newpasswordverifyEdit->text()) {
|
||||
QMessageBox::critical(this, tr("Reset Password Error"), tr("The passwords do not match."));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -356,7 +356,11 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent)
|
|||
|
||||
void DlgRegister::actOk()
|
||||
{
|
||||
if (passwordEdit->text() != passwordConfirmationEdit->text()) {
|
||||
// TODO this stuff should be using qvalidators
|
||||
if (passwordEdit->text().length() < 8) {
|
||||
QMessageBox::critical(this, tr("Registration Warning"), tr("Your password is too short."));
|
||||
return;
|
||||
} else if (passwordEdit->text() != passwordConfirmationEdit->text()) {
|
||||
QMessageBox::critical(this, tr("Registration Warning"), tr("Your passwords do not match, please try again."));
|
||||
return;
|
||||
} else if (emailConfirmationEdit->text() != emailEdit->text()) {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ static const unsigned int protocolVersion = 14;
|
|||
|
||||
RemoteClient::RemoteClient(QObject *parent)
|
||||
: AbstractClient(parent), timeRunning(0), lastDataReceived(0), messageInProgress(false), handshakeStarted(false),
|
||||
usingWebSocket(false), messageLength(0)
|
||||
usingWebSocket(false), messageLength(0), hashedPassword()
|
||||
{
|
||||
|
||||
clearNewClientFeatures();
|
||||
|
|
@ -110,6 +110,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica
|
|||
setStatus(StatusDisconnecting);
|
||||
return;
|
||||
}
|
||||
serverSupportsPasswordHash = event.server_options() & Event_ServerIdentification::SupportsPasswordHash;
|
||||
|
||||
if (getStatus() == StatusRequestingForgotPassword) {
|
||||
Command_ForgotPasswordRequest cmdForgotPasswordRequest;
|
||||
|
|
@ -127,7 +128,14 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica
|
|||
cmdForgotPasswordReset.set_user_name(userName.toStdString());
|
||||
cmdForgotPasswordReset.set_clientid(getSrvClientID(lastHostname).toStdString());
|
||||
cmdForgotPasswordReset.set_token(token.toStdString());
|
||||
cmdForgotPasswordReset.set_new_password(password.toStdString());
|
||||
if (!password.isEmpty() && serverSupportsPasswordHash) {
|
||||
auto passwordSalt = PasswordHasher::generateRandomSalt();
|
||||
hashedPassword = PasswordHasher::computeHash(password, passwordSalt);
|
||||
cmdForgotPasswordReset.set_hashed_new_password(hashedPassword.toStdString());
|
||||
} else if (!password.isEmpty()) {
|
||||
qWarning() << "using plain text password to reset password";
|
||||
cmdForgotPasswordReset.set_new_password(password.toStdString());
|
||||
}
|
||||
PendingCommand *pend = prepareSessionCommand(cmdForgotPasswordReset);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(submitForgotPasswordResetResponse(Response)));
|
||||
|
|
@ -150,7 +158,14 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica
|
|||
if (getStatus() == StatusRegistering) {
|
||||
Command_Register cmdRegister;
|
||||
cmdRegister.set_user_name(userName.toStdString());
|
||||
cmdRegister.set_password(password.toStdString());
|
||||
if (!password.isEmpty() && serverSupportsPasswordHash) {
|
||||
auto passwordSalt = PasswordHasher::generateRandomSalt();
|
||||
hashedPassword = PasswordHasher::computeHash(password, passwordSalt);
|
||||
cmdRegister.set_hashed_password(hashedPassword.toStdString());
|
||||
} else if (!password.isEmpty()) {
|
||||
qWarning() << "using plain text password to register";
|
||||
cmdRegister.set_password(password.toStdString());
|
||||
}
|
||||
cmdRegister.set_email(email.toStdString());
|
||||
cmdRegister.set_country(country.toStdString());
|
||||
cmdRegister.set_real_name(realName.toStdString());
|
||||
|
|
@ -175,13 +190,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica
|
|||
return;
|
||||
}
|
||||
|
||||
if (!password.isEmpty() && event.server_options() & Event_ServerIdentification::SupportsPasswordHash) {
|
||||
// TODO store and log in using stored hashed password
|
||||
doRequestPasswordSalt(); // log in using password salt
|
||||
} else {
|
||||
// TODO add setting for client to reject unhashed logins
|
||||
doLogin();
|
||||
}
|
||||
doLogin();
|
||||
}
|
||||
|
||||
void RemoteClient::doRequestPasswordSalt()
|
||||
|
|
@ -213,21 +222,33 @@ Command_Login RemoteClient::generateCommandLogin()
|
|||
|
||||
void RemoteClient::doLogin()
|
||||
{
|
||||
setStatus(StatusLoggingIn);
|
||||
Command_Login cmdLogin = generateCommandLogin();
|
||||
cmdLogin.set_password(password.toStdString());
|
||||
if (!password.isEmpty() && serverSupportsPasswordHash) {
|
||||
// TODO store and log in using stored hashed password
|
||||
if (hashedPassword.isEmpty()) {
|
||||
doRequestPasswordSalt(); // ask salt to create hashedPassword, then log in
|
||||
} else {
|
||||
doHashedLogin(); // log in using hashed password instead
|
||||
}
|
||||
} else {
|
||||
// TODO add setting for client to reject unhashed logins
|
||||
setStatus(StatusLoggingIn);
|
||||
Command_Login cmdLogin = generateCommandLogin();
|
||||
if (!password.isEmpty()) {
|
||||
qWarning() << "using plain text password to log in";
|
||||
cmdLogin.set_password(password.toStdString());
|
||||
}
|
||||
|
||||
PendingCommand *pend = prepareSessionCommand(cmdLogin);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(loginResponse(Response)));
|
||||
sendCommand(pend);
|
||||
PendingCommand *pend = prepareSessionCommand(cmdLogin);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(loginResponse(Response)));
|
||||
sendCommand(pend);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::doLogin(const QString &passwordSalt)
|
||||
void RemoteClient::doHashedLogin()
|
||||
{
|
||||
setStatus(StatusLoggingIn);
|
||||
Command_Login cmdLogin = generateCommandLogin();
|
||||
|
||||
const auto hashedPassword = PasswordHasher::computeHash(password, passwordSalt);
|
||||
cmdLogin.set_hashed_password(hashedPassword.toStdString());
|
||||
|
||||
PendingCommand *pend = prepareSessionCommand(cmdLogin);
|
||||
|
|
@ -244,12 +265,13 @@ void RemoteClient::passwordSaltResponse(const Response &response)
|
|||
{
|
||||
if (response.response_code() == Response::RespOk) {
|
||||
const Response_PasswordSalt &resp = response.GetExtension(Response_PasswordSalt::ext);
|
||||
QString salt = QString::fromStdString(resp.password_salt());
|
||||
if (salt.isEmpty()) { // the server does not recognize the user but allows them to enter unregistered
|
||||
password = ""; // the password will not be used
|
||||
auto passwordSalt = QString::fromStdString(resp.password_salt());
|
||||
if (passwordSalt.isEmpty()) { // the server does not recognize the user but allows them to enter unregistered
|
||||
password.clear(); // the password will not be used
|
||||
doLogin();
|
||||
} else {
|
||||
doLogin(salt);
|
||||
hashedPassword = PasswordHasher::computeHash(password, passwordSalt);
|
||||
doHashedLogin();
|
||||
}
|
||||
} else if (response.response_code() != Response::RespNotConnected) {
|
||||
emit loginError(response.response_code(), {}, 0, {});
|
||||
|
|
@ -438,6 +460,7 @@ void RemoteClient::doConnectToServer(const QString &hostname,
|
|||
password = _password;
|
||||
lastHostname = hostname;
|
||||
lastPort = port;
|
||||
hashedPassword.clear();
|
||||
|
||||
connectToHost(hostname, port);
|
||||
setStatus(StatusConnecting);
|
||||
|
|
@ -460,6 +483,7 @@ void RemoteClient::doRegisterToServer(const QString &hostname,
|
|||
realName = _realname;
|
||||
lastHostname = hostname;
|
||||
lastPort = port;
|
||||
hashedPassword.clear();
|
||||
|
||||
connectToHost(hostname, port);
|
||||
setStatus(StatusRegistering);
|
||||
|
|
@ -561,7 +585,7 @@ QString RemoteClient::getSrvClientID(const QString &_hostname)
|
|||
QHostAddress hostAddress = hostInfo.addresses().first();
|
||||
srvClientID += hostAddress.toString();
|
||||
} else {
|
||||
qDebug() << "Warning: ClientID generation host lookup failure [" << hostInfo.errorString() << "]";
|
||||
qWarning() << "ClientID generation host lookup failure [" << hostInfo.errorString() << "]";
|
||||
srvClientID += _hostname;
|
||||
}
|
||||
QString uniqueServerClientID =
|
||||
|
|
@ -648,6 +672,7 @@ void RemoteClient::doSubmitForgotPasswordResetToServer(const QString &hostname,
|
|||
lastPort = port;
|
||||
token = _token.trimmed();
|
||||
password = _newpassword;
|
||||
hashedPassword.clear();
|
||||
|
||||
connectToHost(lastHostname, lastPort);
|
||||
setStatus(StatusSubmitForgotPasswordReset);
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ private slots:
|
|||
const QString &_realname);
|
||||
void doRequestPasswordSalt();
|
||||
void doLogin();
|
||||
void doLogin(const QString &passwordSalt);
|
||||
void doHashedLogin();
|
||||
Command_Login generateCommandLogin();
|
||||
void doDisconnectFromServer();
|
||||
void doActivateToServer(const QString &_token);
|
||||
|
|
@ -101,6 +101,7 @@ private:
|
|||
QWebSocket *websocket;
|
||||
QString lastHostname;
|
||||
unsigned int lastPort;
|
||||
QString hashedPassword;
|
||||
|
||||
QString getSrvClientID(const QString &_hostname);
|
||||
bool newMissingFeatureFound(const QString &_serversMissingFeatures);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
#include "dlg_edit_avatar.h"
|
||||
#include "dlg_edit_password.h"
|
||||
#include "dlg_edit_user.h"
|
||||
#include "gettextwithmax.h"
|
||||
#include "passwordhasher.h"
|
||||
#include "pb/response_get_user_info.pb.h"
|
||||
#include "pb/session_commands.pb.h"
|
||||
#include "pending_command.h"
|
||||
|
|
@ -11,6 +13,8 @@
|
|||
|
||||
#include <QDateTime>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
|
||||
|
|
@ -200,7 +204,22 @@ void UserInfoBox::actEditInternal(const Response &r)
|
|||
|
||||
Command_AccountEdit cmd;
|
||||
cmd.set_real_name(dlg.getRealName().toStdString());
|
||||
cmd.set_email(dlg.getEmail().toStdString());
|
||||
if (client->getServerSupportsPasswordHash()) {
|
||||
if (email != dlg.getEmail()) {
|
||||
// real password is required to change email
|
||||
bool ok = false;
|
||||
QString password =
|
||||
getTextWithMax(this, tr("Enter Password"),
|
||||
tr("Password verification is required in order to change your email address"),
|
||||
QLineEdit::Password, "", &ok);
|
||||
if (!ok)
|
||||
return;
|
||||
cmd.set_password_check(password.toStdString());
|
||||
cmd.set_email(dlg.getEmail().toStdString());
|
||||
} // servers that support password hash do not require all fields to be filled anymore
|
||||
} else {
|
||||
cmd.set_email(dlg.getEmail().toStdString());
|
||||
}
|
||||
cmd.set_country(dlg.getCountry().toStdString());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
|
|
@ -216,9 +235,42 @@ void UserInfoBox::actPassword()
|
|||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
auto oldPassword = dlg.getOldPassword();
|
||||
auto newPassword = dlg.getNewPassword();
|
||||
|
||||
if (client->getServerSupportsPasswordHash()) {
|
||||
Command_RequestPasswordSalt cmd;
|
||||
cmd.set_user_name(client->getUserName().toStdString());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend,
|
||||
// we need qoverload here in order to select the right version of this function
|
||||
QOverload<const Response &, const CommandContainer &, const QVariant &>::of(&PendingCommand::finished),
|
||||
this, [=](const Response &response, const CommandContainer &, const QVariant &) {
|
||||
if (response.response_code() == Response::RespOk) {
|
||||
changePassword(oldPassword, newPassword);
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("An error occurred while trying to update your user information."));
|
||||
}
|
||||
});
|
||||
client->sendCommand(pend);
|
||||
} else {
|
||||
changePassword(oldPassword, newPassword);
|
||||
}
|
||||
}
|
||||
|
||||
void UserInfoBox::changePassword(const QString &oldPassword, const QString &newPassword)
|
||||
{
|
||||
Command_AccountPassword cmd;
|
||||
cmd.set_old_password(dlg.getOldPassword().toStdString());
|
||||
cmd.set_new_password(dlg.getNewPassword().toStdString());
|
||||
cmd.set_old_password(oldPassword.toStdString());
|
||||
if (client->getServerSupportsPasswordHash()) {
|
||||
auto passwordSalt = PasswordHasher::generateRandomSalt();
|
||||
QString hashedPassword = PasswordHasher::computeHash(newPassword, passwordSalt);
|
||||
cmd.set_hashed_new_password(hashedPassword.toStdString());
|
||||
} else {
|
||||
cmd.set_new_password(newPassword.toStdString());
|
||||
}
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
|
|
@ -254,6 +306,9 @@ void UserInfoBox::processEditResponse(const Response &r)
|
|||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("This server does not permit you to update your user informations."));
|
||||
break;
|
||||
case Response::RespWrongPassword:
|
||||
QMessageBox::critical(this, tr("Error"), tr("The entered password does not match your account."));
|
||||
break;
|
||||
case Response::RespInternalError:
|
||||
default:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
|
|
@ -280,7 +335,7 @@ void UserInfoBox::processPasswordResponse(const Response &r)
|
|||
case Response::RespInternalError:
|
||||
default:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("An error occured while trying to update your user informations."));
|
||||
tr("An error occurred while trying to update your user information."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ public slots:
|
|||
|
||||
private:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void changePassword(const QString &oldPassword, const QString &newPassword);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue