refactoring

This commit is contained in:
Wieczorek Bartosz 2017-03-05 18:06:04 +01:00
parent 8cd3957ad4
commit 963d829464
8 changed files with 275 additions and 210 deletions

View File

@ -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"(

129
src/app/Application.cpp Normal file
View File

@ -0,0 +1,129 @@
#include "Application.hpp"
#include <Wt/WApplication>
#include <Wt/WEnvironment>
#include <eedb/auth/PgUserAuth.hpp>
#include <eedb/auth/Services.hpp>
#include <eedb/db/connection.hpp>
#include <eedb/widgets/AuthWidget.hpp>
#include <eedb/widgets/Theme.hpp>
#include <memory>
#include <Wt/WApplication>
#include <Wt/WBorderLayout>
#include <Wt/WContainerWidget>
#include <Wt/WEnvironment>
#include <Wt/WLineEdit>
#include <Wt/WMessageBox>
#include <Wt/WNavigationBar>
#include <Wt/WPopupMenu>
#include <Wt/WServer>
#include <Wt/WStackedWidget>
#include <Wt/WText>
#include <Wt/WTextArea>
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));
}
}

17
src/app/Application.hpp Normal file
View File

@ -0,0 +1,17 @@
#include <memory>
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;
};
}

View File

@ -1,4 +1,6 @@
set(SOURCES
Application.cpp
DatabaseConnection.cpp
Session.cpp
main.cpp)

View File

@ -0,0 +1,45 @@
#include "DatabaseConnection.hpp"
#include <Wt/WEnvironment>
#include <Wt/WServer>
#include <eedb/db/connection.hpp>
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);
}
}

View File

@ -0,0 +1,17 @@
#include <memory>
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;
};
}

View File

@ -1,186 +1,22 @@
#include <eedb/auth/PgUserAuth.hpp>
#include "Application.hpp"
#include "DatabaseConnection.hpp"
#include <eedb/auth/Services.hpp>
#include <eedb/db/connection.hpp>
#include <eedb/widgets/AuthWidget.hpp>
#include <eedb/widgets/Theme.hpp>
#include <memory>
#include <Wt/WApplication>
#include <Wt/WBorderLayout>
#include <Wt/WContainerWidget>
#include <Wt/WEnvironment>
#include <Wt/WLineEdit>
#include <Wt/WMessageBox>
#include <Wt/WNavigationBar>
#include <Wt/WPopupMenu>
#include <Wt/WServer>
#include <Wt/WStackedWidget>
#include <Wt/WText>
#include <Wt/WTextArea>
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("<p>Showing Help: {1}</p>").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;

View File

@ -17,7 +17,6 @@
#include <iostream>
#include <random>
#include <string>
//#include <string_view>
#include <Wt/Auth/AuthService>
#include <Wt/Auth/Dbo/AuthInfo>
@ -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;
}