From 963d829464220a2a55511f3b7a751c5bfc24be26 Mon Sep 17 00:00:00 2001 From: Wieczorek Bartosz Date: Sun, 5 Mar 2017 18:06:04 +0100 Subject: [PATCH] refactoring --- sql/schema.sql | 25 ++++- src/app/Application.cpp | 129 ++++++++++++++++++++++++ src/app/Application.hpp | 17 ++++ src/app/CMakeLists.txt | 2 + src/app/DatabaseConnection.cpp | 45 +++++++++ src/app/DatabaseConnection.hpp | 17 ++++ src/app/main.cpp | 174 +-------------------------------- src/eedb/auth/PgUserAuth.cpp | 76 +++++++------- 8 files changed, 275 insertions(+), 210 deletions(-) create mode 100644 src/app/Application.cpp create mode 100644 src/app/Application.hpp create mode 100644 src/app/DatabaseConnection.cpp create mode 100644 src/app/DatabaseConnection.hpp diff --git a/sql/schema.sql b/sql/schema.sql index 3d7a5cf..ff8d8e4 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -101,18 +101,23 @@ BEGIN end $$ language 'plpgsql'; + create table "user" ( "uid" serial not null, "group" int not null default default_users_group_id(), - "status" int not null default 0, "full_name" text not null, "created" timestamp DEFAULT now() not null, "updated" timestamp, "config" json not null DEFAULT ('{}'), - "auth_info_id" bigint, constraint "pk_user_uid" primary key ("uid") ); create trigger "update_user_updated" before update on "user" FOR EACH ROW EXECUTE PROCEDURE last_update_column(); +comment on table "user" is ''; +comment on column "user"."uid" is ''; +comment on column "user"."group" is ''; +comment on column "user"."full_name" is ''; +comment on column "user"."created" is ''; +comment on column "user"."config" is ''; create table "stat"( @@ -209,6 +214,11 @@ create table "auth_identity" ( constraint "pk_auth_identity_id" primary key("id"), constraint "fk_auth_identity_auth_info" foreign key ("auth_info_id") references "auth_info" ("id") on delete cascade deferrable initially deferred ); +comment on table "auth_identity" is ''; +comment on column "auth_identity"."id" is ''; +comment on column "auth_identity"."auth_info_id" is ''; +comment on column "auth_identity"."provider" is ''; +comment on column "auth_identity"."identity" is ''; create table "auth_token" ( @@ -220,6 +230,11 @@ create table "auth_token" ( constraint "fk_auth_token_auth_info_id" foreign key ("auth_info_id") references "auth_info" ("id") on delete cascade deferrable initially deferred ); create index "ix_auth_token_value" ON "auth_token" ("value"); +comment on table "auth_token" is ''; +comment on column "auth_token"."id" is ''; +comment on column "auth_token"."auth_info_id" is ''; +comment on column "auth_token"."value" is ''; +comment on column "auth_token"."expires" is ''; create table "user_audit_action" ( @@ -239,14 +254,18 @@ create table "user_audit" ( "user_id" int not null, "action_id" int not null, "data" jsonb, - "_when" timestamp DEFAULT(now()), + "when_happened" timestamp DEFAULT(now()), constraint "pk_user_history_id" primary key ("id"), constraint "fk_user_history_user_uid" foreign key ("user_id") references "user"("uid") on delete cascade deferrable initially deferred, constraint "fk_user_history_user_action" foreign key ("action_id") references "user_audit_action"("id") on delete cascade deferrable initially deferred ); create index "ix_user_history_data" ON "user_audit" ((("data" ->> 'status')::text)) WHERE ("data" ->> 'status') IS not null; comment on table "user_audit" IS 'saves user actions like login/logout'; +comment on column "user_audit"."id" is ''; +comment on column "user_audit"."user_id" is ''; +comment on column "user_audit"."action_id" is ''; comment on column "user_audit"."data" is 'data column contains information about taken action (if login was successful? if not, from what ip this action was taken?)'; +comment on column "user_audit"."when_happened" is ''; create table "user_groups"( diff --git a/src/app/Application.cpp b/src/app/Application.cpp new file mode 100644 index 0000000..c176ae4 --- /dev/null +++ b/src/app/Application.cpp @@ -0,0 +1,129 @@ +#include "Application.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class AuthApplication : public Wt::WApplication { + public: + AuthApplication(const Wt::WEnvironment & env, std::unique_ptr< eedb::db::PgConnection > _database) + : Wt::WApplication(env), _dbConnection(std::move(_database)) { + _login.changed().connect(this, &AuthApplication::authEvent); + _userDatabase = std::make_unique< eedb::auth::PgUserAuth >(*_dbConnection, env); + + root()->addStyleClass("container"); + useStyleSheet("/resources/style.css"); + + setTheme(eedb::BootstrapTheme(this).create()); + + _authWidget = new eedb::AuthWidget(eedb::auth::Services{}, *_userDatabase, _login); + root()->addWidget(_authWidget); + } + + void authEvent() { + using namespace Wt; + + if(_login.loggedIn()) { + Wt::WContainerWidget * container = root(); + setInternalPath("/app"); + root()->removeStyleClass("container"); + + // Create a navigation bar with a link to a web page. + auto navigation = new Wt::WNavigationBar(container); + navigation->setTitle("eedb", "http://eedb.pl"); + navigation->setResponsive(true); + + auto contentsStack = new Wt::WStackedWidget(container); + contentsStack->addStyleClass("contents"); + + // Setup a Left-aligned menu. + Wt::WMenu * mainTabs = new Wt::WMenu(contentsStack, container); + mainTabs->addItem("Home"); + navigation->addMenu(mainTabs); + + // Setup a Right-aligned menu. + // Create a popup submenu for the Help menu. + auto popup = new Wt::WPopupMenu(); + popup->addItem("Contents"); + + auto help = new Wt::WMenuItem("Help"); + help->setMenu(popup); + + auto rightMenu = new Wt::WMenu(); + rightMenu->addItem(help); + navigation->addMenu(rightMenu, Wt::AlignRight); + + // Setup a Right-aligned menu. + // Create a popup submenu for the Help menu. + auto sessionMenuPopup = new Wt::WPopupMenu(); + sessionMenuPopup->addItem("Logout"); + sessionMenuPopup->itemSelected().connect([=](auto...) { this->_authWidget->login().logout(); }); + + auto sessionItem = new Wt::WMenuItem("Session"); + sessionItem->setMenu(sessionMenuPopup); + + auto sessionMenu = new Wt::WMenu(); + sessionMenu->addItem(sessionItem); + navigation->addMenu(sessionMenu, Wt::AlignRight); + + // Add a Search control. + // navigation->addSearch(edit, Wt::AlignRight); + + Wt::WBorderLayout * layout = new Wt::WBorderLayout(); + container->setLayout(layout); + + Wt::WStackedWidget * contents = new Wt::WStackedWidget(); + + layout->addWidget(navigation, Wt::WBorderLayout::North); + + layout->addWidget(new Wt::WText("West"), Wt::WBorderLayout::West); + + auto item = new Wt::WText(Wt::WString("East")); + item->setStyleClass("green-box"); + layout->addWidget(item, Wt::WBorderLayout::East); + + item = new Wt::WText(Wt::WString("South")); + item->setStyleClass("green-box"); + layout->addWidget(item, Wt::WBorderLayout::South); + + layout->addWidget(contents, Wt::WBorderLayout::Center); + } else { +// root()->clear(); + root()->addWidget(_authWidget); + Wt::log("notice") << "User logged out."; + } + } + + private: + std::unique_ptr< eedb::db::PgConnection > _dbConnection; + std::unique_ptr< eedb::auth::PgUserAuth > _userDatabase; + Wt::Auth::Login _login; + eedb::AuthWidget * _authWidget; +}; + +namespace eedb { + +Wt::WApplication * Application::create(const Wt::WEnvironment & env, std::unique_ptr< eedb::db::PgConnection > connection) const { + return new AuthApplication(env, std::move(connection)); +} +} diff --git a/src/app/Application.hpp b/src/app/Application.hpp new file mode 100644 index 0000000..4aac96c --- /dev/null +++ b/src/app/Application.hpp @@ -0,0 +1,17 @@ +#include + +namespace Wt { +class WApplication; +class WEnvironment; +} + +namespace eedb::db { +class PgConnection; +} + +namespace eedb { +class Application { + public: + Wt::WApplication * create(const Wt::WEnvironment & env, std::unique_ptr< eedb::db::PgConnection > connection) const; +}; +} diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index bdcbbb0..810a1ac 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -1,4 +1,6 @@ set(SOURCES + Application.cpp + DatabaseConnection.cpp Session.cpp main.cpp) diff --git a/src/app/DatabaseConnection.cpp b/src/app/DatabaseConnection.cpp new file mode 100644 index 0000000..eff0417 --- /dev/null +++ b/src/app/DatabaseConnection.cpp @@ -0,0 +1,45 @@ +#include "DatabaseConnection.hpp" + +#include +#include + +#include + +namespace eedb { +std::unique_ptr< db::PgConnection > eedb::DbConnection::create(const Wt::WEnvironment & env) const { + auto config = std::make_shared< sqlpp::postgresql::connection_config >(); + + auto readString = [&env](auto && name, auto & val, auto def) { + if(!env.server()->readConfigurationProperty(name, val)) { + val = def; + } + }; + + auto readInt = [&env](auto && name, auto & val, auto def) { + std::string tmp; + if(env.server()->readConfigurationProperty(name, tmp)) { + val = std::atoi(tmp.c_str()); + } else { + val = def; + } + }; + + auto readBool = [&env](auto && name, auto & val, auto def) { + std::string tmp; + if(env.server()->readConfigurationProperty(name, tmp)) { + val = tmp == "true"; + } else { + val = def; + } + }; + + readString("db_host", config->host, "localhost"); + readString("db_user", config->user, "postgres"); + readString("db_password", config->password, "postgres"); + readString("db_name", config->dbname, "eedb"); + readInt("db_port", config->port, 5432); + readBool("db_debug", config->debug, true); + + return std::make_unique< db::PgConnection >(config); +} +} diff --git a/src/app/DatabaseConnection.hpp b/src/app/DatabaseConnection.hpp new file mode 100644 index 0000000..898b1d6 --- /dev/null +++ b/src/app/DatabaseConnection.hpp @@ -0,0 +1,17 @@ +#include + +namespace Wt { +class WEnvironment; +} + +namespace eedb::db { +class PgConnection; +} + +namespace eedb { + +class DbConnection { + public: + std::unique_ptr< db::PgConnection > create(const Wt::WEnvironment & env) const; +}; +} diff --git a/src/app/main.cpp b/src/app/main.cpp index 63ec8a8..59c8f7b 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -1,186 +1,22 @@ -#include +#include "Application.hpp" +#include "DatabaseConnection.hpp" + #include #include -#include -#include - -#include #include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include - -class AuthApplication : public Wt::WApplication { - public: - auto getDbConfig(const Wt::WEnvironment & env) { - auto config = std::make_unique< sqlpp::postgresql::connection_config >(); - - auto readString = [&env](auto && name, auto & val, auto def) { - if(!env.server()->readConfigurationProperty(name, val)) { - val = def; - } - }; - - auto readInt = [&env](auto && name, auto & val, auto def) { - std::string tmp; - if(env.server()->readConfigurationProperty(name, tmp)) { - val = std::atoi(tmp.c_str()); - } else { - val = def; - } - }; - - auto readBool = [&env](auto && name, auto & val, auto def) { - std::string tmp; - if(env.server()->readConfigurationProperty(name, tmp)) { - val = tmp == "true"; - } else { - val = def; - } - }; - - readString("db_host", config->host, "localhost"); - readString("db_user", config->user, "postgres"); - readString("db_password", config->password, "postgres"); - readString("db_name", config->dbname, "eedb"); - readInt("db_port", config->port, 5432); - readBool("db_debug", config->debug, true); - return config; - } - - AuthApplication(const Wt::WEnvironment & env) : Wt::WApplication(env) { - _login.changed().connect(this, &AuthApplication::authEvent); - - _dbConnection = std::make_unique< eedb::db::PgConnection >(getDbConfig(env)); - _userDatabase = std::make_unique< eedb::auth::PgUserAuth >(*_dbConnection, env); - - root()->addStyleClass("container"); - useStyleSheet("/resources/style.css"); - - setTheme(eedb::BootstrapTheme(this).create()); - - _authWidget = new eedb::AuthWidget(eedb::auth::Services{}, *_userDatabase, _login); - root()->addWidget(_authWidget); - } - - void authEvent() { - using namespace Wt; - - if(_login.loggedIn()) { - Wt::WContainerWidget * container = root(); - setInternalPath("/app"); - root()->removeStyleClass("container"); - - // Create a navigation bar with a link to a web page. - auto navigation = new Wt::WNavigationBar(container); - navigation->setTitle("eedb", "http://eedb.pl"); - navigation->setResponsive(true); - - auto contentsStack = new Wt::WStackedWidget(container); - contentsStack->addStyleClass("contents"); - - // Setup a Left-aligned menu. - Wt::WMenu * mainTabs = new Wt::WMenu(contentsStack, container); - mainTabs->addItem("Home"); - navigation->addMenu(mainTabs); - - // Setup a Right-aligned menu. - // Create a popup submenu for the Help menu. - auto popup = new Wt::WPopupMenu(); - popup->addItem("Contents"); - popup->addItem("Index"); - popup->addSeparator(); - popup->addItem("About"); - popup->itemSelected().connect([](auto && item, auto...) { - auto messageBox = - new Wt::WMessageBox("Help", Wt::WString::fromUTF8("

Showing Help: {1}

").arg(item->text()), Wt::Information, Wt::Ok); - messageBox->buttonClicked().connect([messageBox = messageBox](auto...) { delete messageBox; }); - messageBox->show(); - }); - auto help = new Wt::WMenuItem("Help"); - help->setMenu(popup); - - auto rightMenu = new Wt::WMenu(); - rightMenu->addItem(help); - navigation->addMenu(rightMenu, Wt::AlignRight); - - // Setup a Right-aligned menu. - // Create a popup submenu for the Help menu. - auto sessionMenuPopup = new Wt::WPopupMenu(); - sessionMenuPopup->addItem("Logout"); - sessionMenuPopup->itemSelected().connect([=](auto...) { - this->_authWidget->login().logout(); - std::cout << "logout"; - }); - - auto sessionItem = new Wt::WMenuItem("Session"); - sessionItem->setMenu(sessionMenuPopup); - - auto sessionMenu = new Wt::WMenu(); - sessionMenu->addItem(sessionItem); - navigation->addMenu(sessionMenu, Wt::AlignRight); - - // Add a Search control. - // navigation->addSearch(edit, Wt::AlignRight); - - Wt::WBorderLayout * layout = new Wt::WBorderLayout(); - container->setLayout(layout); - - Wt::WStackedWidget * contents = new Wt::WStackedWidget(); - - layout->addWidget(navigation, Wt::WBorderLayout::North); - - layout->addWidget(new Wt::WText("West"), Wt::WBorderLayout::West); - - auto item = new Wt::WText(Wt::WString("East")); - item->setStyleClass("green-box"); - layout->addWidget(item, Wt::WBorderLayout::East); - - item = new Wt::WText(Wt::WString("South")); - item->setStyleClass("green-box"); - layout->addWidget(item, Wt::WBorderLayout::South); - - layout->addWidget(contents, Wt::WBorderLayout::Center); - } else { - Wt::Auth::AuthWidget * authWidget = new Wt::Auth::AuthWidget(*eedb::auth::Services::authService(), *_userDatabase, _login); - root()->clear(); - authWidget->model()->addPasswordAuth(eedb::auth::Services::passwordService()); - authWidget->model()->addOAuth(eedb::auth::Services::oAuthServices()); - authWidget->setRegistrationEnabled(true); - root()->addWidget(authWidget); - - Wt::log("notice") << "User logged out."; - } - } - - private: - std::unique_ptr< eedb::db::PgConnection > _dbConnection; - std::unique_ptr< eedb::auth::PgUserAuth > _userDatabase; - Wt::Auth::Login _login; - eedb::AuthWidget * _authWidget; -}; Wt::WApplication * createApplication(const Wt::WEnvironment & env) { - return new AuthApplication(env); + auto dbConnection = eedb::DbConnection{}.create(env); + return eedb::Application{}.create(env, std::move(dbConnection)); } int main(int argc, char ** argv) { try { Wt::WServer server(argc, argv, WTHTTP_CONFIGURATION); - server.addEntryPoint(Wt::Application, createApplication); - eedb::auth::Services::configureAuth(); - server.run(); } catch(Wt::WServer::Exception & e) { std::cerr << e.what() << std::endl; diff --git a/src/eedb/auth/PgUserAuth.cpp b/src/eedb/auth/PgUserAuth.cpp index 375a23d..e8a117c 100644 --- a/src/eedb/auth/PgUserAuth.cpp +++ b/src/eedb/auth/PgUserAuth.cpp @@ -17,7 +17,6 @@ #include #include #include -//#include #include #include @@ -74,18 +73,18 @@ namespace { constexpr eedb::auth_info t_info; constexpr eedb::auth_token t_auth_token; - auto auth_info_identity = t_info.join(t_identity).on(t_info.id == t_identity.auth_info_id); - auto user_auth_info = t_user.join(t_info).on(t_info.user_uid == t_user.uid); - auto auth_token_info = user_auth_info.join(t_auth_token).on(t_auth_token.auth_info_id == t_info.id); + static const auto auth_info_identity = t_info.join(t_identity).on(t_info.id == t_identity.auth_info_id); + static const auto user_auth_info = t_user.join(t_info).on(t_info.user_uid == t_user.uid); + static const auto auth_token_info = user_auth_info.join(t_auth_token).on(t_auth_token.auth_info_id == t_info.id); - const auto select_login_action_id = select(t_user_action.id) // - .from(t_user_action) // - .where(t_user_action.name == "login") // - .limit(1u); - const auto select_logout_action_id = select(t_user_action.id) // - .from(t_user_action) // - .where(t_user_action.name == "logout") // - .limit(1u); + static const auto select_login_action_id = select(t_user_action.id) // + .from(t_user_action) // + .where(t_user_action.name == "login") // + .limit(1u); + static const auto select_logout_action_id = select(t_user_action.id) // + .from(t_user_action) // + .where(t_user_action.name == "logout") // + .limit(1u); } PgUserAuth::~PgUserAuth() {} @@ -113,12 +112,12 @@ User PgUserAuth::findWithIdentity(const std::string & provider, const Wt::WStrin std::transform(_identity.begin(), _identity.end(), _identity.begin(), ::tolower); } - auto identity_eq = t_identity.identity == _identity; - auto provider_eq = t_identity.provider == provider; + const auto identity_eq = t_identity.identity == _identity; + const auto provider_eq = t_identity.provider == provider; - auto result = db(select(t_info.user_uid) // - .from(auth_info_identity) // - .where(provider_eq and identity_eq)); + const auto result = db(select(t_info.user_uid) // + .from(auth_info_identity) // + .where(provider_eq and identity_eq)); if(!result.empty()) { return User(std::to_string(result.front().user_uid), *this); @@ -384,16 +383,16 @@ User PgUserAuth::findWithAuthToken(const std::string & hash) const { int PgUserAuth::updateAuthToken(const User & user, const std::string & oldhash, const std::string & newhash) { // method called only after successful login using namespace std::chrono; - auto identity_id = db(select(t_info.id) // - .from(t_info) // - .where(t_info.user_uid == std::atoi(user.id().c_str()))); + const auto identity_id = db(select(t_info.id) // + .from(t_info) // + .where(t_info.user_uid == std::atoi(user.id().c_str()))); if(identity_id.empty()) return 0; - auto expires = db(sqlpp::postgresql::update(t_auth_token) // - .set(t_auth_token.value = newhash) - .where(t_auth_token.value == oldhash) - .returning(t_auth_token.expires)); + const auto expires = db(sqlpp::postgresql::update(t_auth_token) // + .set(t_auth_token.value = newhash) + .where(t_auth_token.value == oldhash) + .returning(t_auth_token.expires)); if(expires.empty()) return 0; @@ -413,14 +412,13 @@ int PgUserAuth::updateAuthToken(const User & user, const std::string & oldhash, t_user_history.action_id = select_login_action_id, // t_user_history.data = tao::json::to_string(data))); - auto now = sqlpp::time_point::_cpp_value_type::clock::now(); - auto diff = expires.front().expires.value() - now; - int seconds = std::chrono::duration_cast< duration< int > >(diff).count(); - return seconds; + const auto now = system_clock::now(); + const auto diff = expires.front().expires.value() - now; + return duration_cast< duration< int > >(diff).count(); } void PgUserAuth::setFailedLoginAttempts(const User & user, int count) { - auto getStatus = [count]() { return count ? "failed"s : "success"s; }; + const auto getStatus = [count]() { return count ? "failed"s : "success"s; }; const tao::json::value data{ {"status"s, getStatus()}, // @@ -445,7 +443,7 @@ inline std::string name_of(const T &) { } int PgUserAuth::failedLoginAttempts(const User & user) const { - auto res = + const auto res = db(select(count(t_user_history.id)) // .from(t_user_history) // .where(t_user_history.user_id == std::atoi(user.id().c_str()) and // @@ -463,17 +461,19 @@ int PgUserAuth::failedLoginAttempts(const User & user) const { void PgUserAuth::setLastLoginAttempt(const User &, const Wt::WDateTime &) {} Wt::WDateTime PgUserAuth::lastLoginAttempt(const User & user) const { - auto res = db(select(t_user_history._when) // - .from(t_user_history) // - .where(t_user_history.action_id == select_login_action_id and t_user_history.user_id == std::atoi(user.id().c_str())) // - .order_by(t_user_history.id.desc()) // - .limit(1u)); + using namespace std::chrono; + const auto res = + db(select(t_user_history._when) // + .from(t_user_history) // + .where(t_user_history.action_id == select_login_action_id and t_user_history.user_id == std::atoi(user.id().c_str())) // + .order_by(t_user_history.id.desc()) // + .limit(1u)); if(res.empty()) return {}; - auto lastLogin = Wt::WDateTime(); - auto time = res.front()._when.value(); - auto systime = std::chrono::system_clock::to_time_t(time); + const auto time = res.front()._when.value(); + const auto systime = system_clock::to_time_t(time); + auto lastLogin = Wt::WDateTime(); lastLogin.setTime_t(systime); return lastLogin; }