Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions src/gui/optionsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,17 @@ namespace
}
};

bool isValidWebUIUsername(const QString &username)
bool isValidWebUIUsernameLength(const QString &username)
{
return (username.length() >= WEBUI_MIN_USERNAME_LENGTH);
}

bool isValidWebUIPassword(const QString &password)
bool isValidWebUIUsernameCharacterSet(const QString &username)
{
return !username.contains(u":");
}

bool isValidWebUIPasswordLength(const QString &password)
{
return (password.length() >= WEBUI_MIN_PASSWORD_LENGTH);
}
Expand Down Expand Up @@ -1444,9 +1449,9 @@ void OptionsDialog::saveWebUITabOptions() const
pref->setWebUIBanDuration(std::chrono::seconds {m_ui->spinBanDuration->value()});
pref->setWebUISessionTimeout(m_ui->spinSessionTimeout->value());
// Authentication
if (const QString username = webUIUsername(); isValidWebUIUsername(username))
if (const QString username = webUIUsername(); isValidWebUIUsernameLength(username) && isValidWebUIUsernameCharacterSet(username))
pref->setWebUIUsername(username);
if (const QString password = webUIPassword(); isValidWebUIPassword(password))
if (const QString password = webUIPassword(); isValidWebUIPasswordLength(password))
pref->setWebUIPassword(Utils::Password::PBKDF2::generate(password));
pref->setWebUILocalAuthEnabled(!m_ui->checkBypassLocalAuth->isChecked());
pref->setWebUIAuthSubnetWhitelistEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked());
Expand Down Expand Up @@ -2106,14 +2111,20 @@ QString OptionsDialog::webUIPassword() const

