mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-10 00:04:48 -07:00
allow login using hashed passwords (#4464)
* Support getting a user's password salt via initial websocket connection (added to Event_ServerIdentification) * Nonsense stuff to figure out later * move passwordhasher to correct location * protobuf changes * add ext to protobuf * implement request password salt server side * add supportspasswordhash to server identification * check backwards compatibility * reset some changes to master * implement get password salt client side * implement checking hashed passwords on server login * check for registration requirement on getting password salt * properly check password salt response and show errors * remove unused property * add password salt to list of response types Co-authored-by: ZeldaZach <zahalpern+github@gmail.com>
This commit is contained in:
parent
b0845837c2
commit
45d86e7ab7
23 changed files with 193 additions and 26 deletions
|
|
@ -1,44 +0,0 @@
|
|||
#include "passwordhasher.h"
|
||||
|
||||
#include "rng_sfmt.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
|
||||
void PasswordHasher::initialize()
|
||||
{
|
||||
// dummy
|
||||
}
|
||||
|
||||
QString PasswordHasher::computeHash(const QString &password, const QString &salt)
|
||||
{
|
||||
QCryptographicHash::Algorithm algo = QCryptographicHash::Sha512;
|
||||
const int rounds = 1000;
|
||||
|
||||
QByteArray hash = (salt + password).toUtf8();
|
||||
for (int i = 0; i < rounds; ++i) {
|
||||
hash = QCryptographicHash::hash(hash, algo);
|
||||
}
|
||||
QString hashedPass = salt + QString(hash.toBase64());
|
||||
return hashedPass;
|
||||
}
|
||||
|
||||
QString PasswordHasher::generateRandomSalt(const int len)
|
||||
{
|
||||
static const char alphanum[] = "0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
QString ret;
|
||||
int size = sizeof(alphanum) - 1;
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
ret.append(alphanum[rng->rand(0, size)]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString PasswordHasher::generateActivationToken()
|
||||
{
|
||||
return QCryptographicHash::hash(generateRandomSalt().toUtf8(), QCryptographicHash::Md5).toBase64().left(16);
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#ifndef PASSWORDHASHER_H
|
||||
#define PASSWORDHASHER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class PasswordHasher
|
||||
{
|
||||
public:
|
||||
static void initialize();
|
||||
static QString computeHash(const QString &password, const QString &salt);
|
||||
static QString generateRandomSalt(const int len = 16);
|
||||
static QString generateActivationToken();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -287,7 +287,8 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot
|
|||
const QString &password,
|
||||
const QString &clientId,
|
||||
QString &reasonStr,
|
||||
int &banSecondsLeft)
|
||||
int &banSecondsLeft,
|
||||
bool passwordNeedsHash)
|
||||
{
|
||||
switch (server->getAuthenticationMethod()) {
|
||||
case Servatrice::AuthenticationNone:
|
||||
|
|
@ -324,7 +325,13 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot
|
|||
qDebug("Login denied: user not active");
|
||||
return UserIsInactive;
|
||||
}
|
||||
if (correctPassword == PasswordHasher::computeHash(password, correctPassword.left(16))) {
|
||||
QString hashedPassword;
|
||||
if (passwordNeedsHash) {
|
||||
hashedPassword = PasswordHasher::computeHash(password, correctPassword.left(16));
|
||||
} else {
|
||||
hashedPassword = password;
|
||||
}
|
||||
if (correctPassword == hashedPassword) {
|
||||
qDebug("Login accepted: password right");
|
||||
return PasswordRight;
|
||||
} else {
|
||||
|
|
@ -490,6 +497,28 @@ bool Servatrice_DatabaseInterface::userExists(const QString &user)
|
|||
return false;
|
||||
}
|
||||
|
||||
QString Servatrice_DatabaseInterface::getUserSalt(const QString &user)
|
||||
{
|
||||
if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) {
|
||||
checkSql();
|
||||
|
||||
QSqlQuery *query =
|
||||
prepareQuery("SELECT SUBSTRING(password_sha512, 1, 16) FROM {prefix}_users WHERE name = :name");
|
||||
|
||||
query->bindValue(":name", user);
|
||||
if (!execSqlQuery(query)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!query->next()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return query->value(0).toString();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int Servatrice_DatabaseInterface::getUserIdInDB(const QString &name)
|
||||
{
|
||||
if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ protected:
|
|||
const QString &password,
|
||||
const QString &clientId,
|
||||
QString &reasonStr,
|
||||
int &secondsLeft);
|
||||
int &banSecondsLeft,
|
||||
bool passwordNeedsHash);
|
||||
|
||||
public slots:
|
||||
void initDatabase(const QSqlDatabase &_sqlDatabase);
|
||||
|
|
@ -59,6 +60,7 @@ public:
|
|||
|
||||
bool activeUserExists(const QString &user);
|
||||
bool userExists(const QString &user);
|
||||
QString getUserSalt(const QString &user);
|
||||
int getUserIdInDB(const QString &name);
|
||||
QMap<QString, ServerInfo_User> getBuddyList(const QString &name);
|
||||
QMap<QString, ServerInfo_User> getIgnoreList(const QString &name);
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
#include "pb/response_deck_list.pb.h"
|
||||
#include "pb/response_deck_upload.pb.h"
|
||||
#include "pb/response_forgotpasswordrequest.pb.h"
|
||||
#include "pb/response_password_salt.pb.h"
|
||||
#include "pb/response_register.pb.h"
|
||||
#include "pb/response_replay_download.pb.h"
|
||||
#include "pb/response_replay_list.pb.h"
|
||||
|
|
@ -95,6 +96,9 @@ bool AbstractServerSocketInterface::initSession()
|
|||
identEvent.set_server_name(servatrice->getServerName().toStdString());
|
||||
identEvent.set_server_version(VERSION_STRING);
|
||||
identEvent.set_protocol_version(protocolVersion);
|
||||
if (servatrice->getAuthenticationMethod() == Servatrice::AuthenticationSql) {
|
||||
identEvent.set_server_options(Event_ServerIdentification::SupportsPasswordHash);
|
||||
}
|
||||
SessionEvent *identSe = prepareSessionEvent(identEvent);
|
||||
sendProtocolItem(*identSe);
|
||||
delete identSe;
|
||||
|
|
@ -193,6 +197,9 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedSessionComm
|
|||
return cmdAccountImage(cmd.GetExtension(Command_AccountImage::ext), rc);
|
||||
case SessionCommand::ACCOUNT_PASSWORD:
|
||||
return cmdAccountPassword(cmd.GetExtension(Command_AccountPassword::ext), rc);
|
||||
case SessionCommand::REQUEST_PASSWORD_SALT:
|
||||
return cmdRequestPasswordSalt(cmd.GetExtension(Command_RequestPasswordSalt::ext), rc);
|
||||
break;
|
||||
default:
|
||||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
|
|
@ -1480,6 +1487,25 @@ AbstractServerSocketInterface::cmdForgotPasswordChallenge(const Command_ForgotPa
|
|||
return continuePasswordRequest(userName, clientId, rc, true);
|
||||
}
|
||||
|
||||
Response::ResponseCode AbstractServerSocketInterface::cmdRequestPasswordSalt(const Command_RequestPasswordSalt &cmd,
|
||||
ResponseContainer &rc)
|
||||
{
|
||||
const QString userName = QString::fromStdString(cmd.user_name());
|
||||
QString passwordSalt = sqlInterface->getUserSalt(userName);
|
||||
if (passwordSalt.isEmpty()) {
|
||||
if (server->getRegOnlyServerEnabled()) {
|
||||
return Response::RespRegistrationRequired;
|
||||
} else {
|
||||
// user does not exist but is allowed to log in unregistered without password
|
||||
return Response::RespOk;
|
||||
}
|
||||
}
|
||||
auto *re = new Response_PasswordSalt;
|
||||
re->set_password_salt(passwordSalt.toStdString());
|
||||
rc.setResponseExtension(re);
|
||||
return Response::RespOk;
|
||||
}
|
||||
|
||||
// ADMIN FUNCTIONS.
|
||||
// Permission is checked by the calling function.
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ private:
|
|||
Response::ResponseCode cmdForgotPasswordReset(const Command_ForgotPasswordReset &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdForgotPasswordChallenge(const Command_ForgotPasswordChallenge &cmd,
|
||||
ResponseContainer &rc);
|
||||
Response::ResponseCode cmdRequestPasswordSalt(const Command_RequestPasswordSalt &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode processExtendedSessionCommand(int cmdType, const SessionCommand &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode
|
||||
processExtendedModeratorCommand(int cmdType, const ModeratorCommand &cmd, ResponseContainer &rc);
|
||||
|
|
@ -198,7 +199,7 @@ class WebsocketServerSocketInterface : public AbstractServerSocketInterface
|
|||
public:
|
||||
WebsocketServerSocketInterface(Servatrice *_server,
|
||||
Servatrice_DatabaseInterface *_databaseInterface,
|
||||
QObject *parent = 0);
|
||||
QObject *parent = nullptr);
|
||||
~WebsocketServerSocketInterface();
|
||||
|
||||
QHostAddress getPeerAddress() const
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue