Older/WebApplication/Application.cpp

311 lines
12 KiB
C++
Raw Normal View History

2024-12-01 15:10:25 +08:00
#include "Application.h"
#include "BoostLog.h"
#include "BulmaTheme.h"
#include "Database/Session.h"
2024-12-02 01:18:57 +08:00
#include "HomePage.h"
2024-12-01 15:10:25 +08:00
#include "LoginPage.h"
2024-12-21 13:35:12 +08:00
#include "NavigationBar.h"
2025-01-05 00:33:19 +08:00
#include "RedirectPage.h"
2024-12-01 15:10:25 +08:00
#include "Restful.h"
#include "VisitorRecordsPage.h"
2025-01-03 14:12:02 +08:00
#include "model/AuthModel.h"
2024-12-01 15:10:25 +08:00
#include <Wt/Auth/AuthService.h>
#include <Wt/Auth/HashFunction.h>
#include <Wt/Auth/Identity.h>
#include <Wt/Auth/PasswordService.h>
#include <Wt/Auth/PasswordStrengthValidator.h>
#include <Wt/Auth/PasswordVerifier.h>
#include <Wt/Dbo/FixedSqlConnectionPool.h>
#include <Wt/Dbo/SqlConnectionPool.h>
#include <Wt/Dbo/backend/Sqlite3.h>
2024-12-26 23:10:41 +08:00
#include <Wt/Http/Cookie.h>
2024-12-01 15:10:25 +08:00
#include <Wt/WContainerWidget.h>
#include <Wt/WEnvironment.h>
#include <Wt/WServer.h>
#include <format>
namespace WebToolkit {
2025-01-04 14:04:27 +08:00
Application::Application(const Wt::WEnvironment &env, bool embedded)
2025-01-08 15:01:54 +08:00
: Wt::WApplication(env), m_startup(this, "startup"), m_logout(this, "logout") {
2024-12-01 15:10:25 +08:00
messageResourceBundle().use(appRoot() + "wt");
messageResourceBundle().use(appRoot() + "auth_strings");
messageResourceBundle().use(appRoot() + "auth_css_theme");
useStyleSheet("/resources/app.css");
LOG(info) << "app root: " << appRoot();
m_session = Database::session();
m_session->login().changed().connect(this, &Application::authEvent);
setTheme(std::make_shared<BulmaTheme>("bulma", !embedded));
2025-01-04 14:04:27 +08:00
std::string externalPath;
2024-12-01 15:10:25 +08:00
if (!embedded) {
2024-12-21 13:35:12 +08:00
m_navigationBar = root()->addNew<NavigationBar>();
2024-12-24 23:46:20 +08:00
m_navigationBar->registerClicked.connect([this]() {
if (m_loginPage) {
2024-12-22 13:56:15 +08:00
m_loginPage->registerNewUser();
2024-12-24 23:46:20 +08:00
} else if (m_loginPageRef) {
2024-12-22 13:56:15 +08:00
m_loginPageRef->registerNewUser();
}
});
2024-12-21 13:35:12 +08:00
m_root = root()->addNew<Wt::WContainerWidget>();
2024-12-01 15:10:25 +08:00
} else {
std::unique_ptr<Wt::WContainerWidget> topPtr = std::make_unique<Wt::WContainerWidget>();
m_root = topPtr.get();
const std::string *div = env.getParameter("div");
if (div) {
setJavaScriptClass(*div);
bindWidget(std::move(topPtr), *div);
} else {
LOG(error) << "Missing: parameter: 'div'";
2025-01-03 04:51:08 +08:00
m_root = nullptr;
2024-12-01 15:10:25 +08:00
}
2025-01-04 14:04:27 +08:00
auto path = env.getParameter("path");
if (path != nullptr) {
externalPath = *path;
LOG(info) << "external path: " << externalPath;
2024-12-01 15:10:25 +08:00
} else {
auto parameters = env.getParameterMap();
for (auto &p : parameters) {
LOG(info) << p.first;
}
}
}
2024-12-21 13:35:12 +08:00
auto app = Amass::Singleton<WebToolkit::Server>::instance();
2025-01-09 12:21:06 +08:00
bool authTokensEnabled = app->authService().authTokensEnabled();
std::string authTokenCookieName = app->authService().authTokenCookieName();
std::string authTokenCookieDomain = app->authService().authTokenCookieDomain();
if (env.hostName().find("amass.fun") != std::string::npos) {
if (authTokenCookieDomain != AuthModel::CookieDomain) {
app->authService().setAuthTokensEnabled(authTokensEnabled, authTokenCookieName, AuthModel::CookieDomain);
}
} else {
if (!authTokenCookieDomain.empty()) {
app->authService().setAuthTokensEnabled(authTokensEnabled, authTokenCookieName, "");
}
}
2025-01-09 19:16:00 +08:00
auto next = env.getParameter("redirect");
if (next != nullptr) {
m_loginedRedirectUrl = *next;
}
2025-01-09 12:21:06 +08:00
LOG(info) << "url: " << url() << ", host name: " << env.hostName();
LOG(info) << "resources url: " << resourcesUrl() << ", relative resources url: " << relativeResourcesUrl();
2025-01-09 19:16:00 +08:00
LOG(info) << "internal path: " << internalPath() << ", bookmark url: " << bookmarkUrl() << ", next: " << m_loginedRedirectUrl;
2025-01-09 12:21:06 +08:00
2024-12-21 13:35:12 +08:00
m_loginPage = std::make_unique<LoginPage>(app->authService(), m_session->users(), m_session->login());
2025-01-04 14:04:27 +08:00
if (externalPath.empty()) {
2025-01-03 04:51:08 +08:00
m_loginPage->processEnvironment();
} else {
2025-01-04 14:04:27 +08:00
m_loginPage->processExternalEnvironment(externalPath, app->authService());
2025-01-03 04:51:08 +08:00
}
2025-01-04 14:04:27 +08:00
m_logout.connect([this]() {
LOG(info) << "logout from external callbak.";
m_session->login().logout();
});
m_startup.connect(this, [externalPath]() {
LOG(info) << "wtapp started.";
auto app = Wt::WApplication::instance();
auto path = externalPath.empty() ? app->internalPath() : externalPath;
if (path != app->internalPath()) {
app->setInternalPath(externalPath.empty() ? app->internalPath() : externalPath, true);
} else {
dynamic_cast<Application *>(app)->handlePathChange(path);
}
});
2024-12-21 23:08:38 +08:00
internalPathChanged().connect(this, &Application::handlePathChange);
2025-01-04 14:04:27 +08:00
doJavaScript(m_startup.createCall({}));
2024-12-01 15:10:25 +08:00
}
Application::~Application() {
}
void Application::authEvent() {
2025-01-09 22:23:03 +08:00
auto app = Amass::Singleton<WebToolkit::Server>::instance();
auto &service = app->authService();
auto token = environment().getCookie(service.authTokenCookieName());
2024-12-01 15:10:25 +08:00
if (m_session->login().loggedIn()) {
const Wt::Auth::User &u = m_session->login().user();
LOG(info) << "User " << u.id() << " (" << u.identity(Wt::Auth::Identity::LoginName) << ")"
<< " logged in.";
2025-01-02 23:02:37 +08:00
if (token == nullptr) {
Wt::Http::Cookie cookie(service.authTokenCookieName(), service.createAuthToken(u));
2025-01-09 12:21:06 +08:00
cookie.setDomain(service.authTokenCookieDomain());
2025-01-03 14:12:02 +08:00
cookie.setPath(AuthModel::CookiePath);
cookie.setExpires(Wt::WDateTime());
2025-01-02 23:02:37 +08:00
setCookie(cookie);
2025-01-09 22:23:03 +08:00
app->insertCookie(cookie.value(), u);
2025-01-02 23:02:37 +08:00
}
2025-01-09 22:23:03 +08:00
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");
2025-01-09 19:16:00 +08:00
}
2025-01-09 22:23:03 +08:00
}
if (m_loginedRedirectUrl.empty()) {
2025-01-09 19:16:00 +08:00
setInternalPath("/", true);
} else {
redirect(m_loginedRedirectUrl);
}
2024-12-01 15:10:25 +08:00
} else {
2025-01-04 14:04:27 +08:00
if (m_navigationBar != nullptr) {
m_loginPage = m_navigationBar->removeLoginItem();
}
2025-01-09 22:23:03 +08:00
if (token != nullptr) {
app->removeCookie(*token);
}
2025-01-04 14:04:27 +08:00
LOG(info) << "user logged out, internal path: " << internalPath();
if (internalPath() == "/wt/login") {
handlePathChange(internalPath());
}
2024-12-01 15:10:25 +08:00
}
2025-01-03 22:17:45 +08:00
doJavaScript("if (window.updateAuthStatus) window.updateAuthStatus();");
2024-12-01 15:10:25 +08:00
}
void Application::handlePathChange(const std::string &path) {
LOG(info) << "handlePathChange: " << path;
2025-01-03 04:51:08 +08:00
if (m_root == nullptr) {
LOG(error) << "root container is null.";
return;
}
2024-12-21 23:08:38 +08:00
if (path.starts_with("/wt/login")) {
if (m_session->login().loggedIn()) {
2025-01-03 04:51:08 +08:00
LOG(info) << "already logged in.";
2025-01-05 00:33:19 +08:00
m_root->clear();
auto p = m_root->addNew<RedirectPage>();
2025-01-05 14:23:02 +08:00
p->setMessage("您已经登录...");
2024-12-21 23:08:38 +08:00
} else {
if (m_loginPage) {
m_root->clear();
m_loginPageRef = m_root->addWidget(std::move(m_loginPage));
2025-01-03 00:02:00 +08:00
m_loginPageRef->addStyleClass("bulma-m-auto bulma-container");
2024-12-21 23:08:38 +08:00
}
}
2024-12-02 01:18:57 +08:00
} else {
2024-12-21 23:08:38 +08:00
if (m_loginPageRef != nullptr && m_loginPageRef->parent() == m_root) {
m_loginPage = m_root->removeWidget(m_loginPageRef);
}
if (path.starts_with("/wt/register")) {
} else if (path.starts_with("/wt/visitor/analysis")) {
m_root->clear();
2025-01-08 15:01:54 +08:00
auto p = m_root->addNew<VisitorRecordsPage>(*m_session);
p->addStyleClass("bulma-is-flex-grow-1");
2025-01-05 00:33:19 +08:00
} else if (path.starts_with("/wt/redirect")) {
m_root->clear();
auto p = m_root->addNew<RedirectPage>();
2025-01-05 14:23:02 +08:00
p->setMessage("这是一个跳转测试....");
p->setRedirect("https://amass.fun");
2024-12-21 23:08:38 +08:00
} else {
m_root->clear();
m_root->addNew<HomePage>();
}
2024-12-01 15:10:25 +08:00
}
}
2024-12-01 20:01:13 +08:00
Server::Server(uint16_t port, const std::string &applicationRoot, const std::string &documentRoot) {
2024-12-01 15:10:25 +08:00
try {
std::vector<std::string> args;
args.push_back(std::format("--docroot={};/resources", documentRoot));
2024-12-01 20:01:13 +08:00
args.push_back(std::format("--approot={}/resources", applicationRoot));
2024-12-01 15:10:25 +08:00
args.push_back(std::format("--http-listen=127.0.0.1:{}", port));
initializeAuthenticationService();
2024-12-01 20:01:13 +08:00
m_server = std::make_unique<Wt::WServer>(std::format("{}/resources", applicationRoot), args);
2024-12-01 15:10:25 +08:00
m_server->addEntryPoint(Wt::EntryPointType::Application,
std::bind(&Server::createApplication, this, std::placeholders::_1, false));
m_server->addEntryPoint(Wt::EntryPointType::WidgetSet,
std::bind(&Server::createApplication, this, std::placeholders::_1, true), "/wt/app.js");
2025-01-03 22:17:45 +08:00
m_server->addResource(std::make_shared<AuthenticationResource>(), "/api/v1/auth/${tag}");
2024-12-01 15:10:25 +08:00
m_server->addResource(std::make_shared<PlaintextResource>(), "/plaintext");
m_server->start();
} catch (const std::exception &e) {
LOG(error) << e.what();
}
}
std::unique_ptr<Wt::WApplication> Server::createApplication(const Wt::WEnvironment &env, bool embedded) {
return std::make_unique<Application>(env, embedded);
}
Server::~Server() {
}
void Server::initializeAuthenticationService() {
m_authService = std::make_unique<Wt::Auth::AuthService>();
2024-12-24 23:46:20 +08:00
m_authService->setEmailVerificationEnabled(true);
m_authService->setEmailVerificationRequired(true);
2025-01-03 14:12:02 +08:00
m_authService->setAuthTokensEnabled(true);
2024-12-01 15:10:25 +08:00
m_passwordService = std::make_unique<Wt::Auth::PasswordService>(*m_authService);
auto verifier = std::make_unique<Wt::Auth::PasswordVerifier>();
verifier->addHashFunction(std::make_unique<Wt::Auth::BCryptHashFunction>(7));
m_passwordService->setVerifier(std::move(verifier));
m_passwordService->setPasswordThrottle(std::make_unique<Wt::Auth::AuthThrottle>());
m_passwordService->setStrengthValidator(std::make_unique<Wt::Auth::PasswordStrengthValidator>());
}
2024-12-26 23:10:41 +08:00
Wt::Auth::AuthService &Server::authService() {
2024-12-01 15:10:25 +08:00
return *m_authService;
}
const Wt::Auth::PasswordService &Server::passwordService() {
return *m_passwordService;
}
2025-01-03 14:12:02 +08:00
2025-01-09 22:23:03 +08:00
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) {
2025-01-03 14:12:02 +08:00
if (!m_cookies.contains(cookie)) {
2025-01-09 22:23:03 +08:00
m_cookies.insert_or_assign(cookie, user);
}
}
void Server::removeCookie(const std::string &cookie) {
if (m_cookies.contains(cookie)) {
m_cookies.erase(cookie);
2025-01-03 14:12:02 +08:00
}
}
Wt::Http::Cookie Server::updateCookie(const std::string &oldCookie, const Wt::Auth::AuthTokenResult &result, bool secure) {
Wt::Http::Cookie cookie(m_authService->authTokenCookieName());
cookie.setPath(AuthModel::CookiePath);
2025-01-09 12:21:06 +08:00
cookie.setDomain(m_authService->authTokenCookieDomain());
2025-01-03 14:12:02 +08:00
cookie.setSecure(secure);
if (result.state() == Wt::Auth::AuthTokenState::Invalid) {
if (m_cookies.contains(oldCookie)) {
m_cookies.erase(oldCookie);
}
cookie.setMaxAge(std::chrono::seconds(0));
cookie.setValue("");
} else {
auto newToken = result.newToken();
if (!newToken.empty()) {
if (m_cookies.contains(oldCookie)) { // 勾选了记住我
m_cookies.erase(oldCookie);
cookie.setMaxAge(std::chrono::seconds(result.newTokenValidity()));
2025-01-09 22:23:03 +08:00
m_cookies.insert_or_assign(newToken, result.user());
2025-01-03 14:12:02 +08:00
} else { // 只在会话期间有效
cookie.setExpires(Wt::WDateTime());
}
cookie.setValue(newToken);
}
}
return cookie;
}
2024-12-01 15:10:25 +08:00
} // namespace WebToolkit