bool OptionsDialog::webUIAuthenticationOk()
{
if (!isValidWebUIUsername(webUIUsername()))
const QString username = webUIUsername();
if (!isValidWebUIUsernameLength(username))
{
QMessageBox::warning(this, tr("Length Error"), tr("The WebUI username must be at least 3 characters long."));
return false;
}
if (!isValidWebUIUsernameCharacterSet(username))
{
QMessageBox::warning(this, tr("Character Error"), tr("The WebUI username must not contain a colon."));
return false;
}

const bool dontChangePassword = webUIPassword().isEmpty() && !Preferences::instance()->getWebUIPassword().isEmpty();
if (!isValidWebUIPassword(webUIPassword()) && !dontChangePassword)
if (!isValidWebUIPasswordLength(webUIPassword()) && !dontChangePassword)
{
QMessageBox::warning(this, tr("Length Error"), tr("The WebUI password must be at least 6 characters long."));
return false;
Expand Down
14 changes: 13 additions & 1 deletion src/webui/api/appcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -894,9 +894,21 @@ void AppController::setPreferencesAction()
pref->setWebUIHttpsKeyPath(Path(it.value().toString()));
// Authentication
if (hasKey(u"web_ui_username"_s))
pref->setWebUIUsername(it.value().toString());
{
const QString username = it.value().toString();
if (username.length() < 3)
throw APIError(APIErrorType::BadParams, tr("WebUI username must be at least 3 characters long"));
if (username.contains(u":"))
throw APIError(APIErrorType::BadParams, tr("WebUI username cannot contain a colon"));
pref->setWebUIUsername(username);
}
if (hasKey(u"web_ui_password"_s))
{
const QString password = it.value().toString();
if (password.length() < 6)
throw APIError(APIErrorType::BadParams, tr("WebUI password must be at least 6 characters long"));
pref->setWebUIPassword(Utils::Password::PBKDF2::generate(it.value().toByteArray()));
}
if (hasKey(u"bypass_local_auth"_s))
pref->setWebUILocalAuthEnabled(!it.value().toBool());
if (hasKey(u"bypass_auth_subnet_whitelist_enabled"_s))
Expand Down
77 changes: 1 addition & 76 deletions src/webui/api/authcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@
#include <QString>

#include "base/global.h"
#include "base/logger.h"
#include "base/preferences.h"
#include "base/utils/password.h"
#include "apierror.h"
#include "isessionmanager.h"

Expand All @@ -43,18 +40,6 @@ AuthController::AuthController(ISessionManager *sessionManager, IApplication *ap
{
}

void AuthController::setUsername(const QString &username)
{
m_username = username;
setResult(QString());
}

void AuthController::setPasswordHash(const QByteArray &passwordHash)
{
m_passwordHash = passwordHash;
setResult(QString());
}

void AuthController::loginAction()
{
if (m_sessionManager->session())
Expand All @@ -63,37 +48,13 @@ void AuthController::loginAction()
return;
}

const QString clientAddr = m_sessionManager->clientId();
const QString usernameFromWeb = params()[u"username"_s];
const QString passwordFromWeb = params()[u"password"_s];

if (isBanned())
{
LogMsg(tr("WebAPI login failure. Reason: IP has been banned, IP: %1, username: %2")
.arg(clientAddr, usernameFromWeb)
, Log::WARNING);
throw APIError(APIErrorType::AccessDenied
, tr("Your IP address has been banned after too many failed authentication attempts."));
}

const bool usernameEqual = Utils::Password::slowEquals(usernameFromWeb.toUtf8(), m_username.toUtf8());
const bool passwordEqual = Utils::Password::PBKDF2::verify(m_passwordHash, passwordFromWeb);

if (usernameEqual && passwordEqual)
if (m_sessionManager->validateCredentials(params()[u"username"_s], params()[u"password"_s]))
{
m_clientFailedLogins.remove(clientAddr);

m_sessionManager->sessionStart();
setStatus(APIStatus::Ok);
LogMsg(tr("WebAPI login success. IP: %1").arg(clientAddr));
}
else
{
if (Preferences::instance()->getWebUIMaxAuthFailCount() > 0)
increaseFailedAttempts();
LogMsg(tr("WebAPI login failure. Reason: invalid credentials, attempt count: %1, IP: %2, username: %3")
.arg(QString::number(failedAttemptsCount()), clientAddr, usernameFromWeb)
, Log::WARNING);
throw APIError(APIErrorType::Unauthorized);
}
}
Expand All @@ -103,39 +64,3 @@ void AuthController::logoutAction()
m_sessionManager->sessionEnd();
setResult(QString());
}

bool AuthController::isBanned() const
{
const auto failedLoginIter = m_clientFailedLogins.constFind(m_sessionManager->clientId());
if (failedLoginIter == m_clientFailedLogins.cend())
return false;

bool isBanned = (failedLoginIter->banTimer.remainingTime() >= 0);
if (isBanned && failedLoginIter->banTimer.hasExpired())
{
m_clientFailedLogins.erase(failedLoginIter);
isBanned = false;
}

return isBanned;
}

int AuthController::failedAttemptsCount() const
{
return m_clientFailedLogins.value(m_sessionManager->clientId()).failedAttemptsCount;
}

void AuthController::increaseFailedAttempts()
{
Q_ASSERT(Preferences::instance()->getWebUIMaxAuthFailCount() > 0);

FailedLogin &failedLogin = m_clientFailedLogins[m_sessionManager->clientId()];
++failedLogin.failedAttemptsCount;

if (failedLogin.failedAttemptsCount >= Preferences::instance()->getWebUIMaxAuthFailCount())
{
// Max number of failed attempts reached
// Start ban period
failedLogin.banTimer.setRemainingTime(Preferences::instance()->getWebUIBanDuration());
}
}
22 changes: 0 additions & 22 deletions src/webui/api/authcontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@

#pragma once

#include <QByteArray>
#include <QDeadlineTimer>
#include <QHash>
#include <QString>

#include "apicontroller.h"

class QString;
Expand All @@ -47,27 +42,10 @@ class AuthController : public APIController
public:
explicit AuthController(ISessionManager *sessionManager, IApplication *app, QObject *parent = nullptr);

void setUsername(const QString &username);
void setPasswordHash(const QByteArray &passwordHash);

private slots:
void loginAction();
void logoutAction();

private:
bool isBanned() const;
int failedAttemptsCount() const;
void increaseFailedAttempts();

ISessionManager *m_sessionManager = nullptr;

QString m_username;
QByteArray m_passwordHash;

struct FailedLogin
{
int failedAttemptsCount = 0;
QDeadlineTimer banTimer {-1};
};
mutable QHash<QString, FailedLogin> m_clientFailedLogins;
};
2 changes: 1 addition & 1 deletion src/webui/api/isessionmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ struct ISession
struct ISessionManager
{
virtual ~ISessionManager() = default;
virtual QString clientId() const = 0;
virtual ISession *session() = 0;
virtual void sessionStart() = 0;
virtual void sessionEnd() = 0;
virtual bool validateCredentials(const QString &username, const QString &password) const = 0;
};
Loading
Loading