fix login rediret retry error.
All checks were successful
Deploy / Build (push) Successful in 5m33s

This commit is contained in:
amass 2025-01-09 22:23:03 +08:00
parent 69cecf8022
commit b17b50b751
7 changed files with 99 additions and 63 deletions

View File

@ -1,6 +1,7 @@
#include "Session.h" #include "Session.h"
#include "BoostLog.h" #include "BoostLog.h"
#include <Wt/Auth/Dbo/UserDatabase.h> #include <Wt/Auth/Dbo/UserDatabase.h>
#include <Wt/Auth/Identity.h>
#include <Wt/Dbo/FixedSqlConnectionPool.h> #include <Wt/Dbo/FixedSqlConnectionPool.h>
#include <Wt/Dbo/SqlConnectionPool.h> #include <Wt/Dbo/SqlConnectionPool.h>
#include <Wt/Dbo/WtJsonSqlTraits.h> #include <Wt/Dbo/WtJsonSqlTraits.h>
@ -11,7 +12,7 @@ std::unique_ptr<Wt::Dbo::SqlConnectionPool> sqlConnectionPool;
bool initialize(const std::string &path) { bool initialize(const std::string &path) {
try { try {
auto connection = std::make_unique<Wt::Dbo::backend::Sqlite3>(path); auto connection = std::make_unique<Wt::Dbo::backend::Sqlite3>(path);
connection->setProperty("show-queries", "true"); // connection->setProperty("show-queries", "true");
connection->setDateTimeStorage(Wt::Dbo::SqlDateTimeType::DateTime, connection->setDateTimeStorage(Wt::Dbo::SqlDateTimeType::DateTime,
Wt::Dbo::backend::DateTimeStorage::PseudoISO8601AsText); Wt::Dbo::backend::DateTimeStorage::PseudoISO8601AsText);
sqlConnectionPool = std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), 10); sqlConnectionPool = std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), 10);
@ -73,6 +74,10 @@ void JsonSerializer::act(FieldRef<std::chrono::system_clock::time_point> field)
} // namespace Dbo } // namespace Dbo
} // namespace Wt } // namespace Wt
User::User(const Wt::Auth::User &user) {
identity = user.identity(Wt::Auth::Identity::LoginName).toUTF8();
}
DBO_INSTANTIATE_TEMPLATES(User) DBO_INSTANTIATE_TEMPLATES(User)
DBO_INSTANTIATE_TEMPLATES(Task) DBO_INSTANTIATE_TEMPLATES(Task)
DBO_INSTANTIATE_TEMPLATES(HomeBox::Item) DBO_INSTANTIATE_TEMPLATES(HomeBox::Item)

View File

@ -8,9 +8,13 @@ using AuthInfo = Wt::Auth::Dbo::AuthInfo<User>;
class User { class User {
public: public:
User() = default;
User(const Wt::Auth::User &user);
template <class Action> template <class Action>
void persist(Action &a) { void persist(Action &a) {
} }
std::string identity;
}; };
DBO_EXTERN_TEMPLATES(User); DBO_EXTERN_TEMPLATES(User);

View File

@ -119,36 +119,36 @@ Application::~Application() {
} }
void Application::authEvent() { void Application::authEvent() {
auto app = Amass::Singleton<WebToolkit::Server>::instance();
auto &service = app->authService();
auto token = environment().getCookie(service.authTokenCookieName());
if (m_session->login().loggedIn()) { if (m_session->login().loggedIn()) {
const Wt::Auth::User &u = m_session->login().user(); const Wt::Auth::User &u = m_session->login().user();
LOG(info) << "User " << u.id() << " (" << u.identity(Wt::Auth::Identity::LoginName) << ")" LOG(info) << "User " << u.id() << " (" << u.identity(Wt::Auth::Identity::LoginName) << ")"
<< " logged in."; << " logged in.";
auto app = Amass::Singleton<WebToolkit::Server>::instance();
auto &service = app->authService();
auto &env = environment();
auto token = env.getCookie(service.authTokenCookieName());
if (token == nullptr) { if (token == nullptr) {
Wt::Http::Cookie cookie(service.authTokenCookieName(), service.createAuthToken(u)); Wt::Http::Cookie cookie(service.authTokenCookieName(), service.createAuthToken(u));
cookie.setDomain(service.authTokenCookieDomain()); cookie.setDomain(service.authTokenCookieDomain());
cookie.setPath(AuthModel::CookiePath); cookie.setPath(AuthModel::CookiePath);
cookie.setExpires(Wt::WDateTime()); cookie.setExpires(Wt::WDateTime());
setCookie(cookie); setCookie(cookie);
app->insertCookie(cookie.value(), u);
}
if (m_loginPage) {
if (m_navigationBar != nullptr) {
m_loginPageRef = m_navigationBar->addLoginItem(std::move(m_loginPage));
m_loginPageRef->removeStyleClass("bulma-m-auto");
m_loginPageRef->removeStyleClass("bulma-container");
}
} else if (m_loginPageRef != nullptr && m_loginPageRef->parent() == m_root) {
m_loginPage = m_loginPageRef->parent()->removeWidget(m_loginPageRef);
if (m_navigationBar != nullptr) {
m_loginPageRef = m_navigationBar->addLoginItem(std::move(m_loginPage));
m_loginPageRef->removeStyleClass("bulma-m-auto");
m_loginPageRef->removeStyleClass("bulma-container");
}
} }
if (m_loginedRedirectUrl.empty()) { if (m_loginedRedirectUrl.empty()) {
if (m_loginPage) {
if (m_navigationBar != nullptr) {
m_loginPageRef = m_navigationBar->addLoginItem(std::move(m_loginPage));
m_loginPageRef->removeStyleClass("bulma-m-auto");
m_loginPageRef->removeStyleClass("bulma-container");
}
} else if (m_loginPageRef != nullptr && m_loginPageRef->parent() == m_root) {
m_loginPage = m_loginPageRef->parent()->removeWidget(m_loginPageRef);
if (m_navigationBar != nullptr) {
m_loginPageRef = m_navigationBar->addLoginItem(std::move(m_loginPage));
m_loginPageRef->removeStyleClass("bulma-m-auto");
m_loginPageRef->removeStyleClass("bulma-container");
}
}
setInternalPath("/", true); setInternalPath("/", true);
} else { } else {
redirect(m_loginedRedirectUrl); redirect(m_loginedRedirectUrl);
@ -157,6 +157,9 @@ void Application::authEvent() {
if (m_navigationBar != nullptr) { if (m_navigationBar != nullptr) {
m_loginPage = m_navigationBar->removeLoginItem(); m_loginPage = m_navigationBar->removeLoginItem();
} }
if (token != nullptr) {
app->removeCookie(*token);
}
LOG(info) << "user logged out, internal path: " << internalPath(); LOG(info) << "user logged out, internal path: " << internalPath();
if (internalPath() == "/wt/login") { if (internalPath() == "/wt/login") {
handlePathChange(internalPath()); handlePathChange(internalPath());
@ -258,9 +261,23 @@ const Wt::Auth::PasswordService &Server::passwordService() {
return *m_passwordService; return *m_passwordService;
} }
void Server::insertCookie(const std::string &cookie) { std::optional<User> Server::user(const std::string &cookie) {
if (m_cookies.contains(cookie)) {
return m_cookies.at(cookie);
} else {
return std::nullopt;
}
}
void Server::insertCookie(const std::string &cookie, const Wt::Auth::User &user) {
if (!m_cookies.contains(cookie)) { if (!m_cookies.contains(cookie)) {
m_cookies.insert(cookie); m_cookies.insert_or_assign(cookie, user);
}
}
void Server::removeCookie(const std::string &cookie) {
if (m_cookies.contains(cookie)) {
m_cookies.erase(cookie);
} }
} }
@ -281,7 +298,7 @@ Wt::Http::Cookie Server::updateCookie(const std::string &oldCookie, const Wt::Au
if (m_cookies.contains(oldCookie)) { // 勾选了记住我 if (m_cookies.contains(oldCookie)) { // 勾选了记住我
m_cookies.erase(oldCookie); m_cookies.erase(oldCookie);
cookie.setMaxAge(std::chrono::seconds(result.newTokenValidity())); cookie.setMaxAge(std::chrono::seconds(result.newTokenValidity()));
m_cookies.insert(newToken); m_cookies.insert_or_assign(newToken, result.user());
} else { // 只在会话期间有效 } else { // 只在会话期间有效
cookie.setExpires(Wt::WDateTime()); cookie.setExpires(Wt::WDateTime());
} }

View File

@ -4,7 +4,8 @@
#include "Singleton.h" #include "Singleton.h"
#include <Wt/WApplication.h> #include <Wt/WApplication.h>
#include <memory> #include <memory>
#include <unordered_set> #include <unordered_map>
#include "Database/User.h"
namespace Wt { namespace Wt {
class WServer; class WServer;
@ -55,8 +56,10 @@ public:
void initializeAuthenticationService(); void initializeAuthenticationService();
Wt::Auth::AuthService &authService(); Wt::Auth::AuthService &authService();
const Wt::Auth::PasswordService &passwordService(); const Wt::Auth::PasswordService &passwordService();
void insertCookie(const std::string &cookie); void insertCookie(const std::string &cookie, const Wt::Auth::User &user);
Wt::Http::Cookie updateCookie(const std::string &oldCookie, const Wt::Auth::AuthTokenResult &result, bool secure); Wt::Http::Cookie updateCookie(const std::string &oldCookie, const Wt::Auth::AuthTokenResult &result, bool secure);
std::optional<User> user(const std::string &cookie);
void removeCookie(const std::string &cookie);
protected: protected:
Server(uint16_t port, const std::string &applicationRoot, const std::string &documentRoot); Server(uint16_t port, const std::string &applicationRoot, const std::string &documentRoot);
@ -68,7 +71,7 @@ private:
std::unique_ptr<Wt::Auth::AuthService> m_authService; std::unique_ptr<Wt::Auth::AuthService> m_authService;
std::unique_ptr<Wt::Auth::PasswordService> m_passwordService; std::unique_ptr<Wt::Auth::PasswordService> m_passwordService;
std::unordered_set<std::string> m_cookies; std::unordered_map<std::string, User> m_cookies;
}; };
} // namespace WebToolkit } // namespace WebToolkit
#endif // __WEBAPPLICATION_H__ #endif // __WEBAPPLICATION_H__

View File

@ -26,7 +26,7 @@ std::unique_ptr<Wt::WWidget> LoginPage::createRegistrationView(const Wt::Auth::I
} }
void LoginPage::processExternalEnvironment(const std::string &externalPath, Wt::Auth::AuthService &service) { void LoginPage::processExternalEnvironment(const std::string &externalPath, Wt::Auth::AuthService &service) {
using namespace Wt::Auth; using namespace Wt;
std::string emailToken; std::string emailToken;
if (service.emailVerificationEnabled()) { if (service.emailVerificationEnabled()) {
auto redirectPath = service.emailRedirectInternalPath(); auto redirectPath = service.emailRedirectInternalPath();
@ -36,24 +36,24 @@ void LoginPage::processExternalEnvironment(const std::string &externalPath, Wt::
} }
} }
if (!emailToken.empty()) { if (!emailToken.empty()) {
EmailTokenResult result = model()->processEmailToken(emailToken); Auth::EmailTokenResult result = model()->processEmailToken(emailToken);
switch (result.state()) { switch (result.state()) {
case EmailTokenState::Invalid: case Auth::EmailTokenState::Invalid:
displayError(tr("Wt.Auth.error-invalid-token")); displayError(tr("Wt.Auth.error-invalid-token"));
break; break;
case EmailTokenState::Expired: case Auth::EmailTokenState::Expired:
displayError(tr("Wt.Auth.error-token-expired")); displayError(tr("Wt.Auth.error-token-expired"));
break; break;
case EmailTokenState::UpdatePassword: case Auth::EmailTokenState::UpdatePassword:
letUpdatePassword(result.user(), false); letUpdatePassword(result.user(), false);
break; break;
case EmailTokenState::EmailConfirmed: case Auth::EmailTokenState::EmailConfirmed:
displayInfo(tr("Wt.Auth.info-email-confirmed")); displayInfo(tr("Wt.Auth.info-email-confirmed"));
User user = result.user(); Auth::User user = result.user();
LoginState state = LoginState::Strong; Auth::LoginState state = Auth::LoginState::Strong;
if (model()->hasMfaStep(user)) { if (model()->hasMfaStep(user)) {
state = LoginState::RequiresMfa; state = Auth::LoginState::RequiresMfa;
} }
model()->loginUser(login(), user, state); model()->loginUser(login(), user, state);
} }
@ -67,10 +67,10 @@ void LoginPage::processExternalEnvironment(const std::string &externalPath, Wt::
return; return;
} }
User user = model()->processAuthToken(); Auth::User user = model()->processAuthToken();
LoginState state = LoginState::Weak; Auth::LoginState state = Auth::LoginState::Weak;
if (model()->hasMfaStep(user)) { if (model()->hasMfaStep(user)) {
state = LoginState::RequiresMfa; state = Auth::LoginState::RequiresMfa;
} }
model()->loginUser(login(), user, state); model()->loginUser(login(), user, state);
} }

View File

@ -1,6 +1,7 @@
#include "Restful.h" #include "Restful.h"
#include "Application.h" #include "Application.h"
#include "Database/Session.h" #include "Database/Session.h"
#include "Database/User.h"
#include "model/AuthModel.h" #include "model/AuthModel.h"
#include <Wt/Auth/AuthService.h> #include <Wt/Auth/AuthService.h>
#include <Wt/Auth/Identity.h> #include <Wt/Auth/Identity.h>
@ -15,6 +16,7 @@
DBO_INSTANTIATE_TEMPLATES(MyMessage) DBO_INSTANTIATE_TEMPLATES(MyMessage)
void AuthenticationResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) { void AuthenticationResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) {
using namespace Wt;
auto tag = request.urlParam("tag"); auto tag = request.urlParam("tag");
// LOG(info) << "path: " << request.path() << ", tag: " << tag << ", server: " << request.hostName(); // LOG(info) << "path: " << request.path() << ", tag: " << tag << ", server: " << request.hostName();
response.setMimeType("application/json"); response.setMimeType("application/json");
@ -22,30 +24,35 @@ void AuthenticationResource::handleRequest(const Wt::Http::Request &request, Wt:
auto app = Amass::Singleton<WebToolkit::Server>::instance(); auto app = Amass::Singleton<WebToolkit::Server>::instance();
auto &service = app->authService(); auto &service = app->authService();
if (tag == "verify") { if (tag == "verify") {
auto session = Database::session(); const std::string *token = request.getCookieValue(service.authTokenCookieName());
auto enabled = service.authTokenUpdateEnabled(); Auth::AuthTokenState state = Auth::AuthTokenState::Invalid;
boost::scope::scope_exit raii([&enabled, &service] { service.setAuthTokenUpdateEnabled(enabled); }); if (token != nullptr) {
service.setAuthTokenUpdateEnabled(false); if (auto u = app->user(*token); u) {
Wt::Auth::AuthTokenState state; state = Auth::AuthTokenState::Valid;
Wt::Auth::User user; message.user = u->identity;
if (service.authTokensEnabled()) { } else {
const std::string *token = request.getCookieValue(service.authTokenCookieName()); Wt::Auth::User user;
if (token != nullptr) { auto session = Database::session();
Wt::Auth::AuthTokenResult result = service.processAuthToken(*token, session->users()); auto enabled = service.authTokenUpdateEnabled();
state = result.state(); boost::scope::scope_exit raii([&enabled, &service] { service.setAuthTokenUpdateEnabled(enabled); });
if (state == Wt::Auth::AuthTokenState::Valid) { service.setAuthTokenUpdateEnabled(false);
user = result.user(); if (service.authTokensEnabled()) {
Auth::AuthTokenResult result = service.processAuthToken(*token, session->users());
state = result.state();
if (state == Auth::AuthTokenState::Valid) {
user = result.user();
message.user = user.identity(Auth::Identity::LoginName).toUTF8();
}
} }
} }
// LOG(info) << "state: " << (int)state << " " << message.user;
} else {
LOG(warning) << "cannot access cookie.";
} }
if (user.isValid()) {
message.user = user.identity(Wt::Auth::Identity::LoginName).toUTF8();
}
// LOG(info) << "state: " << (int)state << " " << message.user;
message.message = "Hello, World!"; message.message = "Hello, World!";
message.status = state == Wt::Auth::AuthTokenState::Valid ? 0 : 404; message.status = state == Auth::AuthTokenState::Valid ? 0 : 404;
using namespace boost::beast::http; using namespace boost::beast::http;
response.setStatus(static_cast<int>(state == Wt::Auth::AuthTokenState::Valid ? status::ok : status::unauthorized)); response.setStatus(static_cast<int>(state == Auth::AuthTokenState::Valid ? status::ok : status::unauthorized));
} else { // logout } else { // logout
auto domain = request.hostName(); auto domain = request.hostName();
if (domain.find("amass.fun") != std::string::npos) { if (domain.find("amass.fun") != std::string::npos) {
@ -54,7 +61,7 @@ void AuthenticationResource::handleRequest(const Wt::Http::Request &request, Wt:
response.addHeader("Set-Cookie", std::format("{}=; path={}; Domain={}; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT", response.addHeader("Set-Cookie", std::format("{}=; path={}; Domain={}; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT",
service.authTokenCookieName(), AuthModel::CookiePath, domain)); service.authTokenCookieName(), AuthModel::CookiePath, domain));
} }
Wt::Dbo::JsonSerializer writer(response.out()); Dbo::JsonSerializer writer(response.out());
writer.serialize(message); writer.serialize(message);
} }

View File

@ -9,22 +9,22 @@ AuthModel::AuthModel(const Wt::Auth::AuthService &baseAuth, Wt::Auth::AbstractUs
} }
Wt::Auth::User AuthModel::processAuthToken() { Wt::Auth::User AuthModel::processAuthToken() {
using namespace Wt::Auth; using namespace Wt;
if (baseAuth()->authTokensEnabled()) { if (baseAuth()->authTokensEnabled()) {
Wt::WApplication *app = Wt::WApplication::instance(); Wt::WApplication *app = Wt::WApplication::instance();
const Wt::WEnvironment &env = app->environment(); const Wt::WEnvironment &env = app->environment();
const std::string *token = env.getCookie(baseAuth()->authTokenCookieName()); const std::string *token = env.getCookie(baseAuth()->authTokenCookieName());
if (token) { if (token) {
AuthTokenResult result = baseAuth()->processAuthToken(*token, users()); Auth::AuthTokenResult result = baseAuth()->processAuthToken(*token, users());
auto server = Amass::Singleton<WebToolkit::Server>::instance(); auto server = Amass::Singleton<WebToolkit::Server>::instance();
auto cookie = server->updateCookie(*token, result, app->environment().urlScheme() == "https"); auto cookie = server->updateCookie(*token, result, app->environment().urlScheme() == "https");
if ((result.state() == AuthTokenState::Invalid) || !cookie.value().empty()) { if ((result.state() == Auth::AuthTokenState::Invalid) || !cookie.value().empty()) {
app->setCookie(cookie); app->setCookie(cookie);
} }
return result.state() == AuthTokenState::Valid ? result.user() : User(); return result.state() == Auth::AuthTokenState::Valid ? result.user() : Auth::User();
} }
} }
return User(); return Auth::User();
} }
void AuthModel::setRememberMeCookie(const Wt::Auth::User &user) { void AuthModel::setRememberMeCookie(const Wt::Auth::User &user) {
@ -39,6 +39,6 @@ void AuthModel::setRememberMeCookie(const Wt::Auth::User &user) {
cookie.setSecure(app->environment().urlScheme() == "https"); cookie.setSecure(app->environment().urlScheme() == "https");
auto server = Amass::Singleton<WebToolkit::Server>::instance(); auto server = Amass::Singleton<WebToolkit::Server>::instance();
server->insertCookie(cookie.value()); server->insertCookie(cookie.value(), user);
app->setCookie(cookie); app->setCookie(cookie);
} }