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