Moved away from Wt::Auth and use a simplified (?) login/password system with a login throttler based on IP address
This commit is contained in:
+192
-47
@@ -19,82 +19,227 @@
|
||||
|
||||
#include "Auth.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
#include <Wt/WFormModel.h>
|
||||
#include <Wt/WLineEdit.h>
|
||||
#include <Wt/WCheckBox.h>
|
||||
#include <Wt/WPushButton.h>
|
||||
|
||||
#include "auth/AuthService.hpp"
|
||||
#include "main/Service.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
#include "utils/Utils.hpp"
|
||||
|
||||
#include "common/Validators.hpp"
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
Auth::Auth()
|
||||
: Wt::WTemplateFormView(Wt::WString::tr("Lms.Auth.template"))
|
||||
{
|
||||
_model = std::make_shared<Wt::Auth::AuthModel>(LmsApp->getDbSession().getAuthService(), LmsApp->getDbSession().getUserDatabase());
|
||||
_model->addPasswordAuth(&Database::Session::getPasswordService());
|
||||
static const std::string authCookieName {"LmsAuth"};
|
||||
|
||||
// LoginName
|
||||
setFormWidget(Wt::Auth::AuthModel::LoginNameField, std::make_unique<Wt::WLineEdit>());
|
||||
static
|
||||
std::string
|
||||
createSecret()
|
||||
{
|
||||
std::array<std::uint8_t, 32> buffer;
|
||||
fillRandom(buffer);
|
||||
|
||||
std::ostringstream oss;
|
||||
for (std::uint8_t b : buffer)
|
||||
oss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(b);
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
void
|
||||
createAuthToken(Database::IdType userId)
|
||||
{
|
||||
|
||||
const std::string secret {createSecret()};
|
||||
const Wt::WDateTime now {Wt::WDateTime::currentDateTime()};
|
||||
const Wt::WDateTime expiry {now.addYears(1)};
|
||||
|
||||
{
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
auto demoUser = Database::User::getDemo(LmsApp->getDbSession());
|
||||
if (demoUser)
|
||||
{
|
||||
const std::string userName {LmsApp->getDbSession().getUserLoginName(demoUser)};
|
||||
_model->setValue(Wt::Auth::AuthModel::LoginNameField, userName );
|
||||
_model->setValue(Wt::Auth::AuthModel::PasswordField, userName);
|
||||
}
|
||||
Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), userId)};
|
||||
Database::AuthToken::create(LmsApp->getDbSession(), secret, expiry, user);
|
||||
|
||||
LMS_LOG(UI, DEBUG) << "Created auth token for user '" << user->getLoginName() << "', expiry = " << expiry.toString();
|
||||
}
|
||||
|
||||
LmsApp->setCookie(authCookieName,
|
||||
secret,
|
||||
expiry.toTime_t() - now.toTime_t(),
|
||||
"",
|
||||
"",
|
||||
LmsApp->environment().urlScheme() == "https");
|
||||
}
|
||||
|
||||
|
||||
boost::optional<Database::IdType>
|
||||
processAuthToken(const Wt::WEnvironment& env)
|
||||
{
|
||||
const std::string* authCookie {env.getCookie(authCookieName)};
|
||||
if (!authCookie)
|
||||
return boost::none;
|
||||
|
||||
Database::IdType userId {};
|
||||
{
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
Database::AuthToken::pointer authToken {Database::AuthToken::getByValue(LmsApp->getDbSession(), *authCookie)};
|
||||
if (!authToken)
|
||||
{
|
||||
LMS_LOG(UI, INFO) << "Client '" << env.clientAddress() << "' presented a token that has not been found";
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
if (authToken->getExpiry() < Wt::WDateTime::currentDateTime())
|
||||
{
|
||||
LMS_LOG(UI, INFO) << "Expired auth token for user '" << authToken->getUser()->getLoginName() << "'!";
|
||||
authToken.remove();
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
LMS_LOG(UI, DEBUG) << "Found auth token for user '" << authToken->getUser()->getLoginName() << "'!";
|
||||
userId = authToken->getUser().id();
|
||||
|
||||
authToken.remove();
|
||||
}
|
||||
|
||||
createAuthToken(userId);
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
class AuthModel : public Wt::WFormModel
|
||||
{
|
||||
public:
|
||||
|
||||
// Associate each field with a unique string literal.
|
||||
static const Field LoginNameField;
|
||||
static const Field PasswordField;
|
||||
static const Field RememberMeField;
|
||||
|
||||
AuthModel()
|
||||
{
|
||||
addField(LoginNameField);
|
||||
addField(PasswordField);
|
||||
addField(RememberMeField);
|
||||
|
||||
setValidator(LoginNameField, createNameValidator());
|
||||
setValidator(PasswordField, createMandatoryValidator());
|
||||
}
|
||||
|
||||
|
||||
void saveData()
|
||||
{
|
||||
{
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
Database::User::pointer user {Database::User::getByLoginName(LmsApp->getDbSession(), valueText(LoginNameField).toUTF8())};
|
||||
user.modify()->setLastLogin(Wt::WDateTime::currentDateTime());
|
||||
_userId = user.id();
|
||||
}
|
||||
|
||||
if (Wt::asNumber(value(RememberMeField)))
|
||||
createAuthToken(*_userId);
|
||||
}
|
||||
|
||||
bool validateField(Field field)
|
||||
{
|
||||
Wt::WString error;
|
||||
|
||||
if (field == PasswordField)
|
||||
{
|
||||
switch (getService<::Auth::AuthService>()->checkUserPassword(
|
||||
LmsApp->getDbSession(),
|
||||
boost::asio::ip::address::from_string(LmsApp->environment().clientAddress()),
|
||||
valueText(LoginNameField).toUTF8(),
|
||||
valueText(PasswordField).toUTF8()))
|
||||
{
|
||||
case ::Auth::AuthService::PasswordCheckResult::Match:
|
||||
break;
|
||||
case ::Auth::AuthService::PasswordCheckResult::Mismatch:
|
||||
error = Wt::WString::tr("Lms.password-bad-login-combination");
|
||||
break;
|
||||
case ::Auth::AuthService::PasswordCheckResult::Throttled:
|
||||
error = Wt::WString::tr("Lms.password-client-throttled");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return Wt::WFormModel::validateField(field);
|
||||
}
|
||||
|
||||
setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::ValidationState::Valid : Wt::ValidationState::Invalid, error));
|
||||
|
||||
return (validation(field).state() == Wt::ValidationState::Valid);
|
||||
}
|
||||
|
||||
boost::optional<Database::IdType> getUserId() const { return _userId; }
|
||||
|
||||
private:
|
||||
|
||||
boost::optional<Database::IdType> _userId;
|
||||
};
|
||||
|
||||
const AuthModel::Field AuthModel::LoginNameField {"login-name"};
|
||||
const AuthModel::Field AuthModel::PasswordField {"password"};
|
||||
const AuthModel::Field AuthModel::RememberMeField {"remember-me"};
|
||||
|
||||
|
||||
Auth::Auth()
|
||||
: Wt::WTemplateFormView {Wt::WString::tr("Lms.Auth.template")}
|
||||
{
|
||||
auto model {std::make_shared<AuthModel>()};
|
||||
|
||||
auto processAuth = [=]()
|
||||
{
|
||||
updateModel(model.get());
|
||||
|
||||
if (model->validate())
|
||||
{
|
||||
model->saveData();
|
||||
userLoggedIn.emit(*model->getUserId());
|
||||
}
|
||||
else
|
||||
updateView(model.get());
|
||||
};
|
||||
|
||||
// LoginName
|
||||
setFormWidget(AuthModel::LoginNameField, std::make_unique<Wt::WLineEdit>());
|
||||
|
||||
// Password
|
||||
auto password = std::make_unique<Wt::WLineEdit>();
|
||||
password->setEchoMode(Wt::EchoMode::Password);
|
||||
password->enterPressed().connect(this, &Auth::processAuth);
|
||||
setFormWidget(Wt::Auth::AuthModel::PasswordField, std::move(password));
|
||||
password->enterPressed().connect(this, processAuth);
|
||||
setFormWidget(AuthModel::PasswordField, std::move(password));
|
||||
|
||||
// Remember Me
|
||||
setFormWidget(Wt::Auth::AuthModel::RememberMeField, std::make_unique<Wt::WCheckBox>());
|
||||
// Remember me
|
||||
setFormWidget(AuthModel::RememberMeField, std::make_unique<Wt::WCheckBox>());
|
||||
|
||||
Wt::WPushButton* loginBtn = bindNew<Wt::WPushButton>("login-btn", Wt::WString::tr("Lms.login"));
|
||||
loginBtn->clicked().connect(this, &Auth::processAuth);
|
||||
|
||||
LmsApp->getDbSession().getLogin().changed().connect(std::bind([=]
|
||||
{
|
||||
if (LmsApp->getDbSession().getLogin().loggedIn())
|
||||
this->setHidden(true);
|
||||
}));
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
updateView(_model.get());
|
||||
|
||||
Wt::Auth::User user = _model->processAuthToken();
|
||||
if (user.isValid())
|
||||
{
|
||||
LMS_LOG(UI, DEBUG) << "Valid user found from auth token (id = " << user.id() << ")";
|
||||
_model->loginUser(LmsApp->getDbSession().getLogin(), user, Wt::Auth::LoginState::Weak);
|
||||
Database::User::pointer demoUser {Database::User::getDemo(LmsApp->getDbSession())};
|
||||
if (demoUser)
|
||||
{
|
||||
model->setValue(AuthModel::LoginNameField, demoUser->getLoginName());
|
||||
model->setValue(AuthModel::PasswordField, demoUser->getLoginName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Auth::processAuth()
|
||||
{
|
||||
updateModel(_model.get());
|
||||
Wt::WPushButton* loginBtn {bindNew<Wt::WPushButton>("login-btn", Wt::WString::tr("Lms.login"))};
|
||||
loginBtn->clicked().connect(this, processAuth);
|
||||
|
||||
if (_model->validate())
|
||||
_model->login(LmsApp->getDbSession().getLogin());
|
||||
else
|
||||
updateView(_model.get());
|
||||
}
|
||||
updateView(model.get());
|
||||
|
||||
void
|
||||
Auth::logout()
|
||||
{
|
||||
_model->logout(LmsApp->getDbSession().getLogin());
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
+9
-7
@@ -19,22 +19,24 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <Wt/WTemplateFormView.h>
|
||||
#include <Wt/Auth/AuthModel.h>
|
||||
|
||||
#include "database/Types.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
|
||||
// If success, returns the authenticated user id
|
||||
boost::optional<Database::IdType> processAuthToken(const Wt::WEnvironment& env);
|
||||
|
||||
class Auth : public Wt::WTemplateFormView
|
||||
{
|
||||
public:
|
||||
Auth();
|
||||
|
||||
void logout();
|
||||
|
||||
private:
|
||||
void processAuth();
|
||||
|
||||
std::shared_ptr<Wt::Auth::AuthModel> _model;
|
||||
Wt::Signal<Database::IdType /*userId*/> userLoggedIn;
|
||||
};
|
||||
|
||||
|
||||
|
||||
+82
-38
@@ -28,13 +28,13 @@
|
||||
#include <Wt/WServer.h>
|
||||
#include <Wt/WStackedWidget.h>
|
||||
#include <Wt/WText.h>
|
||||
#include <Wt/Auth/Identity.h>
|
||||
|
||||
#include "config/config.h"
|
||||
#include "cover/CoverArtGrabber.hpp"
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/Cluster.hpp"
|
||||
#include "database/Release.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "explore/Explore.hpp"
|
||||
#include "main/Service.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "admin/UsersView.hpp"
|
||||
#include "resource/ImageResource.hpp"
|
||||
#include "resource/AudioResource.hpp"
|
||||
#include "Auth.hpp"
|
||||
#include "MediaPlayer.hpp"
|
||||
#include "PlayHistoryView.hpp"
|
||||
#include "PlayQueueView.hpp"
|
||||
@@ -66,6 +67,39 @@ LmsApplication::instance()
|
||||
return reinterpret_cast<LmsApplication*>(Wt::WApplication::instance());
|
||||
}
|
||||
|
||||
Wt::Dbo::ptr<Database::User>
|
||||
LmsApplication::getUser() const
|
||||
{
|
||||
if (!_userId)
|
||||
return {};
|
||||
|
||||
return Database::User::getById(*_dbSession, *_userId);
|
||||
}
|
||||
|
||||
bool
|
||||
LmsApplication::isUserAdmin() const
|
||||
{
|
||||
auto transaction {_dbSession->createSharedTransaction()};
|
||||
|
||||
return getUser()->isAdmin();
|
||||
}
|
||||
|
||||
bool
|
||||
LmsApplication::isUserDemo() const
|
||||
{
|
||||
auto transaction {_dbSession->createSharedTransaction()};
|
||||
|
||||
return getUser()->isDemo();
|
||||
}
|
||||
|
||||
std::string
|
||||
LmsApplication::getUserLoginName() const
|
||||
{
|
||||
auto transaction {_dbSession->createSharedTransaction()};
|
||||
|
||||
return getUser()->getLoginName();
|
||||
}
|
||||
|
||||
LmsApplication::LmsApplication(const Wt::WEnvironment& env,
|
||||
std::unique_ptr<Database::Session> dbSession,
|
||||
LmsApplicationGroupContainer& appGroups)
|
||||
@@ -131,25 +165,35 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
|
||||
if (firstConnection)
|
||||
{
|
||||
root()->addWidget(std::make_unique<InitWizardView>());
|
||||
return;
|
||||
}
|
||||
|
||||
auto userId {processAuthToken(env)};
|
||||
if (userId)
|
||||
{
|
||||
handleUserLoggedIn(*userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
LmsApp->getDbSession().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent);
|
||||
_auth = root()->addNew<Auth>();
|
||||
Auth* auth {root()->addNew<Auth>()};
|
||||
auth->userLoggedIn.connect(this, &LmsApplication::handleUserLoggedIn);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LmsApplication::finalize()
|
||||
{
|
||||
LmsApplicationInfo info = LmsApplicationInfo::fromEnvironment(environment());
|
||||
|
||||
getApplicationGroup().postOthers([info]
|
||||
if (_userId)
|
||||
{
|
||||
LmsApp->getEvents().appClosed(info);
|
||||
});
|
||||
LmsApplicationInfo info = LmsApplicationInfo::fromEnvironment(environment());
|
||||
|
||||
getApplicationGroup().leave();
|
||||
getApplicationGroup().postOthers([info]
|
||||
{
|
||||
LmsApp->getEvents().appClosed(info);
|
||||
});
|
||||
|
||||
getApplicationGroup().leave();
|
||||
}
|
||||
|
||||
preQuit().emit();
|
||||
}
|
||||
@@ -280,28 +324,37 @@ handlePathChange(Wt::WStackedWidget* stack, bool isAdmin)
|
||||
LmsApplicationGroup&
|
||||
LmsApplication::getApplicationGroup()
|
||||
{
|
||||
return _appGroups.get(_userIdentity);
|
||||
return _appGroups.get(*_userId);
|
||||
}
|
||||
|
||||
void
|
||||
LmsApplication::handleAuthEvent()
|
||||
LmsApplication::handleUserLoggedOut()
|
||||
{
|
||||
if (!getDbSession().getLogin().loggedIn())
|
||||
{
|
||||
LMS_LOG(UI, INFO) << "User '" << _userIdentity << " 'logged out, session = " << sessionId();
|
||||
LMS_LOG(UI, INFO) << "User '" << getUserLoginName() << " 'logged out";
|
||||
|
||||
goHomeAndQuit();
|
||||
return;
|
||||
{
|
||||
auto transaction {_dbSession->createUniqueTransaction()};
|
||||
getUser().modify()->clearAuthTokens();
|
||||
}
|
||||
|
||||
setConfirmCloseMessage("");
|
||||
goHomeAndQuit();
|
||||
}
|
||||
|
||||
void
|
||||
LmsApplication::handleUserLoggedIn(Database::IdType userId)
|
||||
{
|
||||
_userId = userId;
|
||||
|
||||
root()->clear();
|
||||
|
||||
try
|
||||
{
|
||||
// post([this]
|
||||
// {
|
||||
_userIdentity = getAuthUser().identity(Wt::Auth::Identity::LoginName);
|
||||
const LmsApplicationInfo info {LmsApplicationInfo::fromEnvironment(environment())};
|
||||
|
||||
LMS_LOG(UI, INFO) << "User '" << _userIdentity << "' logged in from '" << environment().clientAddress() << "', user agent = " << environment().userAgent() << ", session = " << sessionId();
|
||||
LMS_LOG(UI, INFO) << "User '" << getUserLoginName() << "' logged in from '" << environment().clientAddress() << "', user agent = " << environment().userAgent();
|
||||
getApplicationGroup().join(info);
|
||||
|
||||
getApplicationGroup().postOthers([info]
|
||||
@@ -310,7 +363,8 @@ LmsApplication::handleAuthEvent()
|
||||
});
|
||||
|
||||
createHome();
|
||||
triggerUpdate();
|
||||
|
||||
// triggerUpdate();
|
||||
// });
|
||||
}
|
||||
catch (std::exception& e)
|
||||
@@ -323,12 +377,6 @@ LmsApplication::handleAuthEvent()
|
||||
void
|
||||
LmsApplication::createHome()
|
||||
{
|
||||
|
||||
{
|
||||
auto transaction {_dbSession->createSharedTransaction()};
|
||||
_isAdmin = LmsApp->getUser()->isAdmin();
|
||||
}
|
||||
|
||||
_imageResource = std::make_shared<ImageResource>();
|
||||
_audioResource = std::make_shared<AudioResource>();
|
||||
|
||||
@@ -337,8 +385,8 @@ LmsApplication::createHome()
|
||||
Wt::WTemplate* main {root()->addWidget(std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.template")))};
|
||||
|
||||
// Navbar
|
||||
Wt::WNavigationBar* navbar = main->bindNew<Wt::WNavigationBar>("navbar-top");
|
||||
navbar->setTitle("LMS", Wt::WLink(Wt::LinkType::InternalPath, "/artists"));
|
||||
Wt::WNavigationBar* navbar {main->bindNew<Wt::WNavigationBar>("navbar-top")};
|
||||
navbar->setTitle("LMS", Wt::WLink {Wt::LinkType::InternalPath, "/artists"});
|
||||
navbar->setResponsive(true);
|
||||
|
||||
Wt::WMenu* menu {navbar->addMenu(std::make_unique<Wt::WMenu>())};
|
||||
@@ -370,7 +418,7 @@ LmsApplication::createHome()
|
||||
|
||||
Wt::WMenu* rightMenu = navbar->addMenu(std::make_unique<Wt::WMenu>(), Wt::AlignmentFlag::Right);
|
||||
std::size_t itemCounter = 0;
|
||||
if (_isAdmin)
|
||||
if (isUserAdmin())
|
||||
{
|
||||
auto menuItem = rightMenu->insertItem(itemCounter++, Wt::WString::tr("Lms.administration"));
|
||||
menuItem->setSelectable(false);
|
||||
@@ -395,11 +443,7 @@ LmsApplication::createHome()
|
||||
{
|
||||
auto menuItem = rightMenu->insertItem(itemCounter++, Wt::WString::tr("Lms.logout"));
|
||||
menuItem->setSelectable(true);
|
||||
menuItem->triggered().connect(std::bind([=]
|
||||
{
|
||||
setConfirmCloseMessage("");
|
||||
_auth->logout();
|
||||
}));
|
||||
menuItem->triggered().connect(this, &LmsApplication::handleUserLoggedOut);
|
||||
}
|
||||
|
||||
// Contents
|
||||
@@ -412,7 +456,7 @@ LmsApplication::createHome()
|
||||
mainStack->addNew<SettingsView>();
|
||||
|
||||
// Admin stuff
|
||||
if (_isAdmin)
|
||||
if (isUserAdmin())
|
||||
{
|
||||
mainStack->addNew<DatabaseSettingsView>();
|
||||
mainStack->addNew<UsersView>();
|
||||
@@ -493,7 +537,7 @@ LmsApplication::createHome()
|
||||
|
||||
_events.dbScanned.connect([=] (Scanner::MediaScanner::Stats stats)
|
||||
{
|
||||
if (_isAdmin)
|
||||
if (isUserAdmin())
|
||||
{
|
||||
notifyMsg(MsgType::Info, Wt::WString::tr("Lms.Admin.Database.scan-complete")
|
||||
.arg(static_cast<unsigned>(stats.nbFiles()))
|
||||
@@ -509,7 +553,7 @@ LmsApplication::createHome()
|
||||
_events.appOpen.connect([=] (LmsApplicationInfo info)
|
||||
{
|
||||
// Only one active session by user
|
||||
if (!LmsApp->getUser()->isDemo())
|
||||
if (!LmsApp->isUserDemo())
|
||||
{
|
||||
setConfirmCloseMessage("");
|
||||
quit(Wt::WString::tr("Lms.quit-other-session"));
|
||||
@@ -518,10 +562,10 @@ LmsApplication::createHome()
|
||||
|
||||
internalPathChanged().connect(std::bind([=]
|
||||
{
|
||||
handlePathChange(mainStack, _isAdmin);
|
||||
handlePathChange(mainStack, isUserAdmin());
|
||||
}));
|
||||
|
||||
handlePathChange(mainStack, _isAdmin);
|
||||
handlePathChange(mainStack, isUserAdmin());
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "scanner/MediaScanner.hpp"
|
||||
|
||||
#include "LmsApplicationGroup.hpp"
|
||||
#include "Auth.hpp"
|
||||
|
||||
namespace Database {
|
||||
class Artist;
|
||||
@@ -41,6 +40,7 @@ namespace UserInterface {
|
||||
|
||||
class AudioResource;
|
||||
class ImageResource;
|
||||
class Auth;
|
||||
|
||||
// Events that can be listen from anywhere in the application
|
||||
struct Events
|
||||
@@ -83,9 +83,10 @@ class LmsApplication : public Wt::WApplication
|
||||
std::shared_ptr<AudioResource> getAudioResource() { return _audioResource; }
|
||||
Database::Session& getDbSession() { return *_dbSession.get();}
|
||||
|
||||
const Wt::Auth::User& getAuthUser() { return getDbSession().getLogin().user(); }
|
||||
Wt::Dbo::ptr<Database::User> getUser() { return getDbSession().getLoggedUser(); }
|
||||
Wt::WString getUserIdentity() { return _userIdentity; }
|
||||
Wt::Dbo::ptr<Database::User> getUser() const;
|
||||
bool isUserAdmin() const; // user must be logged in prior this call
|
||||
bool isUserDemo() const; // user must be logged in prior this call
|
||||
std::string getUserLoginName() const; // user must be logged in prior this call
|
||||
|
||||
Events& getEvents() { return _events; }
|
||||
|
||||
@@ -109,8 +110,10 @@ class LmsApplication : public Wt::WApplication
|
||||
|
||||
LmsApplicationGroup& getApplicationGroup();
|
||||
|
||||
// Events
|
||||
void handleAuthEvent();
|
||||
// Signal slots
|
||||
void handleUserLoggedOut();
|
||||
void handleUserLoggedIn(Database::IdType userId);
|
||||
|
||||
void notify(const Wt::WEvent& event) override;
|
||||
void finalize() override;
|
||||
|
||||
@@ -120,11 +123,9 @@ class LmsApplication : public Wt::WApplication
|
||||
std::unique_ptr<Database::Session> _dbSession;
|
||||
LmsApplicationGroupContainer& _appGroups;
|
||||
Events _events;
|
||||
Wt::WString _userIdentity;
|
||||
Auth* _auth {};
|
||||
boost::optional<Database::IdType> _userId {};
|
||||
std::shared_ptr<ImageResource> _imageResource;
|
||||
std::shared_ptr<AudioResource> _audioResource;
|
||||
bool _isAdmin {};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ LmsApplicationInfo::fromEnvironment(const Wt::WEnvironment& env)
|
||||
void
|
||||
LmsApplicationGroup::join(LmsApplicationInfo info)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
std::unique_lock<std::mutex> lock {_mutex};
|
||||
|
||||
_apps.emplace(wApp->sessionId(), std::move(info));
|
||||
}
|
||||
@@ -45,7 +45,7 @@ LmsApplicationGroup::join(LmsApplicationInfo info)
|
||||
void
|
||||
LmsApplicationGroup::leave()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
std::unique_lock<std::mutex> lock {_mutex};
|
||||
|
||||
_apps.erase(wApp->sessionId());
|
||||
}
|
||||
@@ -55,7 +55,7 @@ LmsApplicationGroup::getOtherSessionIds() const
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
std::unique_lock<std::mutex> lock {_mutex};
|
||||
for (auto const& app : _apps)
|
||||
{
|
||||
if (app.first != wApp->sessionId())
|
||||
@@ -79,11 +79,11 @@ LmsApplicationGroup::postOthers(std::function<void()> func) const
|
||||
}
|
||||
|
||||
LmsApplicationGroup&
|
||||
LmsApplicationGroupContainer::get(Wt::WString identity)
|
||||
LmsApplicationGroupContainer::get(Database::IdType userId)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
std::unique_lock<std::mutex> lock {_mutex};
|
||||
|
||||
return _apps[identity];
|
||||
return _apps[userId];
|
||||
}
|
||||
|
||||
} // UserInterface
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <Wt/WSignal.h>
|
||||
#include <Wt/WEnvironment.h>
|
||||
|
||||
#include "database/Types.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
|
||||
@@ -56,10 +58,10 @@ class LmsApplicationGroup
|
||||
class LmsApplicationGroupContainer
|
||||
{
|
||||
public:
|
||||
LmsApplicationGroup& get(Wt::WString identity);
|
||||
LmsApplicationGroup& get(Database::IdType userId);
|
||||
|
||||
private:
|
||||
std::map<Wt::WString, LmsApplicationGroup> _apps;
|
||||
std::map<Database::IdType /* userId */, LmsApplicationGroup> _apps;
|
||||
std::mutex _mutex;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackList.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackList.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "main/Service.hpp"
|
||||
#include "similarity/SimilaritySearcher.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
@@ -131,7 +132,14 @@ PlayQueue::PlayQueue()
|
||||
{
|
||||
LmsApp->post([=]
|
||||
{
|
||||
load(LmsApp->getUser()->getCurPlayingTrackPos(), false);
|
||||
std::size_t trackPos {};
|
||||
|
||||
{
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
trackPos = LmsApp->getUser()->getCurPlayingTrackPos();
|
||||
}
|
||||
|
||||
load(trackPos, false);
|
||||
});
|
||||
trackList = LmsApp->getUser()->getQueuedTrackList(LmsApp->getDbSession());
|
||||
}
|
||||
|
||||
+16
-9
@@ -24,12 +24,15 @@
|
||||
#include <Wt/WCheckBox.h>
|
||||
#include <Wt/WComboBox.h>
|
||||
#include <Wt/WLineEdit.h>
|
||||
#include <Wt/WTemplateFormView.h>
|
||||
|
||||
#include <Wt/WFormModel.h>
|
||||
|
||||
#include "common/Validators.hpp"
|
||||
#include "common/ValueStringModel.hpp"
|
||||
|
||||
#include "auth/AuthService.hpp"
|
||||
#include "main/Service.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
@@ -69,20 +72,27 @@ class SettingsModel : public Wt::WFormModel
|
||||
|
||||
void saveData()
|
||||
{
|
||||
Database::User::PasswordHash passwordHash;
|
||||
|
||||
if (!valueText(PasswordField).empty())
|
||||
passwordHash = getService<::Auth::AuthService>()->hashPassword(valueText(PasswordField).toUTF8());
|
||||
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
LmsApp->getUser().modify()->setAudioTranscodeEnable(Wt::asNumber(value(TranscodeEnableField)));
|
||||
Database::User::pointer user {LmsApp->getUser()};
|
||||
|
||||
user.modify()->setAudioTranscodeEnable(Wt::asNumber(value(TranscodeEnableField)));
|
||||
|
||||
auto transcodeBitrateRow {_transcodeBitrateModel->getRowFromString(valueText(TranscodeBitrateField))};
|
||||
if (transcodeBitrateRow)
|
||||
LmsApp->getUser().modify()->setAudioTranscodeBitrate(_transcodeBitrateModel->getValue(*transcodeBitrateRow));
|
||||
user.modify()->setAudioTranscodeBitrate(_transcodeBitrateModel->getValue(*transcodeBitrateRow));
|
||||
|
||||
auto transcodeFormatRow {_transcodeFormatModel->getRowFromString(valueText(TranscodeFormatField))};
|
||||
if (transcodeFormatRow)
|
||||
LmsApp->getUser().modify()->setAudioTranscodeFormat(_transcodeFormatModel->getValue(*transcodeFormatRow));
|
||||
user.modify()->setAudioTranscodeFormat(_transcodeFormatModel->getValue(*transcodeFormatRow));
|
||||
|
||||
if (!valueText(PasswordField).empty())
|
||||
Session::getPasswordService().updatePassword(LmsApp->getAuthUser(), valueText(PasswordField));
|
||||
user.modify()->setPasswordHash(passwordHash);
|
||||
}
|
||||
|
||||
void loadData()
|
||||
@@ -115,11 +125,8 @@ class SettingsModel : public Wt::WFormModel
|
||||
{
|
||||
if (!valueText(PasswordField).empty())
|
||||
{
|
||||
// Evaluate the strength of the password
|
||||
auto res = Session::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), LmsApp->getUserIdentity(), "");
|
||||
|
||||
if (!res.isValid())
|
||||
error = res.message();
|
||||
if (!getService<::Auth::AuthService>()->evaluatePasswordStrength(LmsApp->getUserLoginName(), valueText(PasswordField).toUTF8()))
|
||||
error = Wt::WString::tr("Lms.password-too-weak");
|
||||
}
|
||||
else
|
||||
return Wt::WFormModel::validateField(field);
|
||||
|
||||
@@ -22,8 +22,9 @@
|
||||
#include <Wt/WFormModel.h>
|
||||
#include <Wt/WLineEdit.h>
|
||||
#include <Wt/WPushButton.h>
|
||||
#include <Wt/Auth/Identity.h>
|
||||
|
||||
#include "auth/AuthService.hpp"
|
||||
#include "main/Service.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
|
||||
@@ -54,6 +55,8 @@ class InitWizardModel : public Wt::WFormModel
|
||||
|
||||
void saveData()
|
||||
{
|
||||
const Database::User::PasswordHash passwordHash {getService<::Auth::AuthService>()->hashPassword(valueText(PasswordField).toUTF8())};
|
||||
|
||||
auto transaction(LmsApp->getDbSession().createUniqueTransaction());
|
||||
|
||||
// Check if a user already exist
|
||||
@@ -61,7 +64,7 @@ class InitWizardModel : public Wt::WFormModel
|
||||
if (!Database::User::getAll(LmsApp->getDbSession()).empty())
|
||||
throw LmsException("Admin user already created");
|
||||
|
||||
Database::User::pointer user {LmsApp->getDbSession().createUser(valueText(AdminLoginField).toUTF8(), valueText(PasswordField).toUTF8())};
|
||||
Database::User::pointer user {Database::User::create(LmsApp->getDbSession(), valueText(AdminLoginField).toUTF8(), passwordHash)};
|
||||
user.modify()->setType(Database::User::Type::ADMIN);
|
||||
}
|
||||
|
||||
@@ -74,11 +77,8 @@ class InitWizardModel : public Wt::WFormModel
|
||||
if (!valueText(PasswordField).empty())
|
||||
{
|
||||
// Evaluate the strength of the password
|
||||
auto res = Database::Session::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField),
|
||||
valueText(AdminLoginField), "");
|
||||
|
||||
if (!res.isValid())
|
||||
error = res.message();
|
||||
if (!getService<::Auth::AuthService>()->evaluatePasswordStrength(valueText(AdminLoginField).toUTF8(), valueText(PasswordField).toUTF8()))
|
||||
error = Wt::WString::tr("Lms.password-too-weak");
|
||||
}
|
||||
else
|
||||
return Wt::WFormModel::validateField(field);
|
||||
@@ -126,7 +126,7 @@ InitWizardView::InitWizardView()
|
||||
setFormWidget(InitWizardModel::PasswordConfirmField, std::move(passwordConfirmEdit));
|
||||
|
||||
Wt::WPushButton* saveButton = bindNew<Wt::WPushButton>("create-btn", Wt::WString::tr("Lms.create"));
|
||||
saveButton->clicked().connect(std::bind([=]
|
||||
saveButton->clicked().connect([=]
|
||||
{
|
||||
updateModel(model.get());
|
||||
|
||||
@@ -138,7 +138,7 @@ InitWizardView::InitWizardView()
|
||||
}
|
||||
|
||||
updateView(model.get());
|
||||
}));
|
||||
});
|
||||
|
||||
updateView(model.get());
|
||||
}
|
||||
|
||||
+26
-18
@@ -28,7 +28,9 @@
|
||||
|
||||
#include <Wt/WFormModel.h>
|
||||
|
||||
#include "auth/AuthService.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "main/Service.hpp"
|
||||
#include "utils/Config.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
@@ -78,6 +80,10 @@ class UserModel : public Wt::WFormModel
|
||||
|
||||
void saveData()
|
||||
{
|
||||
boost::optional<Database::User::PasswordHash> passwordHash;
|
||||
if (!valueText(PasswordField).empty())
|
||||
passwordHash = getService<::Auth::AuthService>()->hashPassword(valueText(PasswordField).toUTF8());
|
||||
|
||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||
|
||||
if (_userId)
|
||||
@@ -86,8 +92,11 @@ class UserModel : public Wt::WFormModel
|
||||
Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
|
||||
|
||||
// Account
|
||||
if (!valueText(PasswordField).empty())
|
||||
LmsApp->getDbSession().updateUserPassword(user, valueText(PasswordField).toUTF8());
|
||||
if (passwordHash)
|
||||
{
|
||||
user.modify()->setPasswordHash(*passwordHash);
|
||||
user.modify()->clearAuthTokens();
|
||||
}
|
||||
|
||||
auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))};
|
||||
if (transcodeBitrateLimitRow)
|
||||
@@ -96,7 +105,7 @@ class UserModel : public Wt::WFormModel
|
||||
else
|
||||
{
|
||||
// Create user
|
||||
Database::User::pointer user = LmsApp->getDbSession().createUser(valueText(LoginField).toUTF8(), valueText(PasswordField).toUTF8());
|
||||
Database::User::pointer user {Database::User::create(LmsApp->getDbSession(), valueText(LoginField).toUTF8(), *passwordHash)};
|
||||
|
||||
auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))};
|
||||
if (transcodeBitrateLimitRow )
|
||||
@@ -125,17 +134,17 @@ class UserModel : public Wt::WFormModel
|
||||
setValue(AudioTranscodeBitrateLimitField, _bitrateModel->getString(*transcodeBitrateLimitRow));
|
||||
}
|
||||
|
||||
Wt::WString getLoginName() const
|
||||
std::string getLoginName() const
|
||||
{
|
||||
if (_userId)
|
||||
{
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
|
||||
return LmsApp->getDbSession().getUserLoginName(user);
|
||||
return user->getLoginName();
|
||||
}
|
||||
else
|
||||
return valueText(LoginField);
|
||||
return valueText(LoginField).toUTF8();
|
||||
}
|
||||
|
||||
bool validateField(Field field)
|
||||
@@ -144,7 +153,9 @@ class UserModel : public Wt::WFormModel
|
||||
|
||||
if (field == LoginField)
|
||||
{
|
||||
const Database::User::pointer user {LmsApp->getDbSession().getUser(valueText(LoginField).toUTF8())};
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
const Database::User::pointer user {Database::User::getByLoginName(LmsApp->getDbSession(), valueText(LoginField).toUTF8())};
|
||||
if (user)
|
||||
error = Wt::WString::tr("Lms.Admin.User.user-already-exists");
|
||||
}
|
||||
@@ -154,17 +165,15 @@ class UserModel : public Wt::WFormModel
|
||||
{
|
||||
if (Wt::asNumber(value(DemoField)))
|
||||
{
|
||||
//Demo account: password must be the same as the login name
|
||||
// Demo account: password must be the same as the login name
|
||||
if (valueText(PasswordField) != getLoginName())
|
||||
error = Wt::WString::tr("Lms.Admin.User.demo-password-invalid");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Evaluate the strength of the password for non demo accounts
|
||||
auto res = Database::Session::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), getLoginName(), "");
|
||||
|
||||
if (!res.isValid())
|
||||
error = res.message();
|
||||
if (!getService<::Auth::AuthService>()->evaluatePasswordStrength(getLoginName(), valueText(PasswordField).toUTF8()))
|
||||
error = Wt::WString::tr("Lms.password-too-weak");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,13 +240,12 @@ UserView::refreshView()
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *userId)};
|
||||
const std::string loginName {LmsApp->getDbSession().getUserLoginName(user)};
|
||||
t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-edit").arg(loginName), Wt::TextFormat::Plain);
|
||||
t->setCondition("if-has-last-login-attempt", true);
|
||||
t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-edit").arg(user->getLoginName()), Wt::TextFormat::Plain);
|
||||
t->setCondition("if-has-last-login", true);
|
||||
|
||||
Wt::WLineEdit *lastLoginAttempt {t->bindNew<Wt::WLineEdit>("last-login-attempt")};
|
||||
lastLoginAttempt->setText(LmsApp->getDbSession().getUserLastLoginAttempt(user).toString());
|
||||
lastLoginAttempt->setEnabled(false);
|
||||
Wt::WLineEdit *lastLogin {t->bindNew<Wt::WLineEdit>("last-login")};
|
||||
lastLogin->setText(user->getLastLogin().toString());
|
||||
lastLogin->setEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -68,7 +68,7 @@ UsersView::refreshView()
|
||||
|
||||
Wt::WTemplate* entry {_container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.entry"))};
|
||||
|
||||
entry->bindString("name", LmsApp->getDbSession().getUserLoginName(user), Wt::TextFormat::Plain);
|
||||
entry->bindString("name", user->getLoginName(), Wt::TextFormat::Plain);
|
||||
|
||||
// Create tag
|
||||
if (user->isAdmin() || user->isDemo())
|
||||
@@ -104,7 +104,7 @@ UsersView::refreshView()
|
||||
|
||||
Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), userId)};
|
||||
if (user)
|
||||
LmsApp->getDbSession().removeUser(user);
|
||||
user.remove();
|
||||
|
||||
_container->removeWidget(entry);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/TrackList.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/Utils.hpp"
|
||||
#include "ArtistLink.hpp"
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
+31
-18
@@ -99,17 +99,24 @@ Filters::showDialog()
|
||||
const std::string type {typeCombo->valueText().toUTF8()};
|
||||
const std::string value {valueCombo->valueText().toUTF8()};
|
||||
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
// TODO use a model to store the cluster.id() values
|
||||
Database::IdType clusterId {};
|
||||
|
||||
Database::ClusterType::pointer clusterType {Database::ClusterType::getByName(LmsApp->getDbSession(), type)};
|
||||
if (!clusterType)
|
||||
return;
|
||||
{
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
Database::Cluster::pointer cluster {clusterType->getCluster(value)};
|
||||
if (!cluster)
|
||||
return;
|
||||
Database::ClusterType::pointer clusterType {Database::ClusterType::getByName(LmsApp->getDbSession(), type)};
|
||||
if (!clusterType)
|
||||
return;
|
||||
|
||||
add(cluster.id());
|
||||
Database::Cluster::pointer cluster {clusterType->getCluster(value)};
|
||||
if (!cluster)
|
||||
return;
|
||||
|
||||
clusterId = cluster.id();
|
||||
}
|
||||
|
||||
add(clusterId);
|
||||
});
|
||||
|
||||
dialog->show();
|
||||
@@ -118,23 +125,29 @@ Filters::showDialog()
|
||||
void
|
||||
Filters::add(Database::IdType clusterId)
|
||||
{
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
Database::Cluster::pointer cluster {Database::Cluster::getById(LmsApp->getDbSession(), clusterId)};
|
||||
if (!cluster)
|
||||
return;
|
||||
Wt::WTemplate* filter {};
|
||||
|
||||
auto res {_filterIds.insert(clusterId)};
|
||||
if (!res.second)
|
||||
return;
|
||||
{
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
auto filter {_filters->addWidget(LmsApp->createCluster(cluster, true))};
|
||||
filter->clicked().connect(std::bind([=]
|
||||
Database::Cluster::pointer cluster {Database::Cluster::getById(LmsApp->getDbSession(), clusterId)};
|
||||
if (!cluster)
|
||||
return;
|
||||
|
||||
auto res {_filterIds.insert(clusterId)};
|
||||
if (!res.second)
|
||||
return;
|
||||
|
||||
filter = _filters->addWidget(LmsApp->createCluster(cluster, true));
|
||||
}
|
||||
|
||||
filter->clicked().connect([=]
|
||||
{
|
||||
_filters->removeWidget(filter);
|
||||
_filterIds.erase(clusterId);
|
||||
_sigUpdated.emit();
|
||||
}));
|
||||
});
|
||||
|
||||
LmsApp->notifyMsg(MsgType::Info, Wt::WString::tr("Lms.Explore.filter-added"), std::chrono::seconds {2});
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "database/Release.hpp"
|
||||
#include "database/TrackList.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "resource/ImageResource.hpp"
|
||||
#include "ReleaseLink.hpp"
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackList.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "utils/Utils.hpp"
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "utils/Logger.hpp"
|
||||
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user