add OIDC
Basic Build (Arch) / Build (push) Waiting to run
clang-format Check / Formatting Check (push) Waiting to run

This commit is contained in:
2026-08-19 22:00:14 -05:00
parent bf2fa21e95
commit 06ca52c261
13 changed files with 201 additions and 83 deletions
+1
View File
@@ -108,6 +108,7 @@ You can define which authentication backend to be used thanks to the `authentica
* `internal` (default): _LMS_ uses an internal database to store users and their associated passwords (salted and hashed using [Bcrypt](https://en.wikipedia.org/wiki/Bcrypt)). Only the admin user can create, edit or remove other users. * `internal` (default): _LMS_ uses an internal database to store users and their associated passwords (salted and hashed using [Bcrypt](https://en.wikipedia.org/wiki/Bcrypt)). Only the admin user can create, edit or remove other users.
* `PAM`: the user/password authentication request is forwarded to PAM. A default example PAM configuration file is provided at [`conf/pam/lms`](conf/pam/lms), which demonstrates how to authenticate using system passwords. **Note:** this file is just a base example; depending on your PAM setup, you may need to adjust the service file or PAM rules to ensure proper privileges. * `PAM`: the user/password authentication request is forwarded to PAM. A default example PAM configuration file is provided at [`conf/pam/lms`](conf/pam/lms), which demonstrates how to authenticate using system passwords. **Note:** this file is just a base example; depending on your PAM setup, you may need to adjust the service file or PAM rules to ensure proper privileges.
* `http-headers`: _LMS_ uses a configurable HTTP header field, typically set by a reverse proxy to handle [SSO](https://en.wikipedia.org/wiki/Single_sign-on), to extract the login name. You can customize the field to be used using the `http-headers-login-field` option. * `http-headers`: _LMS_ uses a configurable HTTP header field, typically set by a reverse proxy to handle [SSO](https://en.wikipedia.org/wiki/Single_sign-on), to extract the login name. You can customize the field to be used using the `http-headers-login-field` option.
* `oidc`: _LMS_ authenticates users through an OpenID Connect provider using the authorization code flow. Configure the client and provider endpoints with the `oidc-*` options in `lms.conf`, set `public-url`, and register `<public-url>/oidc/callback` as the client redirect URI. Users are created automatically on first login; the first user is an administrator. By default the stable `sub` claim is used as the LMS login name; `email` (verified addresses only) and `name` are also supported through `oidc-login-name-claim`.
__Note__: the first created user is the admin user. __Note__: the first created user is the admin user.
### `internal` backend: reset admin password ### `internal` backend: reset admin password
Open the the database file located in `/var/lms/lms.db` using `sqlite3`: Open the the database file located in `/var/lms/lms.db` using `sqlite3`:
-49
View File
@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<messages>
<message id="Lms.Admin.InternetRadio.template">
<legend>${tr:Lms.Admin.InternetRadio.title}</legend>
<p class="text-body-secondary">${tr:Lms.Admin.InternetRadio.help}</p>
<div class="card mb-4"><div class="card-body">
<div class="row g-3 align-items-end">
<div class="col-12 col-lg-3"><label class="form-label">${tr:Lms.Admin.InternetRadio.name}</label>${name class="form-control"}</div>
<div class="col-12 col-lg-4"><label class="form-label">${tr:Lms.Admin.InternetRadio.stream-url}</label>${stream-url class="form-control"}</div>
<div class="col-12 col-lg-4"><label class="form-label">${tr:Lms.Admin.InternetRadio.homepage-url}</label>${homepage-url class="form-control"}</div>
<div class="col-12 col-lg-1 d-grid">${add-btn class="btn btn-primary"}</div>
</div>
</div></div>
${stations class="d-grid gap-1 mb-3 Lms-row-container"}
</message>
<message id="Lms.Admin.InternetRadio.template.entry">
<div class="d-flex align-items-center Lms-bg-dark-hover">
<div class="row align-items-center flex-fill overflow-hidden">
<div class="col-12 col-md-3"><div class="p-2 fw-semibold text-truncate">${name}</div></div>
<div class="col-12 col-md-5"><div class="p-2 text-truncate">${stream-url}</div></div>
<div class="col-12 col-md-4"><div class="p-2 text-body-secondary text-truncate">${homepage-url}</div></div>
</div>
<div class="p-2 d-flex gap-1">${edit-btn class="btn btn-sm btn-outline-secondary border-0"}${delete-btn class="btn btn-sm btn-outline-danger border-0"}</div>
</div>
</message>
<message id="Lms.Admin.InternetRadio.template.edit">
<div class="modal fade" tabindex="-1"><div class="modal-dialog"><div class="modal-content">
<div class="modal-header"><h5 class="modal-title">${tr:Lms.Admin.InternetRadio.edit}</h5></div>
<div class="modal-body">
<div class="mb-3"><label class="form-label">${tr:Lms.Admin.InternetRadio.name}</label>${name class="form-control"}</div>
<div class="mb-3"><label class="form-label">${tr:Lms.Admin.InternetRadio.stream-url}</label>${stream-url class="form-control"}</div>
<div><label class="form-label">${tr:Lms.Admin.InternetRadio.homepage-url}</label>${homepage-url class="form-control"}</div>
</div>
<div class="modal-footer">${save-btn class="btn btn-primary"}${cancel-btn class="btn btn-secondary"}</div>
</div></div></div>
</message>
<message id="Lms.Admin.InternetRadio.template.delete">
<div class="modal fade" tabindex="-1"><div class="modal-dialog"><div class="modal-content">
<div class="modal-header"><h5 class="modal-title">${tr:Lms.Admin.InternetRadio.delete-confirm}</h5></div>
<div class="modal-body">${tr:Lms.Admin.InternetRadio.delete-help}</div>
<div class="modal-footer">${delete-btn class="btn btn-danger"}${cancel-btn class="btn btn-secondary"}</div>
</div></div></div>
</message>
</messages>
-2
View File
@@ -35,7 +35,6 @@
${podcasts class="nav-link"} ${podcasts class="nav-link"}
</li> </li>
<li class="nav-item"> <li class="nav-item">
${radio class="nav-link"}
</li> </li>
</ul> </ul>
${filters class="d-flex align-items-center me-auto mb-2 mb-md-0"} ${filters class="d-flex align-items-center me-auto mb-2 mb-md-0"}
@@ -49,7 +48,6 @@
<li>${media-libraries class="dropdown-item"}</li> <li>${media-libraries class="dropdown-item"}</li>
<li>${admin-podcasts class="dropdown-item"}</li> <li>${admin-podcasts class="dropdown-item"}</li>
<li>${admin-internet-radio class="dropdown-item"}</li> <li>${admin-internet-radio class="dropdown-item"}</li>
<li>${scan-settings class="dropdown-item"}</li>
<li>${scanner class="dropdown-item"}</li> <li>${scanner class="dropdown-item"}</li>
<li>${users class="dropdown-item"}</li> <li>${users class="dropdown-item"}</li>
<li>${about class="dropdown-item"}</li> <li>${about class="dropdown-item"}</li>
+4
View File
@@ -213,6 +213,10 @@
<message id="Lms.Auth.login">Log in</message> <message id="Lms.Auth.login">Log in</message>
<message id="Lms.Auth.remember-me">Remember me</message> <message id="Lms.Auth.remember-me">Remember me</message>
<message id="Lms.Auth.welcome">Welcome!</message> <message id="Lms.Auth.welcome">Welcome!</message>
<message id="Lms.OIDCAuth.login">Log in with {1}</message>
<message id="Lms.OIDCAuth.failed">OpenID Connect authentication failed.</message>
<message id="Lms.OIDCAuth.unverified-email">The identity provider did not verify your email address.</message>
<message id="Lms.OIDCAuth.missing-claim">The identity provider did not return the configured login name.</message>
<!--Explore--> <!--Explore-->
<message id="Lms.Explore.add-filter">Add filter</message> <message id="Lms.Explore.add-filter">Add filter</message>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<messages>
<message id="Lms.OIDCAuth.template">
<div class="container">
<div class="row mt-3 justify-content-md-center">
<div class="col-lg-6">
<h2>${tr:Lms.Auth.welcome}</h2>
<div class="mt-3">${login-btn class="btn btn-primary"}</div>
<div class="text-danger mt-3">${error}</div>
</div>
</div>
</div>
</message>
</messages>
-1
View File
@@ -14,7 +14,6 @@
<div class="btn-group me-2"> <div class="btn-group me-2">
${repeat-btn class="btn-check"} ${repeat-btn class="btn-check"}
<label class="btn btn-outline-info" title="${tr:Lms.PlayQueue.repeat}" for="${id:repeat-btn}">${tr:Lms.PlayQueue.template.repeat-btn}</label> <label class="btn btn-outline-info" title="${tr:Lms.PlayQueue.repeat}" for="${id:repeat-btn}">${tr:Lms.PlayQueue.template.repeat-btn}</label>
${radio-btn class="btn-check"}
<label class="btn btn-outline-info" title="${tr:Lms.PlayQueue.radio-mode}" for="${id:radio-btn}">${tr:Lms.PlayQueue.template.radio-btn}</label> <label class="btn btn-outline-info" title="${tr:Lms.PlayQueue.radio-mode}" for="${id:radio-btn}">${tr:Lms.PlayQueue.template.radio-btn}</label>
</div> </div>
<div class="d-inline-block me-2" title="${tr:Lms.save}"> <div class="d-inline-block me-2" title="${tr:Lms.save}">
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<messages>
<message id="Lms.Radio.template">
<legend class="mb-3">${tr:Lms.Radio.radio}</legend>
<p class="text-body-secondary">${tr:Lms.Radio.description}</p>
${stations class="d-grid gap-2"}
</message>
<message id="Lms.Radio.template.station">
<div class="card w-100">
<div class="card-body d-flex align-items-center gap-3">
<div class="flex-fill min-width-0">
<h5 class="card-title text-truncate mb-1">${name}</h5>
${homepage class="small"}
</div>
${play-btn class="btn btn-primary flex-shrink-0"}
</div>
</div>
</message>
</messages>
+13 -1
View File
@@ -74,12 +74,24 @@ lastfm-submit-period-hours = 1;
acousticbrainz-api-base-url = "https://acousticbrainz.org"; acousticbrainz-api-base-url = "https://acousticbrainz.org";
# Authentication # Authentication
# Available backends: "internal", "PAM", "http-headers" # Available backends: "internal", "PAM", "http-headers", "oidc"
authentication-backend = "internal"; authentication-backend = "internal";
# The number of bcrypt rounds to be used when backend is set to "internal". The higher the more secure # The number of bcrypt rounds to be used when backend is set to "internal". The higher the more secure
internal-password-bcrypt-round = 12; internal-password-bcrypt-round = 12;
# The header to be used to read the authentication user when backend is set to "http-headers" # The header to be used to read the authentication user when backend is set to "http-headers"
http-headers-login-field = "X-Forwarded-User"; http-headers-login-field = "X-Forwarded-User";
# OpenID Connect settings (used when authentication-backend is "oidc").
# public-url must also be set, and <public-url>/oidc/callback must be registered
# as an allowed redirect URI at the identity provider.
oidc-client-id = "";
oidc-client-secret = "";
oidc-authorization-endpoint = "";
oidc-token-endpoint = "";
oidc-userinfo-endpoint = "";
oidc-scope = "openid profile email";
oidc-provider-name = "OpenID Connect";
# Login name source: "sub" (stable provider subject), "email" (must be verified), or "name".
oidc-login-name-claim = "sub";
# Max entries in the login throttler (1 entry per IP address. For IPv6, the whole /64 block is used) # Max entries in the login throttler (1 entry per IP address. For IPv6, the whole /64 block is used)
login-throttler-max-entries = 10000; login-throttler-max-entries = 10000;
+35 -2
View File
@@ -21,6 +21,8 @@
#include <thread> #include <thread>
#include <Wt/WApplication.h> #include <Wt/WApplication.h>
#include <Wt/Auth/AuthService.h>
#include <Wt/Auth/OidcService.h>
#include <Wt/WLogSink.h> #include <Wt/WLogSink.h>
#include <Wt/WServer.h> #include <Wt/WServer.h>
#include <boost/asio/io_context.hpp> #include <boost/asio/io_context.hpp>
@@ -83,6 +85,8 @@ namespace lms
return ui::AuthenticationBackend::PAM; return ui::AuthenticationBackend::PAM;
if (backend == "http-headers") if (backend == "http-headers")
return ui::AuthenticationBackend::Env; return ui::AuthenticationBackend::Env;
if (backend == "oidc")
return ui::AuthenticationBackend::OIDC;
throw core::LmsException{ "Invalid config value for 'authentication-backend'" }; throw core::LmsException{ "Invalid config value for 'authentication-backend'" };
} }
@@ -471,6 +475,9 @@ namespace lms
core::Service<auth::IAuthTokenService> authTokenService{ auth::createAuthTokenService(*database, config->getULong("login-throttler-max-entriees", 10'000)) }; core::Service<auth::IAuthTokenService> authTokenService{ auth::createAuthTokenService(*database, config->getULong("login-throttler-max-entriees", 10'000)) };
core::Service<auth::IPasswordService> authPasswordService; core::Service<auth::IPasswordService> authPasswordService;
core::Service<auth::IEnvService> authEnvService; core::Service<auth::IEnvService> authEnvService;
Wt::Auth::AuthService oidcBaseAuthService;
std::unique_ptr<Wt::Auth::OidcService> oidcService;
std::string oidcLoginNameClaim{ config->getString("oidc-login-name-claim", "sub") };
authTokenService->registerDomain("ui", auth::IAuthTokenService::DomainParameters{ authTokenService->registerDomain("ui", auth::IAuthTokenService::DomainParameters{
.tokenMaxUseCount = 1, .tokenMaxUseCount = 1,
@@ -493,6 +500,32 @@ namespace lms
case ui::AuthenticationBackend::Env: case ui::AuthenticationBackend::Env:
authEnvService.assign(auth::createEnvService("http-headers", *database)); authEnvService.assign(auth::createEnvService("http-headers", *database));
break; break;
case ui::AuthenticationBackend::OIDC:
{
if (oidcLoginNameClaim != "sub" && oidcLoginNameClaim != "email" && oidcLoginNameClaim != "name")
throw core::LmsException{ "Invalid config value for 'oidc-login-name-claim'" };
std::string publicUrl{ config->getString("public-url", "") };
if (publicUrl.empty())
throw core::LmsException{ "'public-url' must be configured when using the OIDC authentication backend" };
while (publicUrl.size() > 1 && publicUrl.back() == '/')
publicUrl.pop_back();
ui::OIDCConfig oidcConfig{
.clientId = std::string{ config->getString("oidc-client-id", "") },
.clientSecret = std::string{ config->getString("oidc-client-secret", "") },
.authorizationEndpoint = std::string{ config->getString("oidc-authorization-endpoint", "") },
.tokenEndpoint = std::string{ config->getString("oidc-token-endpoint", "") },
.userInfoEndpoint = std::string{ config->getString("oidc-userinfo-endpoint", "") },
.redirectEndpoint = publicUrl + "/oidc/callback",
.scope = std::string{ config->getString("oidc-scope", "openid profile email") },
.name = std::string{ config->getString("oidc-provider-name", "OpenID Connect") },
};
if (oidcConfig.clientId.empty() || oidcConfig.clientSecret.empty() || oidcConfig.authorizationEndpoint.empty() || oidcConfig.tokenEndpoint.empty() || oidcConfig.userInfoEndpoint.empty())
throw core::LmsException{ "Incomplete OIDC configuration" };
oidcService = ui::createOIDCService(oidcBaseAuthService, oidcConfig);
}
break;
} }
image::init(argv[0]); image::init(argv[0]);
@@ -534,8 +567,8 @@ namespace lms
// bind UI entry point // bind UI entry point
server.addEntryPoint(Wt::EntryPointType::Application, server.addEntryPoint(Wt::EntryPointType::Application,
[&database, &appManager, uiAuthenticationBackend, serverInstanceId](const Wt::WEnvironment& env) { [&database, &appManager, uiAuthenticationBackend, serverInstanceId, &oidcService, oidcLoginNameClaim](const Wt::WEnvironment& env) {
return ui::LmsApplication::create(env, *database, appManager, uiAuthenticationBackend, serverInstanceId); return ui::LmsApplication::create(env, *database, appManager, uiAuthenticationBackend, serverInstanceId, oidcService.get(), oidcLoginNameClaim);
}); });
proxyScannerEventsToApplication(*scannerService, server); proxyScannerEventsToApplication(*scannerService, server);
+76
View File
@@ -20,6 +20,9 @@
#include "Auth.hpp" #include "Auth.hpp"
#include <Wt/Auth/HashFunction.h> #include <Wt/Auth/HashFunction.h>
#include <Wt/Auth/AuthService.h>
#include <Wt/Auth/Identity.h>
#include <Wt/Auth/OidcService.h>
#include <Wt/WCheckBox.h> #include <Wt/WCheckBox.h>
#include <Wt/WEnvironment.h> #include <Wt/WEnvironment.h>
@@ -185,6 +188,22 @@ namespace lms::ui
core::Service<auth::IAuthTokenService>::get()->clearAuthTokens(authTokenDomain, userId); core::Service<auth::IAuthTokenService>::get()->clearAuthTokens(authTokenDomain, userId);
} }
std::unique_ptr<Wt::Auth::OidcService> createOIDCService(const Wt::Auth::AuthService& authService, const OIDCConfig& config)
{
auto service{ std::make_unique<Wt::Auth::OidcService>(authService) };
service->setClientId(config.clientId);
service->setClientSecret(config.clientSecret);
service->setAuthEndpoint(config.authorizationEndpoint);
service->setTokenEndpoint(config.tokenEndpoint);
service->setUserInfoEndpoint(config.userInfoEndpoint);
service->setRedirectEndpoint(config.redirectEndpoint);
service->setAuthenticationScope(config.scope);
service->setName("oidc");
service->setDescription(config.name);
service->setPopupEnabled(false);
return service;
}
PasswordAuth::PasswordAuth(auth::IPasswordService& passwordService) PasswordAuth::PasswordAuth(auth::IPasswordService& passwordService)
: Wt::WTemplateFormView{ Wt::WString::tr("Lms.Auth.template") } : Wt::WTemplateFormView{ Wt::WString::tr("Lms.Auth.template") }
{ {
@@ -234,4 +253,61 @@ namespace lms::ui
updateView(model.get()); updateView(model.get());
} }
OIDCAuth::OIDCAuth(const Wt::Auth::OidcService& service, std::string loginNameClaim)
: Wt::WTemplateFormView{ Wt::WString::tr("Lms.OIDCAuth.template") }
, _process{ service.createProcess(service.authenticationScope()) }
{
Wt::WPushButton* loginBtn{ bindNew<Wt::WPushButton>("login-btn", Wt::WString::tr("Lms.OIDCAuth.login").arg(service.description())) };
loginBtn->clicked().connect(_process.get(), &Wt::Auth::OAuthProcess::startAuthenticate);
_process->authenticated().connect(this, [this, loginNameClaim = std::move(loginNameClaim)](const Wt::Auth::Identity& identity) {
if (!identity.isValid())
{
bindString("error", Wt::WString::tr("Lms.OIDCAuth.failed"));
return;
}
std::string loginName;
if (loginNameClaim == "email")
{
if (!identity.emailVerified())
{
bindString("error", Wt::WString::tr("Lms.OIDCAuth.unverified-email"));
return;
}
loginName = identity.email();
}
else if (loginNameClaim == "name")
loginName = identity.name().toUTF8();
else
loginName = identity.id();
if (loginName.empty())
{
bindString("error", Wt::WString::tr("Lms.OIDCAuth.missing-claim"));
return;
}
db::User::pointer user;
{
auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
user = db::User::find(LmsApp->getDbSession(), loginName);
if (!user)
{
const db::UserType type{ db::User::getCount(LmsApp->getDbSession()) == 0 ? db::UserType::ADMIN : db::UserType::REGULAR };
user = LmsApp->getDbSession().create<db::User>(loginName);
user.modify()->setType(type);
if (!identity.name().empty())
user.modify()->setDisplayName(identity.name().toUTF8());
}
user.modify()->setLastLogin(Wt::WDateTime::currentDateTime());
}
userLoggedIn.emit(user->getId());
});
bindEmpty("error");
}
OIDCAuth::~OIDCAuth() = default;
} // namespace lms::ui } // namespace lms::ui
+37
View File
@@ -21,6 +21,9 @@
#include <Wt/WTemplateFormView.h> #include <Wt/WTemplateFormView.h>
#include <memory>
#include <string>
#include "database/objects/UserId.hpp" #include "database/objects/UserId.hpp"
namespace lms::auth namespace lms::auth
@@ -28,15 +31,37 @@ namespace lms::auth
class IPasswordService; class IPasswordService;
} }
namespace Wt::Auth
{
class AuthService;
class OAuthProcess;
class OidcService;
}
namespace lms::ui namespace lms::ui
{ {
enum class AuthenticationBackend enum class AuthenticationBackend
{ {
Internal, Internal,
Env, Env,
OIDC,
PAM, PAM,
}; };
struct OIDCConfig
{
std::string clientId;
std::string clientSecret;
std::string authorizationEndpoint;
std::string tokenEndpoint;
std::string userInfoEndpoint;
std::string redirectEndpoint;
std::string scope;
std::string name;
};
std::unique_ptr<Wt::Auth::OidcService> createOIDCService(const Wt::Auth::AuthService& authService, const OIDCConfig& config);
db::UserId processAuthToken(const Wt::WEnvironment& env); db::UserId processAuthToken(const Wt::WEnvironment& env);
class PasswordAuth : public Wt::WTemplateFormView class PasswordAuth : public Wt::WTemplateFormView
@@ -46,4 +71,16 @@ namespace lms::ui
Wt::Signal<db::UserId> userLoggedIn; Wt::Signal<db::UserId> userLoggedIn;
}; };
class OIDCAuth : public Wt::WTemplateFormView
{
public:
OIDCAuth(const Wt::Auth::OidcService& service, std::string loginNameClaim);
~OIDCAuth() override;
Wt::Signal<db::UserId> userLoggedIn;
private:
std::unique_ptr<Wt::Auth::OAuthProcess> _process;
};
} // namespace lms::ui } // namespace lms::ui
+16 -3
View File
@@ -96,6 +96,7 @@ namespace lms::ui
res->use(appRoot + "error"); res->use(appRoot + "error");
res->use(appRoot + "explore"); res->use(appRoot + "explore");
res->use(appRoot + "login"); res->use(appRoot + "login");
res->use(appRoot + "oidc-login");
res->use(appRoot + "main"); res->use(appRoot + "main");
res->use(appRoot + "mediaplayer"); res->use(appRoot + "mediaplayer");
res->use(appRoot + "messages"); res->use(appRoot + "messages");
@@ -151,9 +152,9 @@ namespace lms::ui
} // namespace } // namespace
std::unique_ptr<Wt::WApplication> LmsApplication::create(const Wt::WEnvironment& env, db::IDb& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend, const core::UUID& serverInstanceId) std::unique_ptr<Wt::WApplication> LmsApplication::create(const Wt::WEnvironment& env, db::IDb& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend, const core::UUID& serverInstanceId, const Wt::Auth::OidcService* oidcService, std::string oidcLoginNameClaim)
{ {
return std::make_unique<LmsApplication>(env, db, appManager, authBackend, serverInstanceId); return std::make_unique<LmsApplication>(env, db, appManager, authBackend, serverInstanceId, oidcService, std::move(oidcLoginNameClaim));
} }
LmsApplication* LmsApplication::instance() LmsApplication* LmsApplication::instance()
@@ -161,11 +162,13 @@ namespace lms::ui
return static_cast<LmsApplication*>(Wt::WApplication::instance()); return static_cast<LmsApplication*>(Wt::WApplication::instance());
} }
LmsApplication::LmsApplication(const Wt::WEnvironment& env, db::IDb& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend, const core::UUID& serverInstanceId) LmsApplication::LmsApplication(const Wt::WEnvironment& env, db::IDb& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend, const core::UUID& serverInstanceId, const Wt::Auth::OidcService* oidcService, std::string oidcLoginNameClaim)
: Wt::WApplication{ env } : Wt::WApplication{ env }
, _db{ db } , _db{ db }
, _appManager{ appManager } , _appManager{ appManager }
, _authBackend{ authBackend } , _authBackend{ authBackend }
, _oidcService{ oidcService }
, _oidcLoginNameClaim{ std::move(oidcLoginNameClaim) }
, _serverInstanceId{ serverInstanceId } , _serverInstanceId{ serverInstanceId }
, _areDownloadsEnabled(core::Service<core::IConfig>::get()->getBool("ui-allow-downloads", true)) , _areDownloadsEnabled(core::Service<core::IConfig>::get()->getBool("ui-allow-downloads", true))
{ {
@@ -272,10 +275,20 @@ namespace lms::ui
// Try to authenticate using auth token ("remember me" checkbox), may fail // Try to authenticate using auth token ("remember me" checkbox), may fail
userId = processAuthToken(environment()); userId = processAuthToken(environment());
break; break;
case AuthenticationBackend::OIDC:
break;
} }
if (userId.isValid()) if (userId.isValid())
onUserLoggedIn(userId, false /* strongAuth */); onUserLoggedIn(userId, false /* strongAuth */);
else if (_authBackend == AuthenticationBackend::OIDC)
{
assert(_oidcService);
OIDCAuth* auth{ root()->addNew<OIDCAuth>(*_oidcService, _oidcLoginNameClaim) };
auth->userLoggedIn.connect(this, [this](db::UserId authenticatedUserId) {
onUserLoggedIn(authenticatedUserId, true /* strongAuth */);
});
}
else else
processPasswordAuth(); processPasswordAuth();
} }
+4 -2
View File
@@ -55,10 +55,10 @@ namespace lms::ui
class LmsApplication : public Wt::WApplication class LmsApplication : public Wt::WApplication
{ {
public: public:
LmsApplication(const Wt::WEnvironment& env, db::IDb& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend, const core::UUID& serverInstanceId); LmsApplication(const Wt::WEnvironment& env, db::IDb& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend, const core::UUID& serverInstanceId, const Wt::Auth::OidcService* oidcService = nullptr, std::string oidcLoginNameClaim = "sub");
~LmsApplication() override; ~LmsApplication() override;
static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env, db::IDb& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend, const core::UUID& serverInstanceId); static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env, db::IDb& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend, const core::UUID& serverInstanceId, const Wt::Auth::OidcService* oidcService = nullptr, std::string oidcLoginNameClaim = "sub");
static LmsApplication* instance(); static LmsApplication* instance();
// Session application data // Session application data
@@ -115,6 +115,8 @@ namespace lms::ui
Wt::Signal<> _preQuit; Wt::Signal<> _preQuit;
LmsApplicationManager& _appManager; LmsApplicationManager& _appManager;
const AuthenticationBackend _authBackend; const AuthenticationBackend _authBackend;
const Wt::Auth::OidcService* const _oidcService;
const std::string _oidcLoginNameClaim;
const core::UUID _serverInstanceId; const core::UUID _serverInstanceId;
const bool _areDownloadsEnabled; const bool _areDownloadsEnabled;
scanner::Events _scannerEvents; scanner::Events _scannerEvents;