Refresh only last login time when necessary (avoid hammering the db, ref #420)

This commit is contained in:
emeric
2024-03-02 18:11:03 +01:00
parent f9d512ee19
commit f20db00417
4 changed files with 155 additions and 148 deletions
+45 -33
View File
@@ -19,6 +19,7 @@
#include "AuthServiceBase.hpp"
#include <cstdlib>
#include "database/Db.hpp"
#include "database/Session.hpp"
#include "database/User.hpp"
@@ -26,46 +27,57 @@
namespace Auth
{
using namespace Database;
using namespace Database;
AuthServiceBase::AuthServiceBase(Db& db)
: _db {db}
{}
AuthServiceBase::AuthServiceBase(Db& db)
: _db{ db }
{}
UserId
AuthServiceBase::getOrCreateUser(std::string_view loginName)
{
Session& session {getDbSession()};
auto transaction {session.createWriteTransaction()};
UserId AuthServiceBase::getOrCreateUser(std::string_view loginName)
{
Session& session{ getDbSession() };
auto transaction{ session.createWriteTransaction() };
User::pointer user {User::find(session, loginName)};
if (!user)
{
const UserType type {User::getCount(session) == 0 ? UserType::ADMIN : UserType::REGULAR};
User::pointer user{ User::find(session, loginName) };
if (!user)
{
const UserType type{ User::getCount(session) == 0 ? UserType::ADMIN : UserType::REGULAR };
LMS_LOG(AUTH, DEBUG, "Creating user '" << loginName << "', admin = " << (type == UserType::ADMIN));
LMS_LOG(AUTH, DEBUG, "Creating user '" << loginName << "', admin = " << (type == UserType::ADMIN));
user = session.create<User>(loginName);
user.modify()->setType(type);
}
user = session.create<User>(loginName);
user.modify()->setType(type);
}
return user->getId();
}
return user->getId();
}
void
AuthServiceBase::onUserAuthenticated(UserId userId)
{
Session& session {getDbSession()};
auto transaction {session.createWriteTransaction()};
void AuthServiceBase::onUserAuthenticated(UserId userId)
{
Session& session{ getDbSession() };
User::pointer user {User::find(session, userId)};
if (user)
user.modify()->setLastLogin(Wt::WDateTime::currentDateTime());
}
// Update last login only if relevant (avoid hammering write accesses to the database)
{
auto transaction{ session.createReadTransaction() };
Session&
AuthServiceBase::getDbSession()
{
return _db.getTLSSession();
}
const User::pointer user{ User::find(session, userId) };
if (!user)
return;
if (std::abs(Wt::WDateTime::currentDateTime().secsTo(user->getLastLogin())) < 60)
return;
}
{
auto transaction{ session.createWriteTransaction() };
if (User::pointer user{ User::find(session, userId) })
user.modify()->setLastLogin(Wt::WDateTime::currentDateTime());
}
}
Session& AuthServiceBase::getDbSession()
{
return _db.getTLSSession();
}
}
+12 -12
View File
@@ -24,23 +24,23 @@
namespace Database
{
class Db;
class Session;
class Db;
class Session;
}
namespace Auth
{
class AuthServiceBase
{
protected:
AuthServiceBase(Database::Db& db);
class AuthServiceBase
{
protected:
AuthServiceBase(Database::Db& db);
Database::UserId getOrCreateUser(std::string_view loginName);
void onUserAuthenticated(Database::UserId userId);
Database::UserId getOrCreateUser(std::string_view loginName);
void onUserAuthenticated(Database::UserId userId);
Database::Session& getDbSession();
Database::Session& getDbSession();
private:
Database::Db& _db;
};
private:
Database::Db& _db;
};
}
@@ -29,110 +29,105 @@
namespace Auth
{
InternalPasswordService::InternalPasswordService(Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService)
: PasswordServiceBase {db, maxThrottlerEntries, authTokenService}
{
_validator.setMinimumLength(Wt::Auth::PasswordStrengthType::OneCharClass, 4);
_validator.setMinimumLength(Wt::Auth::PasswordStrengthType::TwoCharClass, 4);
_validator.setMinimumLength(Wt::Auth::PasswordStrengthType::PassPhrase, 4);
_validator.setMinimumLength(Wt::Auth::PasswordStrengthType::ThreeCharClass, 4);
_validator.setMinimumLength(Wt::Auth::PasswordStrengthType::FourCharClass, 4);
_validator.setMinimumPassPhraseWords(1);
_validator.setMinimumMatchLength(3);
}
InternalPasswordService::InternalPasswordService(Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService)
: PasswordServiceBase{ db, maxThrottlerEntries, authTokenService }
{
_validator.setMinimumLength(Wt::Auth::PasswordStrengthType::OneCharClass, 4);
_validator.setMinimumLength(Wt::Auth::PasswordStrengthType::TwoCharClass, 4);
_validator.setMinimumLength(Wt::Auth::PasswordStrengthType::PassPhrase, 4);
_validator.setMinimumLength(Wt::Auth::PasswordStrengthType::ThreeCharClass, 4);
_validator.setMinimumLength(Wt::Auth::PasswordStrengthType::FourCharClass, 4);
_validator.setMinimumPassPhraseWords(1);
_validator.setMinimumMatchLength(3);
}
bool
InternalPasswordService::checkUserPassword(std::string_view loginName, std::string_view password)
{
LMS_LOG(AUTH, DEBUG, "Checking internal password for user '" << loginName << "'");
bool InternalPasswordService::checkUserPassword(std::string_view loginName, std::string_view password)
{
LMS_LOG(AUTH, DEBUG, "Checking internal password for user '" << loginName << "'");
Database::User::PasswordHash passwordHash;
{
Database::Session& session {getDbSession()};
auto transaction {session.createReadTransaction()};
Database::User::PasswordHash passwordHash;
{
Database::Session& session{ getDbSession() };
auto transaction{ session.createReadTransaction() };
const Database::User::pointer user {Database::User::find(session, loginName)};
if (!user)
{
LMS_LOG(AUTH, DEBUG, "hashing random stuff");
// hash random stuff here to waste some time
hashRandomPassword();
return false;
}
const Database::User::pointer user{ Database::User::find(session, loginName) };
if (!user)
{
LMS_LOG(AUTH, DEBUG, "hashing random stuff");
// hash random stuff here to waste some time
hashRandomPassword();
return false;
}
// Don't allow users being created or coming from other backends
passwordHash = user->getPasswordHash();
if (passwordHash.salt.empty() || passwordHash.hash.empty())
{
// hash random stuff here to waste some time
hashRandomPassword();
return false;
}
}
// Don't allow users being created or coming from other backends
passwordHash = user->getPasswordHash();
if (passwordHash.salt.empty() || passwordHash.hash.empty())
{
// hash random stuff here to waste some time
hashRandomPassword();
return false;
}
}
return _hashFunc.verify(std::string {password}, std::string {passwordHash.salt}, std::string {passwordHash.hash});
}
return _hashFunc.verify(std::string{ password }, std::string{ passwordHash.salt }, std::string{ passwordHash.hash });
}
bool
InternalPasswordService::canSetPasswords() const
{
return true;
}
bool InternalPasswordService::canSetPasswords() const
{
return true;
}
IPasswordService::PasswordAcceptabilityResult
InternalPasswordService::checkPasswordAcceptability(std::string_view password, const PasswordValidationContext& context) const
{
switch (context.userType)
{
case Database::UserType::ADMIN:
case Database::UserType::REGULAR:
return _validator.evaluateStrength(std::string {password}, context.loginName, "").isValid() ? PasswordAcceptabilityResult::OK : PasswordAcceptabilityResult::TooWeak;
case Database::UserType::DEMO:
return password == context.loginName ? PasswordAcceptabilityResult::OK : PasswordAcceptabilityResult::MustMatchLoginName;
}
IPasswordService::PasswordAcceptabilityResult InternalPasswordService::checkPasswordAcceptability(std::string_view password, const PasswordValidationContext& context) const
{
switch (context.userType)
{
case Database::UserType::ADMIN:
case Database::UserType::REGULAR:
return _validator.evaluateStrength(std::string{ password }, context.loginName, "").isValid() ? PasswordAcceptabilityResult::OK : PasswordAcceptabilityResult::TooWeak;
case Database::UserType::DEMO:
return password == context.loginName ? PasswordAcceptabilityResult::OK : PasswordAcceptabilityResult::MustMatchLoginName;
}
throw NotImplementedException {};
}
throw NotImplementedException{};
}
void
InternalPasswordService::setPassword(Database::UserId userId, std::string_view newPassword)
{
const Database::User::PasswordHash passwordHash {hashPassword(newPassword)};
void InternalPasswordService::setPassword(Database::UserId userId, std::string_view newPassword)
{
const Database::User::PasswordHash passwordHash{ hashPassword(newPassword) };
Database::Session& session {getDbSession()};
auto transaction {session.createWriteTransaction()};
Database::Session& session{ getDbSession() };
auto transaction{ session.createWriteTransaction() };
Database::User::pointer user {Database::User::find(session, userId)};
if (!user)
throw Exception {"User not found!"};
Database::User::pointer user{ Database::User::find(session, userId) };
if (!user)
throw Exception{ "User not found!" };
switch (checkPasswordAcceptability(newPassword, PasswordValidationContext {user->getLoginName(), user->getType()}))
{
case PasswordAcceptabilityResult::OK:
break;
case PasswordAcceptabilityResult::TooWeak:
throw PasswordTooWeakException {};
case PasswordAcceptabilityResult::MustMatchLoginName:
throw PasswordMustMatchLoginNameException {};
}
switch (checkPasswordAcceptability(newPassword, PasswordValidationContext{ user->getLoginName(), user->getType() }))
{
case PasswordAcceptabilityResult::OK:
break;
case PasswordAcceptabilityResult::TooWeak:
throw PasswordTooWeakException{};
case PasswordAcceptabilityResult::MustMatchLoginName:
throw PasswordMustMatchLoginNameException{};
}
user.modify()->setPasswordHash(passwordHash);
getAuthTokenService().clearAuthTokens(userId);
}
user.modify()->setPasswordHash(passwordHash);
getAuthTokenService().clearAuthTokens(userId);
}
Database::User::PasswordHash
InternalPasswordService::hashPassword(std::string_view password) const
{
const std::string salt {Wt::WRandom::generateId(32)};
Database::User::PasswordHash InternalPasswordService::hashPassword(std::string_view password) const
{
const std::string salt{ Wt::WRandom::generateId(32) };
return {salt, _hashFunc.compute(std::string {password}, salt)};
}
return { salt, _hashFunc.compute(std::string {password}, salt) };
}
void
InternalPasswordService::hashRandomPassword() const
{
hashPassword(Wt::WRandom::generateId(32));
}
void
InternalPasswordService::hashRandomPassword() const
{
hashPassword(Wt::WRandom::generateId(32));
}
} // namespace Auth
@@ -28,25 +28,25 @@
namespace Auth
{
class IAuthTokenService;
class IAuthTokenService;
class InternalPasswordService : public PasswordServiceBase
{
public:
InternalPasswordService(Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService);
class InternalPasswordService : public PasswordServiceBase
{
public:
InternalPasswordService(Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService);
private:
bool checkUserPassword(std::string_view loginName, std::string_view password) override;
private:
bool checkUserPassword(std::string_view loginName, std::string_view password) override;
bool canSetPasswords() const override;
PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override;
void setPassword(Database::UserId userId, std::string_view newPassword) override;
bool canSetPasswords() const override;
PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override;
void setPassword(Database::UserId userId, std::string_view newPassword) override;
Database::User::PasswordHash hashPassword(std::string_view password) const;
void hashRandomPassword() const;
Database::User::PasswordHash hashPassword(std::string_view password) const;
void hashRandomPassword() const;
const Wt::Auth::BCryptHashFunction _hashFunc {7}; // TODO parametrize this
Wt::Auth::PasswordStrengthValidator _validator;
};
const Wt::Auth::BCryptHashFunction _hashFunc{ 7 }; // TODO parametrize this
Wt::Auth::PasswordStrengthValidator _validator;
};
}