From b0cd59af1205cc0d9eff18a50f5fad95769b8f67 Mon Sep 17 00:00:00 2001 From: Bartosz Wieczorek Date: Mon, 5 Feb 2018 14:36:42 +0100 Subject: [PATCH] simplify sql model --- sql/schema.sql | 157 +----- src/eedb/auth/PgUserAuth.cpp | 102 ++-- src/eedb/auth/PgUserAuth.hpp | 18 +- src/eedb/data/AuthIdentity.hpp | 2 +- src/eedb/data/AuthToken.hpp | 15 +- src/eedb/data/Users.hpp | 3 + src/eedb/db/CMakeLists.txt | 1 - src/eedb/db/data/PgAuthToken.cpp | 79 ++- src/eedb/db/data/PgAuthToken.hpp | 4 +- src/eedb/db/data/PgUser.cpp | 9 +- src/eedb/db/data/PgUserAuth.cpp | 453 ------------------ src/eedb/db/data/PgUserAuth.hpp | 125 ----- src/eedb/db/data/PgUsers.cpp | 24 +- src/eedb/db/data/PgUsers.hpp | 1 + src/eedb/db/model/auth_info.h | 124 ++--- src/eedb/db/model/auth_token.h | 24 +- src/eedb/db/model/measurands.h | 92 ++++ src/eedb/db/model/metric_systems.h | 76 +++ src/eedb/db/model/user.h | 140 ------ src/eedb/db/model/user_audit.h | 26 +- src/eedb/db/model/user_inventory.h | 12 +- src/utils/Visitor.hpp | 8 + tests/unit/db/test_eedb_data_PgAuthToken.cpp | 106 ++-- tests/unit/db/test_eedb_data_PgAuthTokens.cpp | 97 ++-- tests/unit/db/test_eedb_data_PgUsers.cpp | 23 +- 25 files changed, 540 insertions(+), 1181 deletions(-) delete mode 100644 src/eedb/db/data/PgUserAuth.cpp delete mode 100644 src/eedb/db/data/PgUserAuth.hpp create mode 100644 src/eedb/db/model/measurands.h create mode 100644 src/eedb/db/model/metric_systems.h delete mode 100644 src/eedb/db/model/user.h create mode 100644 src/utils/Visitor.hpp diff --git a/sql/schema.sql b/sql/schema.sql index fd3d183..8c88aef 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -80,41 +80,26 @@ BEGIN end $$ -- $$ language 'plpgsql'; -- $$ - -create table "user" ( - "uid" serial not null, - "group" int not null default default_users_group_id(), - "status" integer DEFAULT 0 NOT NULL, - "full_name" text not null, - "created" timestamp DEFAULT now() not null, - "updated" timestamp, - "config" json not null DEFAULT ('{}'), - 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 "auth_info" ( "id" serial not null, - "user_uid" integer, + "group" int not null default default_users_group_id(), + "created" timestamp DEFAULT now() not null, + "updated" timestamp, + "config" json not null DEFAULT ('{}'), "password_hash" varchar(100) not null, "password_method" varchar(20) not null, "password_salt" varchar(20) not null, "email" varchar(256) not null, - "email_verified" boolean not null default false, - "email_token" varchar(64), - "email_token_expires" timestamp, - "email_token_role" integer, + "status" integer DEFAULT 0 NOT NULL, constraint "pk_auth_into_id" primary key("id"), - constraint "fk_auth_info_user_uid" foreign key ("user_uid") references "user" ("uid") on delete cascade deferrable initially deferred, constraint "ux_auth_info_email" unique ("email") ); +create trigger "update_auth_info_updated" before update on "auth_info" FOR EACH ROW EXECUTE PROCEDURE last_update_column(); +comment on table "auth_info" is ''; +comment on column "auth_info"."id" is ''; +comment on column "auth_info"."group" is ''; +comment on column "auth_info"."created" is ''; +comment on column "auth_info"."config" is ''; create table "auth_identity" ( @@ -137,6 +122,7 @@ create table "auth_token" ( "auth_info_id" bigint, "value" varchar(64) not null, "expires" timestamp not null, + "role" smallint not null, constraint "pk_auth_token_id" primary key("id"), constraint "fk_auth_token_auth_info_id" foreign key ("auth_info_id") references "auth_info" ("id") on delete cascade deferrable initially deferred ); @@ -148,7 +134,6 @@ comment on column "auth_token"."value" is ''; comment on column "auth_token"."expires" is ''; - create table "user_audit_action" ( "id" serial not null, "name" text, @@ -163,18 +148,18 @@ insert into "user_audit_action"("name") values ('login'), ('logout'); create table "user_audit" ( "id" serial not null, - "user_id" int not null, + "auth_info_id" int not null, "action_id" int not null, "data" jsonb, "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_auth_info_id" foreign key ("auth_info_id") references "auth_info"("id") 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"."auth_info_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 ''; @@ -198,7 +183,7 @@ create table "stat"( "created" timestamp DEFAULT now() not null, "updated" timestamp, constraint "pk_stat" primary key ("id"), - constraint "fk_stat_user" foreign key ("owner") references "user" ("uid") on delete cascade deferrable initially deferred, + constraint "fk_stat_user" foreign key ("owner") references "auth_info" ("id") on delete cascade deferrable initially deferred, constraint "fk_stat_primary_group" foreign key ("group") references "group" ("gid") on delete cascade deferrable initially deferred ); create trigger update_stat_last_update before update on stat FOR EACH ROW EXECUTE PROCEDURE last_update_column(); @@ -280,7 +265,7 @@ create table "category"( "parent_path" ltree, constraint "pk_category_uid" primary key ("id"), constraint "fk_category_parent_id" foreign key ("parent_id") references "category"("id") on delete cascade deferrable initially deferred, - constraint "fk_category_stat_owner" foreign key ("owner") references "user"("uid") deferrable initially immediate + constraint "fk_category_stat_owner" foreign key ("owner") references "auth_info"("id") deferrable initially immediate ) INHERITS (stat); create index "ix_category_parent_path" on "category" using GIST ("parent_path"); create unique index "ux_category_name" on "category" ( "parent_id", "name" ); @@ -366,7 +351,7 @@ create table "item"( "description" TEXT, constraint "pk_items" primary key ("id"), constraint "fk_item_category" foreign key ("category_id") references "category"("id") on delete cascade deferrable initially deferred, - constraint "fk_item_user" foreign key ("owner") REFERENCES "user"("uid") deferrable initially IMMEDIATE + constraint "fk_item_auth_info" foreign key ("owner") REFERENCES "auth_info"("id") deferrable initially IMMEDIATE ) INHERITS (stat); create index "ix_item_attributes" on "item" USING GIN ("attributes"); create unique index "ux_item" on "item"("name", "symbol"); @@ -384,16 +369,16 @@ comment on column "item"."description" is ''; create table "inventory"( "description" TEXT check(length(description)< 100000), constraint "pk_inventory" primary key ("id"), - constraint "fk_inventory_owner" foreign key ("owner") REFERENCES "user" ("uid") deferrable initially IMMEDIATE, + constraint "fk_inventory_owner" foreign key ("owner") REFERENCES "auth_info" ("id") deferrable initially IMMEDIATE, constraint "chk_inventory_name_length" check (length(name) < 100) ) INHERITS (stat); create trigger update_inventory_last_update before update on inventory FOR EACH ROW EXECUTE PROCEDURE last_update_column(); create table "user_inventory"( - "user_id" INTEGER not null REFERENCES "user" on DELETE CASCADE, + "auth_info_id" INTEGER not null REFERENCES "auth_info" on DELETE CASCADE, "inventory_id" INTEGER not null REFERENCES "inventory" on DELETE CASCADE, - constraint user_inventory_pk primary key ("inventory_id", "user_id") + constraint user_inventory_pk primary key ("inventory_id", "auth_info_id") ); @@ -435,106 +420,6 @@ create table "user_inventory"( -- create trigger update_shelfs_last_update before update on shelfs FOR EACH ROW EXECUTE PROCEDURE last_update_column(); -- create trigger update_inventory_operations_lats_update before update on inventory_operations FOR EACH ROW EXECUTE PROCEDURE last_update_column(); - -create OR REPLACE function objects_with_action (m_tab VARCHAR, m_action varchar, userid int) -RETURNS setof int AS $$ - -DECLARE r int; -DECLARE usergroups INT; -DECLARE groupsroot INT; -DECLARE tablename VARCHAR(255); - -BEGIN - SELECT "group" - FROM "user" - WHERE uid = userid - INTO usergroups; - - groupsroot := 1; - - FOR - r IN -execute 'select distinct obj.uid -from ' || m_tab ||' as obj -inner join implemented_action as ia -on ia.table_name ='''|| m_tab || ''' -and ia.action = '''|| m_action ||''' -and ((ia.status = 0) or (ia.status & obj.status <> 0)) -inner join action as ac -on ac.title = '''|| m_action ||''' -left outer join privilege as pr -on pr.related_table_name = '''|| m_tab || ''' -and pr.action = '''|| m_action ||''' -and ( -(pr.type = ''object'' and pr.related_object_uid = obj.uid) -or pr.type = ''global'' -or (pr.role = ''self'' and ' || userid || ' = obj.uid and '''|| m_tab || ''' = ''users'')) - WHERE ac.apply_object - AND ( - (' || usergroups || ' & ' || groupsroot || ' <> 0) - OR ( - ac.title = ''read'' - AND ( - (obj.unixperms & 4 <> 0) - OR ( - (obj.unixperms & 256 <> 0) - AND obj.owner = ' || userid || ' - ) - OR ( - (obj.unixperms & 32 <> 0) - AND (' || usergroups || ' & obj.group <> 0) - ) - ) - ) - OR ( - ac.title = ''write'' - AND ( - (obj.unixperms & 2 <> 0) - OR ( - (obj.unixperms & 128 <> 0) - AND obj.owner = ' || userid || ' - ) - OR ( - (obj.unixperms & 16 <> 0) - AND (' || usergroups || ' & obj.group <> 0) - ) - ) - ) - OR ( - ac.title = ''delete'' - AND ( - (obj.unixperms & 1 <> 0) - OR ((obj.unixperms & 64 <> 0) AND obj.owner = ' || userid || ') - OR ((obj.unixperms & 8 <> 0) AND (' || usergroups || ' & obj.group <> 0)) - ) - ) - OR ( - pr.role = ''user'' - AND pr.who = ' || userid || ' - ) - OR ( - pr.role = ''owner'' - AND obj.owner = ' || userid || ' - ) - OR ( - pr.role = ''owner_group'' - AND (obj.group & ' || usergroups || ' <> 0) - ) - OR ( - pr.role = ''group'' - AND (pr.who & ' || usergroups || ' <> 0) - ) - ) - OR pr.role = ''self'' ' - - LOOP - RETURN NEXT r; - end loop; -END $$ -LANGUAGE plpgsql ; - --- insert needed data into dataase - DO $$ DECLARE lastid int; diff --git a/src/eedb/auth/PgUserAuth.cpp b/src/eedb/auth/PgUserAuth.cpp index 5be0d1a..6f4614d 100644 --- a/src/eedb/auth/PgUserAuth.cpp +++ b/src/eedb/auth/PgUserAuth.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -41,13 +42,37 @@ struct TransactionGuard : public Wt::Auth::AbstractUserDatabase::Transaction { namespace eedb::auth { -PgUserAuth::PgUserAuth(std::unique_ptr users, const Wt::WEnvironment & env) : _env{env} { - this->setAuthService(eedb::auth::Services::authService()); -} +struct PgUserAuth::UserAuthPriv { + UserAuthPriv(std::unique_ptr< eedb::Users > users, const Wt::WEnvironment & env) : _users{std::move(users)}, _env{env} { + this->setAuthService(eedb::auth::Services::authService()); + } + + void setAuthService(Wt::Auth::AuthService * s) { + _authService = s; + } + + std::unique_ptr< eedb::Users > _users; + + std::optional< std::string > _email; + std::optional< std::string > _unverifiedEmail; + std::optional< std::string > _emailToken; + std::optional< std::chrono::time_point > _emailTokenExpire; + std::optional< Wt::Auth::PasswordHash > _password; + std::optional< std::string > _identity; + std::optional< int > _emailTokenRole; + + const Wt::WEnvironment & _env; + const Wt::Auth::AuthService * _authService; + int _in_transaction{0}; + bool _registrationStarted{false}; +}; + +PgUserAuth::PgUserAuth(std::unique_ptr< eedb::Users > users, const Wt::WEnvironment & env) + : _priv{spimpl::make_unique_impl< PgUserAuth::UserAuthPriv >(std::move(users), env)} {} Wt::Auth::User PgUserAuth::findWithId(const std::string & id) const { spdlog::get("default")->debug("searching user by id: '{}'...", id); - auto duser = users->findWith(std::atoi(id.c_str())); + auto duser = _priv->_users->findWith(std::atoi(id.c_str())); if(duser) { spdlog::get("default")->trace("\tuser exists"); @@ -60,11 +85,11 @@ Wt::Auth::User PgUserAuth::findWithId(const std::string & id) const { Wt::Auth::User PgUserAuth::findWithIdentity(const std::string & provider, const Wt::WString & identity) const { spdlog::get("default")->debug("searching user by identity/provider: '{}'/'{}'...", identity.toUTF8(), provider); auto _identity = identity.toUTF8(); - if(_authService && _authService->identityPolicy() == Wt::Auth::IdentityPolicy::EmailAddress) { + if(_priv->_authService && _priv->_authService->identityPolicy() == Wt::Auth::IdentityPolicy::EmailAddress) { std::transform(_identity.begin(), _identity.end(), _identity.begin(), ::tolower); } - auto duser = users->findWith(eedb::ConstAuthIdentity{_identity, provider}); + auto duser = _priv->_users->findWith(eedb::ConstAuthIdentity{_identity, provider}); if(duser) { spdlog::get("default")->trace("\tuser exists"); return Wt::Auth::User(std::to_string(duser->uid()), *this); @@ -76,7 +101,7 @@ Wt::Auth::User PgUserAuth::findWithIdentity(const std::string & provider, const Wt::Auth::User PgUserAuth::findWithEmailToken(const std::string & hash) const { spdlog::get("default")->debug("searching user by email token: '{}'...", hash); - auto duser = users->findWith(eedb::EmailToken{hash}); + auto duser = _priv->_users->findWith(eedb::EmailToken{hash}); if(duser) { spdlog::get("default")->trace("\tuser exists"); return Wt::Auth::User(std::to_string(duser->uid()), *this); @@ -88,7 +113,7 @@ Wt::Auth::User PgUserAuth::findWithEmailToken(const std::string & hash) const { Wt::Auth::User PgUserAuth::findWithEmail(const std::string & address) const { spdlog::get("default")->debug("searching user by email: '{}'...", address); - auto duser = users->findWith(eedb::Email{address}); + auto duser = _priv->_users->findWith(eedb::Email{address}); if(duser) { spdlog::get("default")->trace("\tuser exists"); return Wt::Auth::User(std::to_string(duser->uid()), *this); @@ -100,7 +125,7 @@ Wt::Auth::User PgUserAuth::findWithEmail(const std::string & address) const { // @registration void PgUserAuth::addIdentity(const Wt::Auth::User & user, const std::string & provider, const Wt::WString & identity) { spdlog::get("default")->debug("addind identity/provider: '{}'/'{}' for user_id'{}'...", identity.toUTF8(), provider, user.id()); - auto u = users->findWith(std::atoi(user.id().c_str())); + auto u = _priv->_users->findWith(std::atoi(user.id().c_str())); u->authIdentities().addIdentity(std::make_unique< eedb::ConstAuthIdentity >(identity.toUTF8(), provider)); } @@ -111,7 +136,7 @@ void PgUserAuth::addIdentity(const Wt::Auth::User & user, const std::string & pr Wt::WString PgUserAuth::identity(const Wt::Auth::User & user, const std::string & provider) const { spdlog::get("default")->trace("user{} identity: '{}'", provider); - auto opt_identity = users->findWith(std::atoi(user.id().c_str()))->authIdentities().findByProvider(provider); + auto opt_identity = _priv->_users->findWith(std::atoi(user.id().c_str()))->authIdentities().byProvider(provider); if(opt_identity) return Wt::WString::fromUTF8(std::string{opt_identity.value()->identity()}); @@ -120,16 +145,18 @@ Wt::WString PgUserAuth::identity(const Wt::Auth::User & user, const std::string void PgUserAuth::removeIdentity(const Wt::Auth::User & user, const std::string & provider) { spdlog::get("default")->trace("user{} remove provider: '{}'", user.id(), provider); - users->findWith(std::atoi(user.id().c_str()))->authIdentities().removeProvider(provider); + _priv->_users->findWith(std::atoi(user.id().c_str()))->authIdentities().removeProvider(provider); } Wt::Auth::User PgUserAuth::registerNew() { spdlog::get("default")->debug("registering new user"); + _priv->_registrationStarted = true; } void PgUserAuth::deleteUser(const Wt::Auth::User & user) { spdlog::get("default")->debug("delete user {}", user.id()); throw std::runtime_error("not implemented void PgUserAuth::deleteUser(const Wt::Auth::User & user)"); +// _priv->_users->removeUser(eedb::ConstAuthIdentity); // _userAuth.deleteUser({user.id()}); } @@ -153,55 +180,64 @@ void PgUserAuth::deleteUser(const Wt::Auth::User & user) { // @registration void PgUserAuth::setPassword(const Wt::Auth::User & user, const PasswordHash & password) { spdlog::get("default")->trace("user{} set password", user.id()); - _password = password; + _priv->_password = password; } PasswordHash PgUserAuth::password(const Wt::Auth::User & user) const { spdlog::get("default")->trace("user{} get password", user.id()); - auto password = users->findWith(std::atoi(user.id().c_str()))->authInfo().password(); + auto password = _priv->_users->findWith(std::atoi(user.id().c_str()))->authInfo().password(); return {password.function(), password.salt(), password.value()}; } bool PgUserAuth::setEmail(const Wt::Auth::User & user, const std::string & address) { -// spdlog::get("default")->trace("user{} set email {}", user.id(), address); -// return _userAuth.setEmail({user.id()}, address); + spdlog::get("default")->trace("user{} set email {}", user.id(), address); + if(_priv->_users->findWith(eedb::Email{address})) + return false; //user with given email exists + + _priv->_email = address; + return true; } std::string PgUserAuth::email(const Wt::Auth::User & user) const { -// spdlog::get("default")->trace("user{} get email", user.id()); -// return _userAuth.email({user.id()}); + spdlog::get("default")->trace("user{} get email", user.id()); + return std::string{_priv->_users->findWith(std::atoi(user.id().c_str()))->authInfo().email().address()}; } // @registration void PgUserAuth::setUnverifiedEmail(const Wt::Auth::User & user, const std::string & address) { -// spdlog::get("default")->trace("user{} set unverified email {}", user.id(), address); -// _userAuth.setUnverifiedEmail({user.id()}, address); + spdlog::get("default")->trace("user{} set unverified email {}", user.id(), address); + _priv->_unverifiedEmail = address; } // @registration void PgUserAuth::setEmailToken(const Wt::Auth::User & user, const Token & token, EmailTokenRole role) { -// spdlog::get("default")->trace("user{} set email token {}", user.id(), token.hash()); -// auto exp = ::date::floor<::std::chrono::milliseconds >(std::chrono::system_clock::from_time_t(token.expirationTime().toTime_t())); -// _userAuth.setEmailToken({user.id()}, {token.hash(), exp}, static_cast< int >(role)); + using namespace std::chrono; + spdlog::get("default")->trace("user{} set email token {}", user.id(), token.hash()); + auto exp = time_point_cast< microseconds >(system_clock::from_time_t(token.expirationTime().toTime_t())); + _priv->_emailToken = token.hash(); + _priv->_emailTokenExpire = exp; + _priv->_emailTokenRole = static_cast< int >(role); } // @registration std::string PgUserAuth::unverifiedEmail(const Wt::Auth::User & user) const { -// spdlog::get("default")->trace("user{} get unverified email", user.id()); -// return _userAuth.unverifiedEmail({user.id()}); + spdlog::get("default")->trace("user{} get unverified email", user.id()); + assert(_priv->_unverifiedEmail.has_value()); + return _priv->_unverifiedEmail.value(); } Token PgUserAuth::emailToken(const Wt::Auth::User & user) const { -// spdlog::get("default")->trace("user{} get email token", user.id()); -// auto tok = _userAuth.emailToken({user.id()}); -// if(!tok) -// return {}; -// auto exp = Wt::WDateTime(); -// auto time = tok->expirationTime(); -// auto systime = std::chrono::system_clock::to_time_t(time); -// exp.setTime_t(systime); -// return {tok->mhash(), exp}; + spdlog::get("default")->trace("user{} get email token", user.id()); + auto tok = _priv->_users->findWith(std::atoi(user.id().c_str()))->authTokens().find(AuthTokenRole::EmailToken); +// .emailToken({user.id()}); +// if(!tok) +// return {}; +// auto exp = Wt::WDateTime(); +// auto time = tok->expirationTime(); +// auto systime = std::chrono::system_clock::to_time_t(time); +// exp.setTime_t(systime); +// return {tok->mhash(), exp}; } EmailTokenRole PgUserAuth::emailTokenRole(const Wt::Auth::User & user) const { diff --git a/src/eedb/auth/PgUserAuth.hpp b/src/eedb/auth/PgUserAuth.hpp index 987543b..2603270 100644 --- a/src/eedb/auth/PgUserAuth.hpp +++ b/src/eedb/auth/PgUserAuth.hpp @@ -2,6 +2,8 @@ #include +#include + namespace eedb { class Users; } @@ -18,10 +20,6 @@ namespace eedb::auth { class PgUserAuth : public Wt::Auth::AbstractUserDatabase { public: - void setAuthService(Wt::Auth::AuthService * s) { - _authService = s; - } - PgUserAuth(std::unique_ptr< eedb::Users > users, const Wt::WEnvironment & env); Transaction * startTransaction() override; @@ -64,15 +62,7 @@ class PgUserAuth : public Wt::Auth::AbstractUserDatabase { void logout(const Wt::Auth::User & user); private: - std::unique_ptr< eedb::Users > users; - - mutable std::optional< std::string > _email; - mutable std::optional< std::string > _emailToken; - mutable std::optional< Wt::Auth::PasswordHash > _password; - mutable std::optional< std::string > _identity; - - const Wt::WEnvironment & _env; - const Wt::Auth::AuthService * _authService; - int _in_transaction{0}; + struct UserAuthPriv; + spimpl::unique_impl_ptr< UserAuthPriv > _priv; }; } // namespace eedb::auth diff --git a/src/eedb/data/AuthIdentity.hpp b/src/eedb/data/AuthIdentity.hpp index f614429..9d04e0e 100644 --- a/src/eedb/data/AuthIdentity.hpp +++ b/src/eedb/data/AuthIdentity.hpp @@ -50,7 +50,7 @@ class AuthIdentities { */ virtual AuthIdentity * addIdentity(std::unique_ptridentity); - virtual std::optional< AuthIdentity * > findByProvider(std::string_view provider) const = 0; + virtual std::optional< AuthIdentity * > byProvider(std::string_view provider) const = 0; virtual void removeProvider(std::string_view provider) const = 0; }; diff --git a/src/eedb/data/AuthToken.hpp b/src/eedb/data/AuthToken.hpp index ba6e1a0..5242a65 100644 --- a/src/eedb/data/AuthToken.hpp +++ b/src/eedb/data/AuthToken.hpp @@ -1,9 +1,16 @@ #pragma once -#include #include +#include +#include namespace eedb { +enum class AuthTokenRole : uint8_t { // + Auth, + EmailToken, + PasswordReset +}; + class AuthToken { public: virtual ~AuthToken() = default; @@ -19,10 +26,10 @@ class AuthTokens { public: virtual ~AuthTokens() = default; - virtual std::optional< AuthToken * > find(std::string_view token) const = 0; + virtual AuthToken * find(std::variant< std::string_view, AuthTokenRole > token) const = 0; - virtual void removeToken( std::string_view token) = 0; + virtual void removeToken(std::string_view token) = 0; - virtual AuthToken * addToken(std::string token) = 0; + virtual AuthToken * addToken(std::string token, AuthTokenRole role) = 0; }; } // namespace eedb diff --git a/src/eedb/data/Users.hpp b/src/eedb/data/Users.hpp index 4457925..30ff8bc 100644 --- a/src/eedb/data/Users.hpp +++ b/src/eedb/data/Users.hpp @@ -29,5 +29,8 @@ class Users { // adds new user into users virtual unique_ptr< User > addUser(unique_ptr< AuthInfo >, unique_ptr< AuthIdentity >) = 0; + + // remove user from database + virtual void removeUser(std::unique_ptr< AuthInfo > user) const = 0; }; } // namespace eedb diff --git a/src/eedb/db/CMakeLists.txt b/src/eedb/db/CMakeLists.txt index 34c4081..3847e7f 100644 --- a/src/eedb/db/CMakeLists.txt +++ b/src/eedb/db/CMakeLists.txt @@ -1,7 +1,6 @@ set(SOURCE data/PgCategory.cpp data/PgUser.cpp - data/PgUserAuth.cpp data/PgUsers.cpp data/PgAuthToken.cpp diff --git a/src/eedb/db/data/PgAuthToken.cpp b/src/eedb/db/data/PgAuthToken.cpp index b287b55..3f84765 100644 --- a/src/eedb/db/data/PgAuthToken.cpp +++ b/src/eedb/db/data/PgAuthToken.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include @@ -19,24 +21,23 @@ struct PgAuthToken::PgAuthTokenPriv { int64_t id; sqlpp::chrono::microsecond_point expire; std::string hash; + bool updated{false}; void update() { - auto & result = db(select(t_auth_token.expires, t_auth_token.value) // - .from(t_auth_token) // - .where(t_auth_token.id == id)) - .front(); - expire = result.expires; - hash = std::move(result.value); + if(!updated) { + auto & result = db(select(t_auth_token.expires, t_auth_token.value) // + .from(t_auth_token) // + .where(t_auth_token.id == id)) + .front(); + expire = result.expires; + hash = std::move(result.value); + } + updated = true; } }; PgAuthToken::PgAuthToken(db::PgConnection & con, int64_t uid) : _priv{spimpl::make_unique_impl< PgAuthTokenPriv >(con)} { - auto & result = con(select(sqlpp::all_of(t_auth_token)) // - .from(t_auth_token) // - .where(t_auth_token.id == uid)) - .front(); _priv->id = uid; - _priv->update(); } PgAuthToken::PgAuthToken(db::PgConnection & con, @@ -47,13 +48,16 @@ PgAuthToken::PgAuthToken(db::PgConnection & con, _priv->id = uid; _priv->expire = expirationtime; _priv->hash = std::move(hash); + _priv->updated = true; } std::string_view PgAuthToken::token() const { + _priv->update(); return _priv->hash; } bool PgAuthToken::expired() const { + _priv->update(); return _priv->expire < std::chrono::system_clock::now(); } @@ -74,15 +78,19 @@ struct PgAuthTokens::PgAuthTokensPriv { const User * _owner; std::map< std::string, std::unique_ptr< AuthToken > > _cache; + std::map< int, std::unique_ptr< AuthToken > > _emailTokensCache; - AuthToken * find(std::string token) { + AuthToken * findAuthToken(std::string token) { if(auto tok = _cache.find(token); tok != _cache.end()) { return tok->second.get(); } - auto token_data = _db(select(sqlpp::all_of(t_auth_token)) // - .from(t_auth_token) //. - .where(t_auth_token.value == token)); + auto token_data = _db(select(sqlpp::all_of(t_auth_token)) // + .from(t_auth_token) //. + .where( // + t_auth_token.value == token and // + t_auth_token.role == static_cast< int >(AuthTokenRole::Auth) and // + t_auth_token.auth_info_id == _owner->uid())); if(token_data.empty()) return nullptr; @@ -94,7 +102,29 @@ struct PgAuthTokens::PgAuthTokensPriv { return iterator->second.get(); } + AuthToken * findEmailToken(int role) { + if(auto tok = _emailTokensCache.find(role); tok != _emailTokensCache.end()) { + return tok->second.get(); + } + + auto token_data = _db(select(sqlpp::all_of(t_auth_token)) // + .from(t_auth_token) //. + .where( // + t_auth_token.role == role and // + t_auth_token.auth_info_id == _owner->uid())); + + if(token_data.empty()) + return nullptr; + + auto & data = token_data.front(); + const auto & iterator = + _emailTokensCache.emplace(role, std::make_unique< PgAuthToken >(_db, data.id, std::move(data.value), data.expires)).first; + + return iterator->second.get(); + } + AuthToken * add(std::string token) { + /// FIXME role using namespace std::chrono; auto exp = time_point_cast< microseconds >(system_clock::now() + hours{14 * 24}); @@ -102,7 +132,8 @@ struct PgAuthTokens::PgAuthTokensPriv { .set( // t_auth_token.value = token, // t_auth_token.auth_info_id = _owner->uid(), // - t_auth_token.expires = exp) // + t_auth_token.expires = exp, + t_auth_token.role = 0) // .returning(t_auth_token.id)) .front() .id; @@ -119,18 +150,22 @@ struct PgAuthTokens::PgAuthTokensPriv { PgAuthTokens::PgAuthTokens(db::PgConnection & con, const User * owner) : _priv{spimpl::make_unique_impl< PgAuthTokensPriv >(con, owner)} {} -std::optional< AuthToken * > PgAuthTokens::find(std::string_view token) const { - auto ptr = _priv->find(std::string{token}); - if(ptr) - return std::make_optional(ptr); - return {}; +AuthToken * PgAuthTokens::find(std::variant< std::string_view, AuthTokenRole > token) const { + auto authTokenVisitor = [impl = _priv.get()](std::string_view token) { + return impl->findAuthToken(std::string{token}); + }; + auto emailRoleVisitor = [impl = _priv.get()](AuthTokenRole role) { + return impl->findEmailToken(static_cast< int >(role)); + }; + + return std::visit(Visitor{authTokenVisitor, emailRoleVisitor}, token); } void PgAuthTokens::removeToken(std::string_view token) { _priv->remove(std::string{token}); } -AuthToken * PgAuthTokens::addToken(std::string hash) { +AuthToken * PgAuthTokens::addToken(std::string hash, AuthTokenRole role) { return _priv->add(std::move(hash)); } } // namespace eedb diff --git a/src/eedb/db/data/PgAuthToken.hpp b/src/eedb/db/data/PgAuthToken.hpp index 8633022..d6e42a3 100644 --- a/src/eedb/db/data/PgAuthToken.hpp +++ b/src/eedb/db/data/PgAuthToken.hpp @@ -37,11 +37,11 @@ class PgAuthTokens final : public AuthTokens { public: PgAuthTokens(db::PgConnection & con, const User * owner); - std::optional< AuthToken * > find(std::string_view token) const override; + AuthToken * find(std::variant< std::string_view, AuthTokenRole > token) const override; void removeToken(std::string_view token) override; - AuthToken * addToken(std::string hash) override; + AuthToken * addToken(std::string hash, AuthTokenRole role) override; private: struct PgAuthTokensPriv; diff --git a/src/eedb/db/data/PgUser.cpp b/src/eedb/db/data/PgUser.cpp index d1a0a42..2cdb79d 100644 --- a/src/eedb/db/data/PgUser.cpp +++ b/src/eedb/db/data/PgUser.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -23,16 +22,12 @@ namespace { SQLPP_ALIAS_PROVIDER(fk_auth_info_id); SQLPP_ALIAS_PROVIDER(user_status); -constexpr eedb::user t_user; constexpr eedb::auth_identity t_identity; constexpr eedb::auth_info t_info; -constexpr eedb::auth_token authToken; - -auto auth = t_user.join(t_info).on(t_user.uid == t_info.user_uid).join(t_identity).on(t_info.id == t_identity.auth_info_id); +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(authToken).on(authToken.auth_info_id == t_info.id); +auto auth_token_info = t_info.join(t_auth_token).on(t_auth_token.auth_info_id == t_info.id); } // namespace namespace eedb { diff --git a/src/eedb/db/data/PgUserAuth.cpp b/src/eedb/db/data/PgUserAuth.cpp deleted file mode 100644 index 900c41a..0000000 --- a/src/eedb/db/data/PgUserAuth.cpp +++ /dev/null @@ -1,453 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#include -#pragma GCC diagnostic pop - -#include -#include -#include - -using User = eedb::db::details::User; - -constexpr const char eedb::user_audit_::Data::_alias_t::_literal[]; -constexpr const char eedb::user_audit::_alias_t::_literal[]; - -// enum LoginActions { Login, Logout }; -// static std::array< std::string_view, 2 > UserActionNames = {{"login", "logout"}}; - -std::string RandomString(uint len) { - using namespace std; - srand(static_cast< unsigned int >(time(nullptr))); - experimental::string_view str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - string newstr; - std::size_t pos; - while(newstr.size() != len) { - pos = static_cast< std::size_t >((rand() % (static_cast< int >(str.size()) - 1))); - newstr += str.substr(pos, 1).to_string(); - } - return newstr; -} - -namespace eedb::db { - -namespace { - constexpr eedb::user t_user; - constexpr eedb::user_audit t_user_history; - constexpr eedb::user_action t_user_action; - constexpr eedb::auth_identity t_identity; - constexpr eedb::auth_info t_info; - constexpr eedb::auth_token t_auth_token; - - 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); - - 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); -} // namespace - -PgUserAuth::PgUserAuth(PgConnection & _db) : db{_db} {} - -User PgUserAuth::findWithId(const std::string & id) const { - const auto uid_eq = t_user.uid == std::atoi(id.c_str()); - - if(db(select(exists(select(t_user.uid) // - .from(t_user) // - .where(uid_eq)))) - .front() - .exists) { - return User(id); - } - return User(); -} - -User PgUserAuth::findWithIdentity(const std::string & provider, const std::string & identity) const { - const auto identity_eq = t_identity.identity == identity; - const auto provider_eq = t_identity.provider == provider; - - 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)); - } - return User(); -} - -User PgUserAuth::findWithEmailToken(const std::string & hash) const { - auto ret = db(select(t_user.uid) // - .from(user_auth_info) // - .where(t_info.email_token == hash)); - - if(ret.empty()) - return {}; - return {std::to_string(ret.front().uid)}; -} - -User PgUserAuth::findWithEmail(const std::string & address) const { - auto ret = db(select(t_info.user_uid) // - .from(t_info) // - .where(t_info.email == address and t_info.email_verified == true)); - - if(ret.empty()) - return {}; - return {std::to_string(ret.front().user_uid)}; -} - -void PgUserAuth::addIdentity(const User & user, const std::string & provider, const std::string & identity) { - const auto uid_eq = t_info.user_uid == std::atoi(user.id().c_str()); - const auto identity_eq = t_identity.identity == identity; - const auto provider_eq = t_identity.provider == provider; - - auto res = db(select(t_info.user_uid, t_info.id) // - .from(auth_info_identity) - .where(identity_eq and provider_eq and uid_eq)); - - if(!res.empty()) { -// Wt::log("error") << "cannot add identity " << provider << ":'" << identity << "': already exists"; - return; - } - - auto auth_info_id = db(select(t_info.id) - .from(t_info) // - .where(uid_eq)); - - if(auth_info_id.empty()) { - throw std::exception(); - } - - db(insert_into(t_identity) - .set(t_identity.identity = identity, // - t_identity.provider = provider, // - t_identity.auth_info_id = auth_info_id.front().id)); -} - -void PgUserAuth::setIdentity(const User & user, const std::string & provider, const std::string & identity) { - const auto uid = std::atoi(user.id().c_str()); - const auto provider_eq = t_identity.provider == provider; - - db(update(t_identity) // - .set(t_identity.identity = identity) // - .where(t_identity.auth_info_id == select(t_info.id).from(t_info).where(t_info.user_uid == uid) and provider_eq)); -} - -std::wstring PgUserAuth::identity(const User & user, const std::string & provider) const { - const int uid = std::atoi(user.id().c_str()); - const auto id_eq = t_info.user_uid == uid; - const auto provider_eq = t_identity.provider == provider; - - auto res = db(select(t_identity.identity) // - .from(auth_info_identity) // - .where(id_eq and provider_eq)); - - return boost::locale::conv::utf_to_utf< wchar_t >(res.front().identity.text); -} - -void PgUserAuth::removeIdentity(const User & user, const std::string & provider) { - const auto id_eq = t_identity.id == std::atoi(user.id().c_str()); - const auto provider_eq = t_identity.provider == provider; - - db(remove_from(t_identity).where(id_eq and provider_eq)); -} - -User PgUserAuth::registerNew() { - auto user_id = db(sqlpp::postgresql::insert_into(t_user) // - .set( // - t_user.full_name = RandomString(256), // - t_user.status = -1) - .returning(t_user.uid)) - .front() - .uid; - - db(insert_into(t_info).set( // - t_info.password_hash = "NONE", // - t_info.password_method = "NONE", // - t_info.password_salt = "NONE", // - t_info.user_uid = user_id, // - t_info.email = RandomString(20) + "@NONE.org")); - - return User{std::to_string(user_id)}; -} - -void PgUserAuth::deleteUser(const User & user) { - db(remove_from(t_info) // - .where(t_info.id == std::atoi(user.id().c_str()))); -} - -// User::Status PgUserAuth::status(const User & user) const { -// const auto id_eq = auth_identity.id == std::atoi(user.id().c_str()); - -// auto status = db(select(auth_info.status.as(user_status)) // -// .from(join) // -// .where(id_eq)) -// .front() -// .user_status; -// return status.value() == User::Status::Normal ? User::Status::Normal : User::Status::Disabled; -//} - -// void PgUserAuth::setStatus(const User & user, User::Status status) { -// db(update(auth_info) // -// .set(auth_info.status = static_cast< int >(status)) // -// .where(auth_info.id == std::atoi(user.id().c_str()))); -//} - -void PgUserAuth::setPassword(const User & user, details::PasswordHash password) { - db(update(t_info) - .set( // - t_info.password_hash = password.mvalue(), // - t_info.password_method = password.mfunction(), // - t_info.password_salt = password.msalt()) - .where(t_info.user_uid == std::atoi(user.id().c_str()))); -} - -std::optional PgUserAuth::password(const User & user) const { - const auto id_eq = t_info.user_uid == std::atoi(user.id().c_str()); - - auto row = db(select(t_info.password_hash, t_info.password_method, t_info.password_salt) - .from(t_info) // - .where(id_eq)); - if(row.empty()) - return {}; - auto & front = row.front(); - return details::PasswordHash{std::move(front.password_method), std::move(front.password_salt), std::move(front.password_hash)}; -} - -bool PgUserAuth::setEmail(const User & user, const std::string & address) { - const auto uid_eq = t_info.user_uid == std::atoi(user.id().c_str()); - - db(update(t_info) // - .set( // - t_info.email = address, - t_info.email_verified = true) // - .where(uid_eq)); - return true; -} - -std::string PgUserAuth::email(const User & user) const { - auto ret = db(select(t_info.email) // - .from(t_info) // - .where(t_info.user_uid == std::atoi(user.id().c_str()) and t_info.email_verified == true)); - if(ret.empty()) - return {}; - return std::move(ret.front().email); -} - -void PgUserAuth::setUnverifiedEmail(const User & user, const std::string & address) { - // orginal implementation of UserDatabase (provided by WT team) sets veryfied and unverified emails in - // different fields in database. So in order to verify email, they just copy the unverified_email to email and then - // set unverified_email to empty string (db don't allow empty strings as email*) - if(address.empty()) - db(update(t_info) // - .set(t_info.email_verified = true) // - .where(t_info.user_uid == std::atoi(user.id().c_str()))); - else - db(update(t_info) // - .set( // - t_info.email = address, // - t_info.email_verified = false) // - .where(t_info.user_uid == std::atoi(user.id().c_str()))); -} - -std::string PgUserAuth::unverifiedEmail(const User & user) const { - auto ret = db(select(t_info.email) // - .from(t_info) // - .where(t_info.user_uid == std::atoi(user.id().c_str()) and t_info.email_verified == false)); - if(ret.empty()) - return {}; - return std::move(ret.front().email); -} - -void PgUserAuth::setEmailToken(const User & user, details::Token token, int role) { - db(update(t_info) // - .set( // - t_info.email_token = token.mhash(), // - t_info.email_token_expires = token.expirationTime(), - t_info.email_token_role = static_cast< int >(role)) // - .where(t_info.user_uid == std::atoi(user.id().c_str()))); -} - -std::optional< details::Token > PgUserAuth::emailToken(const User & user) const { - auto ret = db(select(t_info.email_token, t_info.email_token_expires) // - .from(t_info) // - .where(t_info.user_uid == std::atoi(user.id().c_str()))); - if(ret.empty()) - return {}; - auto time = ret.front().email_token_expires.value(); - return details::Token{ret.front().email_token, time}; -} - -int PgUserAuth::emailTokenRole(const User & user) const { - auto ret = db(select(t_info.email_token_role) // - .from(t_info) // - .where(t_info.user_uid == std::atoi(user.id().c_str()))); - if(ret.empty()) - throw std::exception(); - auto val = ret.front().email_token_role; - return static_cast< int >(val.value()); -} - -void PgUserAuth::addAuthToken(const User & user, details::Token token) { - auto select_identity_id = select(t_info.id) // - .from(t_info) - .where(t_info.user_uid == std::atoi(user.id().c_str())) - .limit(1u); - db(insert_into(t_auth_token) // - .set( // - t_auth_token.auth_info_id = select_identity_id, // - t_auth_token.expires = std::chrono::system_clock::now() + ::sqlpp::chrono::days{14}, // - t_auth_token.value = token.mhash())); -} - -void PgUserAuth::removeAuthToken(const User &, const std::string & hash) { - db(remove_from(t_auth_token).where(t_auth_token.value == hash)); -} - -std::optional PgUserAuth::findWithAuthToken(const std::string & hash) const { - auto fullUser = t_auth_token // - .join(t_info) - .on(t_info.id == t_auth_token.auth_info_id); - - auto ret = db(select(t_info.user_uid) // - .from(fullUser) // - .where(t_auth_token.value == hash and t_auth_token.expires > std::chrono::system_clock::now())); - - if(ret.empty()) - return {}; - return details::User{std::to_string(ret.front().user_uid)}; -} - -int PgUserAuth::updateAuthToken(const User & user, const std::string & oldhash, const std::string & newhash) { - // method called only after successful login - using namespace std::chrono; - using namespace std::string_literals; - 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; - - 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; - - const nlohmann::json data = { - {"status"s, "success"}, // - {"method"s, "token"}, // -// {"user_address"s, _env.clientAddress()}, // -// {"user_agent"s, _env.userAgent()}, // -// {"user_locale"s, _env.locale().name()}, // -// {"referer", _env.referer()}, // -// {"url_scheme", _env.urlScheme()} // - }; - - db(insert_into(t_user_history) - .set(t_user_history.user_id = std::atoi(user.id().c_str()), - t_user_history.action_id = select_login_action_id, // - t_user_history.data = data.dump())); - - 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) { - using namespace std::string_literals; - const auto getStatus = [count]() { return count ? "failed"s : "success"s; }; - - const nlohmann::json data = { - {"status"s, getStatus()}, // - {"method"s, "password"}, // -// {"user_address"s, _env.clientAddress()}, // -// {"user_agent"s, _env.userAgent()}, // -// {"user_locale"s, _env.locale().name()}, // -// {"referer", _env.referer()}, // -// {"url_scheme", _env.urlScheme()} // - }; - - db(insert_into(t_user_history) - .set(t_user_history.user_id = std::atoi(user.id().c_str()), - t_user_history.action_id = select_login_action_id, // - t_user_history.data = data.dump())); -} - -template < typename T > -inline std::string name_of(const T &) { - static_assert(sqlpp::is_column_t< T >::value, "T should by a calumn"); - return std::string{T::_table::_alias_t::_literal} + '.' + T::_alias_t::_literal; -} - -int PgUserAuth::failedLoginAttempts(const User & user) const { - 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 // - t_user_history.action_id == select_login_action_id and // - sqlpp::verbatim< sqlpp::boolean >(name_of(t_user_history.data) + "->>'status' = 'failed'") and // - t_user_history.id > select(t_user_history.id) // - .from(t_user_history) // - .where(t_user_history.user_id == std::atoi(user.id().c_str()) and // - t_user_history.action_id == select_login_action_id and // - sqlpp::verbatim< sqlpp::boolean >(name_of(t_user_history.data) + "->>'status' = 'success'")) // - .limit(1u))); - return static_cast< int >(res.front().count); -} - -void PgUserAuth::setLastLoginAttempt(const User &, PgUserAuth::time_point) {} - -PgUserAuth::time_point PgUserAuth::lastLoginAttempt(const User & user) const { - 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 {}; - - return res.front()._when.value(); -} - -void PgUserAuth::logout(const User & user) { - using namespace std::string_literals; - const nlohmann::json data = {{"status"s, "success"}}; - - db(insert_into(t_user_history) - .set(t_user_history.user_id = std::atoi(user.id().c_str()), - t_user_history.action_id = select_logout_action_id, // - t_user_history.data = data.dump())); -} - -//AbstractUserDatabase::Transaction * PgUserAuth::startTransaction() { -// return new TransactionGuard< decltype(db) >(db, _in_transaction); -//} -} // namespace eedb::db diff --git a/src/eedb/db/data/PgUserAuth.hpp b/src/eedb/db/data/PgUserAuth.hpp deleted file mode 100644 index c40dce0..0000000 --- a/src/eedb/db/data/PgUserAuth.hpp +++ /dev/null @@ -1,125 +0,0 @@ -#pragma once - -#include - -#include - -namespace eedb::db { -namespace details { - class User { - public: - User(std::string id = {}) : _id{std::move(id)} {} - const std::string & id() const { - return _id; - } - - std::string && moveid() { - return std::move(_id); - } - - operator bool() const { - return _id != ""; - } - - private: - std::string _id; - }; - - class PasswordHash { - public: - PasswordHash(std::string function, std::string salt, std::string value) - : function_{std::move(function)}, salt_{std::move(salt)}, value_{std::move(value)} {} - - bool empty() const { - return value_.empty(); - } - - std::string && mfunction() { - return std::move(function_); - } - - std::string && msalt() { - return std::move(salt_); - } - - std::string && mvalue() { - return std::move(value_); - } - - private: - std::string function_, salt_, value_; - }; - - class Token { - public: - using time_point = std::chrono::time_point< std::chrono::system_clock, std::chrono::microseconds >; - Token(std::string hash, time_point expirationTime) : hash_{std::move(hash)}, expirationTime_{expirationTime} {} - - bool empty() const { - return hash_.empty(); - } - - std::string && mhash() { - return std::move(hash_); - } - - time_point expirationTime() const { - return expirationTime_; - } - - private: - std::string hash_; - time_point expirationTime_; - }; -} // namespace details - -class[[deprecated]] PgUserAuth { - public: - using time_point = std::chrono::time_point< std::chrono::system_clock, std::chrono::microseconds >; - - PgUserAuth(PgConnection & db); - - details::User findWithId(const std::string & id) const; - details::User findWithIdentity(const std::string & provider, const std::string & identity) const; - details::User findWithEmailToken(const std::string & hash) const; - details::User findWithEmail(const std::string & address) const; - - void addIdentity(const details::User & user, const std::string & provider, const std::string & id); - void setIdentity(const details::User & user, const std::string & provider, const std::string & id); - std::wstring identity(const details::User & user, const std::string & provider) const; - void removeIdentity(const details::User & user, const std::string & provider); - - details::User registerNew(); - void deleteUser(const details::User & user); - - void setPassword(const details::User & user, details::PasswordHash password); - std::optional< details::PasswordHash > password(const details::User & user) const; - - bool setEmail(const details::User & user, const std::string & address); - std::string email(const details::User & user) const; - void setUnverifiedEmail(const details::User & user, const std::string & address); - std::string unverifiedEmail(const details::User & user) const; - - void setEmailToken(const details::User & user, details::Token token, int role); - std::optional emailToken(const details::User & user) const; - int emailTokenRole(const details::User & user) const; - - void addAuthToken(const details::User & user, details::Token token); - void removeAuthToken(const details::User &user, const std::string & hash); - std::optional findWithAuthToken(const std::string & hash) const; - int updateAuthToken(const details::User & user, const std::string & oldhash, const std::string & newhash); - - void setFailedLoginAttempts(const details::User & user, int count); - int failedLoginAttempts(const details::User & user) const; - void setLastLoginAttempt(const details::User & user, time_point t); - time_point lastLoginAttempt(const details::User & user) const; - - void logout(const details::User & user); - - private: - eedb::db::PgConnection & db; - // const Wt::WEnvironment & _env; - // const Wt::Auth::AuthService * _authService; - // int _in_transaction{0}; -}; -} // namespace eedb::db diff --git a/src/eedb/db/data/PgUsers.cpp b/src/eedb/db/data/PgUsers.cpp index 1245d3f..a47cab1 100644 --- a/src/eedb/db/data/PgUsers.cpp +++ b/src/eedb/db/data/PgUsers.cpp @@ -8,11 +8,9 @@ #include #include #include -#include #include -constexpr eedb::user t_user; constexpr eedb::auth_identity t_auth_identity; constexpr eedb::auth_info t_auth_info; constexpr eedb::auth_token t_auth_token; @@ -37,26 +35,26 @@ unique_ptr< User > PgUsers::findWith(const AuthIdentity & identity) const { const auto provider_eq = t_auth_identity.provider == std::string{identity.provider()}; auto & db = *(_priv->_db.native()); - auto result = db(select(t_auth_info.user_uid) // + auto result = db(select(t_auth_info.id) // .from(t_auth_info.inner_join(t_auth_identity).on(t_auth_info.id == t_auth_identity.auth_info_id)) // .where(provider_eq and identity_eq)); if(result.empty()) { return {}; } - return std::make_unique< PgUser >(_priv->_db, result.front().user_uid); + return std::make_unique< PgUser >(_priv->_db, result.front().id); } unique_ptr< User > PgUsers::findWith(const Email & email) const { auto & db = *(_priv->_db.native()); - auto result = db(select(t_auth_info.user_uid) // + auto result = db(select(t_auth_info.id) // .from(t_auth_info) // .where(t_auth_info.email == std::string{email.address()})); if(result.empty()) { return {}; } - return std::make_unique< PgUser >(_priv->_db, result.front().user_uid); + return std::make_unique< PgUser >(_priv->_db, result.front().id); } unique_ptr< User > PgUsers::findWith(const EmailToken & token) const {} @@ -71,14 +69,6 @@ unique_ptr< User > PgUsers::addUser(unique_ptr< AuthInfo > authInfo, unique_ptr< auto & db = *(_priv->_db.native()); - auto user_id = db(sqlpp::postgresql::insert_into(t_user) // - .set( // - t_user.full_name = "none", // - t_user.status = 0) - .returning(t_user.uid)) - .front() - .uid; - const auto & pass = authInfo->password(); const auto & email = authInfo->email(); @@ -88,8 +78,8 @@ unique_ptr< User > PgUsers::addUser(unique_ptr< AuthInfo > authInfo, unique_ptr< t_auth_info.password_hash = pass.value(), // t_auth_info.password_method = pass.function(), // t_auth_info.password_salt = pass.salt(), // - t_auth_info.user_uid = user_id, // - t_auth_info.email = std::string{email.address()}) + t_auth_info.email = std::string{email.address()}, + t_auth_info.status = 0) ///TODO status->UserWaitingForVerification .returning(t_auth_info.id)) .front() .id; @@ -100,7 +90,7 @@ unique_ptr< User > PgUsers::addUser(unique_ptr< AuthInfo > authInfo, unique_ptr< t_auth_identity.identity = std::string{authIdentity->identity()}, t_auth_identity.provider = std::string{authIdentity->provider()})); - return std::make_unique< eedb::PgUser >(_priv->_db, user_id); + return std::make_unique< eedb::PgUser >(_priv->_db, auth_info_id); } } // namespace eedb diff --git a/src/eedb/db/data/PgUsers.hpp b/src/eedb/db/data/PgUsers.hpp index 53f8f43..e2b89f3 100644 --- a/src/eedb/db/data/PgUsers.hpp +++ b/src/eedb/db/data/PgUsers.hpp @@ -21,6 +21,7 @@ class PgUsers : public Users { // unique_ptr< User > findWith(const AuthToken & token) const override; unique_ptr< User > addUser(unique_ptr< AuthInfo >, unique_ptr< AuthIdentity >) override; + void removeUser(std::unique_ptr< AuthInfo > user) const override{} private: struct PgUsersPriv; diff --git a/src/eedb/db/model/auth_info.h b/src/eedb/db/model/auth_info.h index d419cd3..26b2a6f 100644 --- a/src/eedb/db/model/auth_info.h +++ b/src/eedb/db/model/auth_info.h @@ -24,19 +24,64 @@ namespace eedb { using _traits = ::sqlpp::make_traits<::sqlpp::integer, sqlpp::tag::must_not_insert, sqlpp::tag::must_not_update>; }; - struct User_uid { + struct Group { struct _alias_t { - static constexpr const char _literal[] = R"("user_uid")"; + static constexpr const char _literal[] = R"("group")"; using _name_t = sqlpp::make_char_sequence; template struct _member_t { - T user_uid; - T &operator()() { return user_uid; } - const T &operator()() const { return user_uid; } + T group; + T &operator()() { return group; } + const T &operator()() const { return group; } }; }; - using _traits = ::sqlpp::make_traits<::sqlpp::integer, sqlpp::tag::can_be_null>; + using _traits = ::sqlpp::make_traits<::sqlpp::integer>; + }; + + struct Created { + struct _alias_t { + static constexpr const char _literal[] = R"("created")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T created; + T &operator()() { return created; } + const T &operator()() const { return created; } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::time_point>; + }; + + struct Updated { + struct _alias_t { + static constexpr const char _literal[] = R"("updated")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T updated; + T &operator()() { return updated; } + const T &operator()() const { return updated; } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::time_point, sqlpp::tag::can_be_null>; + }; + + struct Config { + struct _alias_t { + static constexpr const char _literal[] = R"("config")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T config; + T &operator()() { return config; } + const T &operator()() const { return config; } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::text>; }; struct Password_hash { @@ -99,78 +144,33 @@ namespace eedb { using _traits = ::sqlpp::make_traits<::sqlpp::varchar, sqlpp::tag::require_insert>; }; - struct Email_verified { + struct Status { struct _alias_t { - static constexpr const char _literal[] = R"("email_verified")"; + static constexpr const char _literal[] = R"("status")"; using _name_t = sqlpp::make_char_sequence; template struct _member_t { - T email_verified; - T &operator()() { return email_verified; } - const T &operator()() const { return email_verified; } + T status; + T &operator()() { return status; } + const T &operator()() const { return status; } }; }; - using _traits = ::sqlpp::make_traits<::sqlpp::boolean>; - }; - - struct Email_token { - struct _alias_t { - static constexpr const char _literal[] = R"("email_token")"; - using _name_t = sqlpp::make_char_sequence; - template - struct _member_t { - T email_token; - T &operator()() { return email_token; } - const T &operator()() const { return email_token; } - }; - }; - - using _traits = ::sqlpp::make_traits<::sqlpp::varchar, sqlpp::tag::can_be_null>; - }; - - struct Email_token_expires { - struct _alias_t { - static constexpr const char _literal[] = R"("email_token_expires")"; - using _name_t = sqlpp::make_char_sequence; - template - struct _member_t { - T email_token_expires; - T &operator()() { return email_token_expires; } - const T &operator()() const { return email_token_expires; } - }; - }; - - using _traits = ::sqlpp::make_traits<::sqlpp::time_point, sqlpp::tag::can_be_null>; - }; - - struct Email_token_role { - struct _alias_t { - static constexpr const char _literal[] = R"("email_token_role")"; - using _name_t = sqlpp::make_char_sequence; - template - struct _member_t { - T email_token_role; - T &operator()() { return email_token_role; } - const T &operator()() const { return email_token_role; } - }; - }; - - using _traits = ::sqlpp::make_traits<::sqlpp::integer, sqlpp::tag::can_be_null>; + using _traits = ::sqlpp::make_traits<::sqlpp::integer>; }; } struct auth_info : sqlpp::table_t { + auth_info_::Status> { using _value_type = sqlpp::no_value_t; struct _alias_t { static constexpr const char _literal[] = R"("auth_info")"; diff --git a/src/eedb/db/model/auth_token.h b/src/eedb/db/model/auth_token.h index 13b3066..49e0e55 100644 --- a/src/eedb/db/model/auth_token.h +++ b/src/eedb/db/model/auth_token.h @@ -36,10 +36,10 @@ namespace eedb { }; }; - using _traits = ::sqlpp::make_traits<::sqlpp::bigint, sqlpp::tag::require_insert >; - }; + using _traits = ::sqlpp::make_traits<::sqlpp::bigint, sqlpp::tag::can_be_null>; + }; - struct Value { + struct Value { struct _alias_t { static constexpr const char _literal[] = R"("value")"; using _name_t = sqlpp::make_char_sequence; @@ -68,13 +68,29 @@ namespace eedb { using _traits = ::sqlpp::make_traits<::sqlpp::time_point, sqlpp::tag::require_insert>; }; + + struct Role { + struct _alias_t { + static constexpr const char _literal[] = R"("role")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T role; + T &operator()() { return role; } + const T &operator()() const { return role; } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::smallint, sqlpp::tag::require_insert>; + }; } struct auth_token : sqlpp::table_t { + auth_token_::Expires, + auth_token_::Role> { using _value_type = sqlpp::no_value_t; struct _alias_t { static constexpr const char _literal[] = R"("auth_token")"; diff --git a/src/eedb/db/model/measurands.h b/src/eedb/db/model/measurands.h new file mode 100644 index 0000000..2886876 --- /dev/null +++ b/src/eedb/db/model/measurands.h @@ -0,0 +1,92 @@ +#ifndef EEDB_MEASURANDS_H +#define EEDB_MEASURANDS_H + +#include +#include +#include + +namespace eedb { + + namespace measurands_ { + + struct Id { + struct _alias_t { + static constexpr const char _literal[] = R"("id")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T id; + T &operator()() { return id; } + const T &operator()() const { return id; } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::integer, sqlpp::tag::must_not_insert, sqlpp::tag::must_not_update>; + }; + + struct Name { + struct _alias_t { + static constexpr const char _literal[] = R"("name")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T name; + T &operator()() { return name; } + const T &operator()() const { return name; } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::require_insert>; + }; + + struct Description { + struct _alias_t { + static constexpr const char _literal[] = R"("description")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T description; + T &operator()() { return description; } + const T &operator()() const { return description; } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::can_be_null>; + }; + + struct Dimension_symbol { + struct _alias_t { + static constexpr const char _literal[] = R"("dimension_symbol")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T dimension_symbol; + T &operator()() { return dimension_symbol; } + const T &operator()() const { return dimension_symbol; } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::can_be_null>; + }; + } + + struct measurands : sqlpp::table_t { + using _value_type = sqlpp::no_value_t; + struct _alias_t { + static constexpr const char _literal[] = R"("measurands")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T measurands; + T &operator()() { return measurands; } + const T &operator()() const { return measurands; } + }; + }; + }; +} + +#endif diff --git a/src/eedb/db/model/metric_systems.h b/src/eedb/db/model/metric_systems.h new file mode 100644 index 0000000..03315fd --- /dev/null +++ b/src/eedb/db/model/metric_systems.h @@ -0,0 +1,76 @@ +#ifndef EEDB_METRIC_SYSTEMS_H +#define EEDB_METRIC_SYSTEMS_H + +#include +#include +#include + +namespace eedb { + + namespace metric_systems_ { + + struct Id { + struct _alias_t { + static constexpr const char _literal[] = R"("id")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T id; + T &operator()() { return id; } + const T &operator()() const { return id; } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::integer, sqlpp::tag::must_not_insert, sqlpp::tag::must_not_update>; + }; + + struct Name { + struct _alias_t { + static constexpr const char _literal[] = R"("name")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T name; + T &operator()() { return name; } + const T &operator()() const { return name; } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::varchar, sqlpp::tag::require_insert>; + }; + + struct Description { + struct _alias_t { + static constexpr const char _literal[] = R"("description")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T description; + T &operator()() { return description; } + const T &operator()() const { return description; } + }; + }; + + using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::can_be_null>; + }; + } + + struct metric_systems : sqlpp::table_t { + using _value_type = sqlpp::no_value_t; + struct _alias_t { + static constexpr const char _literal[] = R"("metric_systems")"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t { + T metric_systems; + T &operator()() { return metric_systems; } + const T &operator()() const { return metric_systems; } + }; + }; + }; +} + +#endif diff --git a/src/eedb/db/model/user.h b/src/eedb/db/model/user.h deleted file mode 100644 index 74d63e3..0000000 --- a/src/eedb/db/model/user.h +++ /dev/null @@ -1,140 +0,0 @@ -#ifndef EEDB_USER_H -#define EEDB_USER_H - -#include -#include -#include - -namespace eedb { - - namespace user_ { - - struct Uid { - struct _alias_t { - static constexpr const char _literal[] = R"("uid")"; - using _name_t = sqlpp::make_char_sequence; - template - struct _member_t { - T uid; - T &operator()() { return uid; } - const T &operator()() const { return uid; } - }; - }; - - using _traits = ::sqlpp::make_traits<::sqlpp::integer, sqlpp::tag::must_not_insert, sqlpp::tag::must_not_update>; - }; - - struct Group { - struct _alias_t { - static constexpr const char _literal[] = R"("group")"; - using _name_t = sqlpp::make_char_sequence; - template - struct _member_t { - T group; - T &operator()() { return group; } - const T &operator()() const { return group; } - }; - }; - - using _traits = ::sqlpp::make_traits<::sqlpp::integer>; - }; - - struct Status { - struct _alias_t { - static constexpr const char _literal[] = R"("status")"; - using _name_t = sqlpp::make_char_sequence; - template - struct _member_t { - T status; - T &operator()() { return status; } - const T &operator()() const { return status; } - }; - }; - - using _traits = ::sqlpp::make_traits<::sqlpp::integer>; - }; - - struct Full_name { - struct _alias_t { - static constexpr const char _literal[] = R"("full_name")"; - using _name_t = sqlpp::make_char_sequence; - template - struct _member_t { - T full_name; - T &operator()() { return full_name; } - const T &operator()() const { return full_name; } - }; - }; - - using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::require_insert>; - }; - - struct Created { - struct _alias_t { - static constexpr const char _literal[] = R"("created")"; - using _name_t = sqlpp::make_char_sequence; - template - struct _member_t { - T created; - T &operator()() { return created; } - const T &operator()() const { return created; } - }; - }; - - using _traits = ::sqlpp::make_traits<::sqlpp::time_point>; - }; - - struct Updated { - struct _alias_t { - static constexpr const char _literal[] = R"("updated")"; - using _name_t = sqlpp::make_char_sequence; - template - struct _member_t { - T updated; - T &operator()() { return updated; } - const T &operator()() const { return updated; } - }; - }; - - using _traits = ::sqlpp::make_traits<::sqlpp::time_point, sqlpp::tag::can_be_null>; - }; - - struct Config { - struct _alias_t { - static constexpr const char _literal[] = R"("config")"; - using _name_t = sqlpp::make_char_sequence; - template - struct _member_t { - T config; - T &operator()() { return config; } - const T &operator()() const { return config; } - }; - }; - - using _traits = ::sqlpp::make_traits<::sqlpp::text>; - }; - } - - struct user : sqlpp::table_t { - using _value_type = sqlpp::no_value_t; - struct _alias_t { - static constexpr const char _literal[] = R"("user")"; - using _name_t = sqlpp::make_char_sequence; - template - struct _member_t { - T user; - T &operator()() { return user; } - const T &operator()() const { return user; } - }; - }; - }; -} - -#endif diff --git a/src/eedb/db/model/user_audit.h b/src/eedb/db/model/user_audit.h index 0006480..8aa78a6 100644 --- a/src/eedb/db/model/user_audit.h +++ b/src/eedb/db/model/user_audit.h @@ -24,15 +24,15 @@ namespace eedb { using _traits = ::sqlpp::make_traits<::sqlpp::integer, sqlpp::tag::must_not_insert, sqlpp::tag::must_not_update>; }; - struct User_id { + struct Auth_info_id { struct _alias_t { - static constexpr const char _literal[] = R"("user_id")"; + static constexpr const char _literal[] = R"("auth_info_id")"; using _name_t = sqlpp::make_char_sequence; template struct _member_t { - T user_id; - T &operator()() { return user_id; } - const T &operator()() const { return user_id; } + T auth_info_id; + T &operator()() { return auth_info_id; } + const T &operator()() const { return auth_info_id; } }; }; @@ -69,15 +69,15 @@ namespace eedb { using _traits = ::sqlpp::make_traits<::sqlpp::text, sqlpp::tag::can_be_null>; }; - struct When { + struct When_happened { struct _alias_t { - static constexpr const char _literal[] = R"("when")"; + static constexpr const char _literal[] = R"("when_happened")"; using _name_t = sqlpp::make_char_sequence; template struct _member_t { - T _when; - T &operator()() { return _when; } - const T &operator()() const { return _when; } + T when_happened; + T &operator()() { return when_happened; } + const T &operator()() const { return when_happened; } }; }; @@ -87,13 +87,13 @@ namespace eedb { struct user_audit : sqlpp::table_t { + user_audit_::When_happened> { using _value_type = sqlpp::no_value_t; struct _alias_t { - static constexpr const char _literal[] = R"("user_audit")"; + static constexpr const char _literal[] = R"("user_audit")"; using _name_t = sqlpp::make_char_sequence; template struct _member_t { diff --git a/src/eedb/db/model/user_inventory.h b/src/eedb/db/model/user_inventory.h index 815ecd0..cfd227d 100644 --- a/src/eedb/db/model/user_inventory.h +++ b/src/eedb/db/model/user_inventory.h @@ -9,15 +9,15 @@ namespace eedb { namespace user_inventory_ { - struct User_id { + struct Auth_info_id { struct _alias_t { - static constexpr const char _literal[] = R"("user_id")"; + static constexpr const char _literal[] = R"("auth_info_id")"; using _name_t = sqlpp::make_char_sequence; template struct _member_t { - T user_id; - T &operator()() { return user_id; } - const T &operator()() const { return user_id; } + T auth_info_id; + T &operator()() { return auth_info_id; } + const T &operator()() const { return auth_info_id; } }; }; @@ -41,7 +41,7 @@ namespace eedb { } struct user_inventory : sqlpp::table_t { using _value_type = sqlpp::no_value_t; struct _alias_t { diff --git a/src/utils/Visitor.hpp b/src/utils/Visitor.hpp new file mode 100644 index 0000000..7560d71 --- /dev/null +++ b/src/utils/Visitor.hpp @@ -0,0 +1,8 @@ +#include + +template < class... Ts > +struct Visitor : Ts... { + using Ts::operator()...; +}; +template < class... Ts > +Visitor(Ts...)->Visitor< std::decay_t< Ts >... >; diff --git a/tests/unit/db/test_eedb_data_PgAuthToken.cpp b/tests/unit/db/test_eedb_data_PgAuthToken.cpp index c39aaf6..c4c5786 100644 --- a/tests/unit/db/test_eedb_data_PgAuthToken.cpp +++ b/tests/unit/db/test_eedb_data_PgAuthToken.cpp @@ -8,93 +8,51 @@ #include #include #include -#include #include #include #include #include -constexpr eedb::user t_user; constexpr eedb::user_audit t_user_audit; constexpr eedb::user_action t_user_action; constexpr eedb::auth_identity t_auth_identity; constexpr eedb::auth_info t_auth_info; constexpr eedb::auth_token t_auth_token; -class UserMock : public eedb::User { - public: - MOCK_CONST_METHOD0(uid, int()); - const eedb::UserConfig & config() const override {} - eedb::AuthTokens & authTokens() const override {} - void logout() override {} - eedb::AuthIdentities & authIdentities() const {} - int64_t _uid; - - // User interface -public: - eedb::AuthInfo &authInfo() const override{} -}; - class PgAuthTokenTest : public DbTestBase< PgAuthTokenTest > { public: PgAuthTokenTest() { using namespace testing; db().native()->start_transaction(); + + authInfoId = db()(sqlpp::postgresql::insert_into(t_auth_info) + .set( // + t_auth_info.password_hash = "$2y$07$RyytUhDhLDbAPjf0b0r2Y.dsg.FlQ7L.xzWHMmoelI81u0MfBrW7q", + t_auth_info.password_method = "bcrypt", + t_auth_info.password_salt = "OM/Z1c4WBFXvwkxh", + t_auth_info.email = "none@eedb.pl", // + t_auth_info.status = 0) + .returning(t_auth_info.id)) + .front() + .id; + + sutId = db()(sqlpp::postgresql::insert_into(t_auth_token) // + .set( // + t_auth_token.auth_info_id = authInfoId, // + t_auth_token.expires = std::chrono::system_clock::now(), // + t_auth_token.role = static_cast< int >(eedb::AuthTokenRole::Auth), // + t_auth_token.value = "empty") // + .returning(t_auth_token.id)) + .front() + .id; + + sut = std::make_unique< eedb::PgAuthToken >(db(), sutId); } - void createExpiredToken() { - auto sql = R"sql( - WITH user_ AS ( INSERT INTO "user" ("full_name","status") - VALUES('none',0) - RETURNING "user"."uid" AS user_id ) - , auth_info_ AS ( - INSERT INTO "auth_info" ("password_hash","password_method","password_salt","user_uid","email") - SELECT '$2y$07$RyytUhDhLDbAPjf0b0r2Y.dsg.FlQ7L.xzWHMmoelI81u0MfBrW7q','bcrypt','OM/Z1c4WBFXvwkxh',user_id,'none@eedb.pl' - FROM user_ - RETURNING "auth_info"."id" AS auth_info_id ) - , auth_identity_ AS( - INSERT INTO "auth_identity" ("auth_info_id","identity","provider") - SELECT auth_info_id,'username','loginname' - FROM auth_info_ ) - INSERT into auth_token ("auth_info_id","value","expires") - SELECT auth_info_id, 'hash', now() - interval '1 hour' - FROM auth_info_ - )sql"; - - db().native()->execute(sql); - - auto auth_info_id = db()(select(t_auth_info.id).from(t_auth_info).where(t_auth_info.email == "none@eedb.pl")).front().id; - auto _uid = db()(select(t_auth_token.id).from(t_auth_token).where(t_auth_token.auth_info_id == auth_info_id)).front().id; - - sut = std::make_unique< eedb::PgAuthToken >(db(), _uid); - } - - void createValidToken() { - auto sql = R"sql( - WITH user_ AS ( INSERT INTO "user" ("full_name","status") - VALUES('none',0) - RETURNING "user"."uid" AS user_id ) - , auth_info_ AS ( - INSERT INTO "auth_info" ("password_hash","password_method","password_salt","user_uid","email") - SELECT '$2y$07$RyytUhDhLDbAPjf0b0r2Y.dsg.FlQ7L.xzWHMmoelI81u0MfBrW7q','bcrypt','OM/Z1c4WBFXvwkxh',user_id,'none@eedb.pl' - FROM user_ - RETURNING "auth_info"."id" AS auth_info_id ) - , auth_identity_ AS( - INSERT INTO "auth_identity" ("auth_info_id","identity","provider") - SELECT auth_info_id,'username','loginname' - FROM auth_info_ ) - INSERT into auth_token ("auth_info_id","value","expires") - SELECT auth_info_id, 'hash', now() + interval '1 hour' - FROM auth_info_ - )sql"; - - db().native()->execute(sql); - - auto auth_info_id = db()(select(t_auth_info.id).from(t_auth_info).where(t_auth_info.email == "none@eedb.pl")).front().id; - auto _uid = db()(select(t_auth_token.id).from(t_auth_token).where(t_auth_token.auth_info_id == auth_info_id)).front().id; - - sut = std::make_unique< eedb::PgAuthToken >(db(), _uid); + void setExpireDate(std::chrono::system_clock::time_point exp) { + using namespace std::chrono; + db()(update(t_auth_token).set(t_auth_token.expires = time_point_cast< microseconds >(exp)).where(t_auth_token.id == sutId)); } ~PgAuthTokenTest() { @@ -102,7 +60,8 @@ class PgAuthTokenTest : public DbTestBase< PgAuthTokenTest > { } protected: - testing::NiceMock< UserMock > user; + int64_t authInfoId; + int64_t sutId; std::unique_ptr< eedb::PgAuthToken > sut; }; @@ -110,24 +69,23 @@ template <> std::unique_ptr< PgTestDatabasePrepare > DbTestBase< PgAuthTokenTest >::_test_db = {}; using namespace sqlpp; +using namespace std::chrono; TEST_F(PgAuthTokenTest, isExpired) { - createExpiredToken(); + setExpireDate(system_clock::now() - hours{1}); EXPECT_TRUE(sut->expired()); } TEST_F(PgAuthTokenTest, isNotExpired) { - createValidToken(); + setExpireDate(system_clock::now() + hours{1}); EXPECT_FALSE(sut->expired()); } TEST_F(PgAuthTokenTest, validValue) { - createValidToken(); - EXPECT_EQ(sut->token(), "hash"); + EXPECT_EQ(sut->token(), "empty"); } TEST_F(PgAuthTokenTest, updateValue) { - createValidToken(); sut->update("NEW"); EXPECT_EQ(sut->token(), "NEW"); } diff --git a/tests/unit/db/test_eedb_data_PgAuthTokens.cpp b/tests/unit/db/test_eedb_data_PgAuthTokens.cpp index 4bd56d6..aa6ff37 100644 --- a/tests/unit/db/test_eedb_data_PgAuthTokens.cpp +++ b/tests/unit/db/test_eedb_data_PgAuthTokens.cpp @@ -2,20 +2,18 @@ #include -#include #include +#include #include #include #include -#include #include #include #include #include -constexpr eedb::user t_user; constexpr eedb::user_audit t_user_audit; constexpr eedb::user_action t_user_action; constexpr eedb::auth_identity t_auth_identity; @@ -33,8 +31,8 @@ class UserMock : public eedb::User { int64_t _uid; // User interface -public: - eedb::AuthInfo &authInfo() const override{} + public: + eedb::AuthInfo & authInfo() const override {} }; } // namespace @@ -43,36 +41,29 @@ class PgAuthTokensTest : public DbTestBase< PgAuthTokensTest > { PgAuthTokensTest() { using namespace testing; db().native()->start_transaction(); + + authInfoId = db()(sqlpp::postgresql::insert_into(t_auth_info) + .set( // + t_auth_info.password_hash = "$2y$07$RyytUhDhLDbAPjf0b0r2Y.dsg.FlQ7L.xzWHMmoelI81u0MfBrW7q", + t_auth_info.password_method = "bcrypt", + t_auth_info.password_salt = "OM/Z1c4WBFXvwkxh", + t_auth_info.email = "none@eedb.pl", // + t_auth_info.status = 0) + .returning(t_auth_info.id)) + .front() + .id; + + ON_CALL(user, uid()).WillByDefault(testing::Return(authInfoId)); + sut = std::make_unique< eedb::PgAuthTokens >(db(), &user); } - void createToken() { - auto sql = R"sql( - WITH user_ AS ( INSERT INTO "user" ("full_name","status") - VALUES('none',0) - RETURNING "user"."uid" AS user_id ) - , auth_info_ AS ( - INSERT INTO "auth_info" ("password_hash","password_method","password_salt","user_uid","email") - SELECT '$2y$07$RyytUhDhLDbAPjf0b0r2Y.dsg.FlQ7L.xzWHMmoelI81u0MfBrW7q','bcrypt','OM/Z1c4WBFXvwkxh',user_id,'none@eedb.pl' - FROM user_ - RETURNING "auth_info"."id" AS auth_info_id ) - , auth_identity_ AS( - INSERT INTO "auth_identity" ("auth_info_id","identity","provider") - SELECT auth_info_id,'username','loginname' - FROM auth_info_ ) - INSERT into auth_token ("auth_info_id","value","expires") - SELECT auth_info_id, 'hash', now() + interval '1 hour' - FROM auth_info_ - )sql"; - - db().native()->execute(sql); - - auto auth_info_id = db()(select(t_auth_info.id).from(t_auth_info).where(t_auth_info.email == "none@eedb.pl")).front().id; - // auto _uid = db()(select(t_auth_token.id).from(t_auth_token).where(t_auth_token.auth_info_id == - // auth_info_id)).front().id; - - ON_CALL(user, uid()).WillByDefault(testing::Return(auth_info_id)); - - sut = std::make_unique< eedb::PgAuthTokens >(db(), &user); + void createToken(std::string hash, eedb::AuthTokenRole role) { + db()(sqlpp::postgresql::insert_into(t_auth_token) // + .set( // + t_auth_token.value = hash, // + t_auth_token.auth_info_id = authInfoId, // + t_auth_token.role = static_cast< int16_t >(role), // + t_auth_token.expires = std::chrono::system_clock::now() + std::chrono::hours{1})); } ~PgAuthTokensTest() { @@ -81,6 +72,7 @@ class PgAuthTokensTest : public DbTestBase< PgAuthTokensTest > { protected: UserMock user; + int64_t authInfoId{0}; std::unique_ptr< eedb::PgAuthTokens > sut; }; @@ -88,33 +80,34 @@ template <> std::unique_ptr< PgTestDatabasePrepare > DbTestBase< PgAuthTokensTest >::_test_db = {}; using namespace sqlpp; +using namespace eedb; -TEST_F(PgAuthTokensTest, tokenFound) { - createToken(); - EXPECT_TRUE(sut->find("hash")); +TEST_F(PgAuthTokensTest, authTokenFound) { + createToken("token", AuthTokenRole::Auth); + EXPECT_TRUE(sut->find("token")); +} + +TEST_F(PgAuthTokensTest, emailTokenFound) { + createToken("emailtoken", AuthTokenRole::EmailToken); + EXPECT_TRUE(sut->find(AuthTokenRole::EmailToken)); } TEST_F(PgAuthTokensTest, tokenNotFound) { - createToken(); - EXPECT_FALSE(sut->find("bad hash")); + EXPECT_FALSE(sut->find("token")); } -TEST_F(PgAuthTokensTest, readFromCache) { - createToken(); - EXPECT_TRUE(sut->find("hash")); - EXPECT_TRUE(sut->find("hash")); -} +TEST_F(PgAuthTokensTest, addMultipleAuthTokens) { + sut->addToken("authtoken1", eedb::AuthTokenRole::Auth); + sut->addToken("authtoken2", eedb::AuthTokenRole::Auth); -TEST_F(PgAuthTokensTest, addToken) { - createToken(); - sut->addToken("hash2"); - EXPECT_TRUE(sut->find("hash")); - EXPECT_TRUE(sut->find("hash2")); + EXPECT_TRUE(sut->find("authtoken1")); + EXPECT_TRUE(sut->find("authtoken2")); } TEST_F(PgAuthTokensTest, removeToken) { - createToken(); - EXPECT_TRUE(sut->find("hash")); - sut->removeToken("hash"); - EXPECT_FALSE(sut->find("hash")); + sut->addToken("authtoken1", eedb::AuthTokenRole::Auth); + sut->addToken("authtoken2", eedb::AuthTokenRole::Auth); + + sut->removeToken("authtoken1"); + EXPECT_FALSE(sut->find("authtoken1")); } diff --git a/tests/unit/db/test_eedb_data_PgUsers.cpp b/tests/unit/db/test_eedb_data_PgUsers.cpp index 27079eb..a4b587a 100644 --- a/tests/unit/db/test_eedb_data_PgUsers.cpp +++ b/tests/unit/db/test_eedb_data_PgUsers.cpp @@ -5,14 +5,12 @@ #include #include #include -#include #include #include #include #include -constexpr eedb::user t_user; constexpr eedb::user_audit t_user_audit; constexpr eedb::user_action t_user_action; constexpr eedb::auth_identity t_auth_identity; @@ -47,18 +45,16 @@ class PgUsersTest : public DbTestBase< PgUsersTest > { } auto idMap() { - std::map< std::string, uint64_t > _userIdMap; + std::map< std::string, int64_t > _userIdMap; auto ids = db()( // - select(t_user.uid, t_auth_identity.identity) - .from(t_user // - .inner_join(t_auth_info) - .on(t_auth_info.user_uid == t_user.uid) + select(t_auth_info.id, t_auth_identity.identity) + .from(t_auth_info // .inner_join(t_auth_identity) .on(t_auth_identity.auth_info_id == t_auth_info.id)) .unconditionally()); for(const auto & row : ids) { - _userIdMap[row.identity] = row.uid; + _userIdMap[row.identity] = row.id; } return _userIdMap; @@ -82,18 +78,15 @@ TEST_F(PgUsersTest, createValidUser) { auto new_user = sut->addUser(std::move(info_), std::move(identity_)); ASSERT_TRUE(new_user); - auto info = db()(select(sqlpp::all_of(t_auth_info)).from(t_auth_info).where(t_auth_info.user_uid == new_user->uid())); + auto info = db()(select(sqlpp::all_of(t_auth_info)).from(t_auth_info).where(t_auth_info.id == new_user->uid())); EXPECT_FALSE(info.empty()); EXPECT_EQ(info.front().password_hash, "$2y$07$RyytUhDhLDbAPjf0b0r2Y.dsg.FlQ7L.xzWHMmoelI81u0MfBrW7q"); EXPECT_EQ(info.front().password_method, "bcrypt"); EXPECT_EQ(info.front().password_salt, "OM/Z1c4WBFXvwkxh"); EXPECT_EQ(info.front().email, "none@eedb.pl"); - EXPECT_EQ(info.front().email_verified, false); - EXPECT_EQ(info.front().user_uid.value(), new_user->uid()); - - auto user = db()(select(sqlpp::all_of(t_user)).from(t_user).where(t_user.uid == new_user->uid())); - /// TODO check username structure + EXPECT_EQ(info.front().status.value(), 0); /// ODO status:Waiting for verification + EXPECT_EQ(info.front().id.value(), new_user->uid()); auto identity = db()(select(t_auth_identity.provider, t_auth_identity.identity) .from(t_auth_identity) @@ -113,7 +106,7 @@ TEST_F(PgUsersTest, createWithSameEmailMustFail) { EXPECT_THROW(createUser("_test_user_2", "test_email@eedb.pl"), sqlpp::postgresql::integrity_constraint_violation); } -//TEST_F(PgUsersTest, findById) { +// TEST_F(PgUsersTest, findById) { // createTestUsers(); // auto u = sut->findWith();