move all db dependent data into db subdir
This commit is contained in:
parent
207e50a880
commit
51e39e5498
@ -10,6 +10,7 @@ file(GLOB SOURCE
|
|||||||
)
|
)
|
||||||
|
|
||||||
include_directories( ${PostgreSQL_INCLUDE_DIRS} )
|
include_directories( ${PostgreSQL_INCLUDE_DIRS} )
|
||||||
|
|
||||||
add_subdirectory(db)
|
add_subdirectory(db)
|
||||||
|
|
||||||
add_library(auth STATIC ${SOURCE})
|
add_library(auth STATIC ${SOURCE})
|
||||||
|
|||||||
@ -1,21 +1,7 @@
|
|||||||
#include "PgUserAuth.hpp"
|
#include "PgUserAuth.hpp"
|
||||||
|
|
||||||
|
#include <eedb/db/data/PgUserAuth.hpp>
|
||||||
#include <eedb/auth/Services.hpp>
|
#include <eedb/auth/Services.hpp>
|
||||||
#include <eedb/db/connection.hpp>
|
|
||||||
#include <eedb/db/model/auth_identity.h>
|
|
||||||
#include <eedb/db/model/auth_info.h>
|
|
||||||
#include <eedb/db/model/auth_token.h>
|
|
||||||
#include <eedb/db/model/user.h>
|
|
||||||
#include <eedb/db/model/user_action.h>
|
|
||||||
#include <eedb/db/model/user_audit.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cctype>
|
|
||||||
#include <experimental/string_view>
|
|
||||||
#include <functional>
|
|
||||||
#include <iostream>
|
|
||||||
#include <random>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <Wt/Auth/AuthService.h>
|
#include <Wt/Auth/AuthService.h>
|
||||||
#include <Wt/Auth/Dbo/AuthInfo.h>
|
#include <Wt/Auth/Dbo/AuthInfo.h>
|
||||||
@ -23,29 +9,9 @@
|
|||||||
#include <Wt/WLogger.h>
|
#include <Wt/WLogger.h>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <sqlpp11/sqlpp11.h>
|
|
||||||
|
|
||||||
using namespace Wt::Auth;
|
using namespace Wt::Auth;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
template < typename Connection >
|
template < typename Connection >
|
||||||
struct TransactionGuard : public Wt::Auth::AbstractUserDatabase::Transaction {
|
struct TransactionGuard : public Wt::Auth::AbstractUserDatabase::Transaction {
|
||||||
TransactionGuard(Connection & c, int & transaction_active) : _c(c), _transaction{transaction_active} {
|
TransactionGuard(Connection & c, int & transaction_active) : _c(c), _transaction{transaction_active} {
|
||||||
@ -72,42 +38,15 @@ struct TransactionGuard : public Wt::Auth::AbstractUserDatabase::Transaction {
|
|||||||
|
|
||||||
namespace eedb::auth {
|
namespace eedb::auth {
|
||||||
|
|
||||||
namespace {
|
PgUserAuth::PgUserAuth(eedb::db::PgConnection & _db, const Wt::WEnvironment & env) : db(_db), _env(env), _userAuth{_db} {
|
||||||
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(eedb::db::PgConnection & _db, const Wt::WEnvironment & env) : db(_db), _env(env) {
|
|
||||||
this->setAuthService(eedb::auth::Services::authService());
|
this->setAuthService(eedb::auth::Services::authService());
|
||||||
}
|
}
|
||||||
|
|
||||||
User PgUserAuth::findWithId(const std::string & id) const {
|
User PgUserAuth::findWithId(const std::string & id) const {
|
||||||
const auto uid_eq = t_user.uid == std::atoi(id.c_str());
|
auto duser = _userAuth.findWithId(id);
|
||||||
|
|
||||||
if(db(select(exists(select(t_user.uid) //
|
if(duser)
|
||||||
.from(t_user) //
|
return User(duser.moveid(), *this);
|
||||||
.where(uid_eq))))
|
|
||||||
.front()
|
|
||||||
.exists) {
|
|
||||||
return User(id, *this);
|
|
||||||
}
|
|
||||||
return User();
|
return User();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,101 +56,53 @@ User PgUserAuth::findWithIdentity(const std::string & provider, const Wt::WStrin
|
|||||||
std::transform(_identity.begin(), _identity.end(), _identity.begin(), ::tolower);
|
std::transform(_identity.begin(), _identity.end(), _identity.begin(), ::tolower);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto identity_eq = t_identity.identity == _identity;
|
auto duser = _userAuth.findWithIdentity(provider, _identity);
|
||||||
const auto provider_eq = t_identity.provider == provider;
|
if(duser) {
|
||||||
|
return User(duser.moveid(), *this);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
return User();
|
return User();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
User PgUserAuth::findWithEmailToken(const std::string & hash) const {
|
||||||
|
auto duser = _userAuth.findWithEmailToken(hash);
|
||||||
|
if(duser)
|
||||||
|
return {duser.moveid(), *this};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
User PgUserAuth::findWithEmail(const std::string & address) const {
|
||||||
|
auto duser = _userAuth.findWithEmail(address);
|
||||||
|
if(duser)
|
||||||
|
return {duser.moveid(), *this};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void PgUserAuth::addIdentity(const User & user, const std::string & provider, const Wt::WString & identity) {
|
void PgUserAuth::addIdentity(const User & user, const std::string & provider, const Wt::WString & identity) {
|
||||||
const auto uid_eq = t_info.user_uid == std::atoi(user.id().c_str());
|
_userAuth.addIdentity({user.id()}, provider, identity.toUTF8());
|
||||||
const auto identity_eq = t_identity.identity == identity.toUTF8();
|
|
||||||
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.toUTF8(), //
|
|
||||||
t_identity.provider = provider, //
|
|
||||||
t_identity.auth_info_id = auth_info_id.front().id));
|
|
||||||
|
|
||||||
Wt::log("info") << "created new identity for user";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PgUserAuth::setIdentity(const User & user, const std::string & provider, const Wt::WString & identity) {
|
void PgUserAuth::setIdentity(const User & user, const std::string & provider, const Wt::WString & identity) {
|
||||||
const auto uid = std::atoi(user.id().c_str());
|
_userAuth.setIdentity({user.id()}, provider, identity.toUTF8());
|
||||||
const auto provider_eq = t_identity.provider == provider;
|
|
||||||
|
|
||||||
db(update(t_identity) //
|
|
||||||
.set(t_identity.identity = identity.toUTF8()) //
|
|
||||||
.where(t_identity.auth_info_id == select(t_info.id).from(t_info).where(t_info.user_uid == uid) and provider_eq));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WString PgUserAuth::identity(const User & user, const std::string & provider) const {
|
Wt::WString PgUserAuth::identity(const User & user, const std::string & provider) const {
|
||||||
const int uid = std::atoi(user.id().c_str());
|
auto id = _userAuth.identity({user.id()}, provider);
|
||||||
const auto id_eq = t_info.user_uid == uid;
|
|
||||||
const auto provider_eq = t_identity.provider == provider;
|
|
||||||
|
|
||||||
auto res = db(select(t_identity.identity) //
|
if(id.empty())
|
||||||
.from(auth_info_identity) //
|
|
||||||
.where(id_eq and provider_eq));
|
|
||||||
|
|
||||||
if(res.empty())
|
|
||||||
return Wt::WString::Empty;
|
return Wt::WString::Empty;
|
||||||
return {res.front().identity};
|
return {id};
|
||||||
}
|
}
|
||||||
|
|
||||||
void PgUserAuth::removeIdentity(const User & user, const std::string & provider) {
|
void PgUserAuth::removeIdentity(const User & user, const std::string & provider) {
|
||||||
const auto id_eq = t_identity.id == std::atoi(user.id().c_str());
|
_userAuth.removeIdentity({user.id()}, provider);
|
||||||
const auto provider_eq = t_identity.provider == provider;
|
|
||||||
|
|
||||||
db(remove_from(t_identity).where(id_eq and provider_eq));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
User PgUserAuth::registerNew() {
|
User PgUserAuth::registerNew() {
|
||||||
auto user_id = db(sqlpp::postgresql::insert_into(t_user) //
|
return User{_userAuth.registerNew().moveid(), *this};
|
||||||
.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), *this};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PgUserAuth::deleteUser(const User & user) {
|
void PgUserAuth::deleteUser(const User & user) {
|
||||||
db(remove_from(t_info) //
|
_userAuth.deleteUser({user.id()});
|
||||||
.where(t_info.id == std::atoi(user.id().c_str())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// User::Status PgUserAuth::status(const User & user) const {
|
// User::Status PgUserAuth::status(const User & user) const {
|
||||||
@ -232,265 +123,98 @@ void PgUserAuth::deleteUser(const User & user) {
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
void PgUserAuth::setPassword(const User & user, const PasswordHash & password) {
|
void PgUserAuth::setPassword(const User & user, const PasswordHash & password) {
|
||||||
db(update(t_info)
|
_userAuth.setPassword({user.id()}, {password.function(), password.salt(), password.value()});
|
||||||
.set( //
|
|
||||||
t_info.password_hash = password.value(), //
|
|
||||||
t_info.password_method = password.function(), //
|
|
||||||
t_info.password_salt = password.salt())
|
|
||||||
.where(t_info.user_uid == std::atoi(user.id().c_str())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PasswordHash PgUserAuth::password(const User & user) const {
|
PasswordHash PgUserAuth::password(const User & user) const {
|
||||||
const auto id_eq = t_info.user_uid == std::atoi(user.id().c_str());
|
auto pass = _userAuth.password({user.id()});
|
||||||
|
if(!pass)
|
||||||
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 PasswordHash();
|
return PasswordHash();
|
||||||
auto & front = row.front();
|
return {pass->mfunction(), pass->msalt(), pass->mvalue()};
|
||||||
return {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) {
|
bool PgUserAuth::setEmail(const User & user, const std::string & address) {
|
||||||
const auto uid_eq = t_info.user_uid == std::atoi(user.id().c_str());
|
return _userAuth.setEmail({user.id()}, address);
|
||||||
|
|
||||||
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 {
|
std::string PgUserAuth::email(const User & user) const {
|
||||||
auto ret = db(select(t_info.email) //
|
return _userAuth.email({user.id()});
|
||||||
.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) {
|
void PgUserAuth::setUnverifiedEmail(const User & user, const std::string & address) {
|
||||||
// orginal implementation of UserDatabase (provided by WT team) sets veryfied and unverified emails in
|
_userAuth.setUnverifiedEmail({user.id()}, address);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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), *this};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PgUserAuth::setEmailToken(const User & user, const Token & token, EmailTokenRole role) {
|
void PgUserAuth::setEmailToken(const User & user, const Token & token, EmailTokenRole role) {
|
||||||
auto exp = ::date::floor<::std::chrono::milliseconds >(std::chrono::system_clock::from_time_t(token.expirationTime().toTime_t()));
|
auto exp = ::date::floor<::std::chrono::milliseconds >(std::chrono::system_clock::from_time_t(token.expirationTime().toTime_t()));
|
||||||
db(update(t_info) //
|
_userAuth.setEmailToken({user.id()}, {token.hash(), exp}, static_cast< int >(role) );
|
||||||
.set( //
|
}
|
||||||
t_info.email_token = token.hash(), //
|
|
||||||
t_info.email_token_expires = exp,
|
std::string PgUserAuth::unverifiedEmail(const User & user) const {
|
||||||
t_info.email_token_role = static_cast< int >(role)) //
|
_userAuth.unverifiedEmail({user.id()});
|
||||||
.where(t_info.user_uid == std::atoi(user.id().c_str())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Token PgUserAuth::emailToken(const User & user) const {
|
Token PgUserAuth::emailToken(const User & user) const {
|
||||||
auto ret = db(select(t_info.email_token, t_info.email_token_expires) //
|
auto tok = _userAuth.emailToken({user.id()});
|
||||||
.from(t_info) //
|
if(!tok)
|
||||||
.where(t_info.user_uid == std::atoi(user.id().c_str())));
|
|
||||||
if(ret.empty())
|
|
||||||
return {};
|
return {};
|
||||||
auto exp = Wt::WDateTime();
|
auto exp = Wt::WDateTime();
|
||||||
auto time = ret.front().email_token_expires.value();
|
auto time = tok->expirationTime();
|
||||||
auto systime = std::chrono::system_clock::to_time_t(time);
|
auto systime = std::chrono::system_clock::to_time_t(time);
|
||||||
exp.setTime_t(systime);
|
exp.setTime_t(systime);
|
||||||
return {ret.front().email_token, exp};
|
return {tok->mhash(), exp};
|
||||||
}
|
}
|
||||||
|
|
||||||
EmailTokenRole PgUserAuth::emailTokenRole(const User & user) const {
|
EmailTokenRole PgUserAuth::emailTokenRole(const User & user) const {
|
||||||
auto ret = db(select(t_info.email_token_role) //
|
auto val = _userAuth.emailTokenRole({user.id()});
|
||||||
.from(t_info) //
|
return static_cast< EmailTokenRole >(val);
|
||||||
.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< EmailTokenRole >(val.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
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), *this};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PgUserAuth::addAuthToken(const User & user, const Token & token) {
|
void PgUserAuth::addAuthToken(const User & user, const Token & token) {
|
||||||
auto select_identity_id = select(t_info.id) //
|
auto exp = std::chrono::time_point_cast< std::chrono::microseconds >(std::chrono::system_clock::now()) + ::sqlpp::chrono::days{14};
|
||||||
.from(t_info)
|
auto tok = eedb::db::details::Token{token.hash(), exp};
|
||||||
.where(t_info.user_uid == std::atoi(user.id().c_str()))
|
_userAuth.addAuthToken({user.id()}, std::move(tok));
|
||||||
.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.hash()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PgUserAuth::removeAuthToken(const User &, const std::string & hash) {
|
void PgUserAuth::removeAuthToken(const User &user, const std::string & hash) {
|
||||||
db(remove_from(t_auth_token).where(t_auth_token.value == hash));
|
_userAuth.removeAuthToken({user.id()}, hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
User PgUserAuth::findWithAuthToken(const std::string & hash) const {
|
User PgUserAuth::findWithAuthToken(const std::string & hash) const {
|
||||||
auto fullUser = t_auth_token //
|
auto u = _userAuth.findWithAuthToken(hash);
|
||||||
.join(t_info)
|
if(!u)
|
||||||
.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 {};
|
||||||
return {std::to_string(ret.front().user_uid), *this};
|
return {u->moveid(), *this};
|
||||||
}
|
}
|
||||||
|
|
||||||
int PgUserAuth::updateAuthToken(const User & user, const std::string & oldhash, const std::string & newhash) {
|
int PgUserAuth::updateAuthToken(const User & user, const std::string & oldhash, const std::string & newhash) {
|
||||||
// method called only after successful login
|
// method called only after successful login
|
||||||
using namespace std::chrono;
|
return _userAuth.updateAuthToken({user.id()}, oldhash, newhash);
|
||||||
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) {
|
void PgUserAuth::setFailedLoginAttempts(const User & user, int count) {
|
||||||
using namespace std::string_literals;
|
_userAuth.setFailedLoginAttempts({user.id()}, count);
|
||||||
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 {
|
int PgUserAuth::failedLoginAttempts(const User & user) const {
|
||||||
const auto res =
|
return _userAuth.failedLoginAttempts({user.id()});
|
||||||
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 &, const Wt::WDateTime &) {}
|
void PgUserAuth::setLastLoginAttempt(const User &, const Wt::WDateTime &) {}
|
||||||
|
|
||||||
Wt::WDateTime PgUserAuth::lastLoginAttempt(const User & user) const {
|
Wt::WDateTime PgUserAuth::lastLoginAttempt(const User & user) const {
|
||||||
using namespace std::chrono;
|
const auto time = _userAuth.lastLoginAttempt({user.id()});
|
||||||
const auto res =
|
const auto systime = std::chrono::system_clock::to_time_t(time);
|
||||||
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 {};
|
|
||||||
|
|
||||||
const auto time = res.front()._when.value();
|
|
||||||
const auto systime = system_clock::to_time_t(time);
|
|
||||||
auto lastLogin = Wt::WDateTime();
|
auto lastLogin = Wt::WDateTime();
|
||||||
lastLogin.setTime_t(systime);
|
lastLogin.setTime_t(systime);
|
||||||
return lastLogin;
|
return lastLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PgUserAuth::logout(const User & user) {
|
void PgUserAuth::logout(const User & user) {
|
||||||
using namespace std::string_literals;
|
_userAuth.logout({user.id()});
|
||||||
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() {
|
AbstractUserDatabase::Transaction * PgUserAuth::startTransaction() {
|
||||||
return new TransactionGuard< decltype(db) >(db, _in_transaction);
|
// return new TransactionGuard< decltype(db) >(db, _in_transaction);
|
||||||
}
|
}
|
||||||
} // namespace eedb::auth
|
} // namespace eedb::auth
|
||||||
|
|||||||
@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
#include <Wt/Auth/AbstractUserDatabase.h>
|
#include <Wt/Auth/AbstractUserDatabase.h>
|
||||||
|
|
||||||
|
#include <eedb/db/data/PgUserAuth.hpp>
|
||||||
|
|
||||||
namespace eedb::db {
|
namespace eedb::db {
|
||||||
class PgConnection;
|
class PgConnection;
|
||||||
|
class PgUserAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Wt {
|
namespace Wt {
|
||||||
class WEnviroment;
|
class WEnvironment;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Wt::Auth {
|
namespace Wt::Auth {
|
||||||
@ -65,6 +68,7 @@ class PgUserAuth : public Wt::Auth::AbstractUserDatabase {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
eedb::db::PgConnection & db;
|
eedb::db::PgConnection & db;
|
||||||
|
eedb::db::PgUserAuth _userAuth;
|
||||||
const Wt::WEnvironment & _env;
|
const Wt::WEnvironment & _env;
|
||||||
const Wt::Auth::AuthService * _authService;
|
const Wt::Auth::AuthService * _authService;
|
||||||
int _in_transaction{0};
|
int _in_transaction{0};
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
set(SOURCE
|
set(SOURCE
|
||||||
|
data/PgCategory.cpp
|
||||||
|
data/PgUser.cpp
|
||||||
|
data/PgUserAuth.cpp
|
||||||
|
data/PgUsers.cpp
|
||||||
|
|
||||||
connection.cpp
|
connection.cpp
|
||||||
config.cpp
|
config.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
INCLUDE_DIRECTORIES( ${PostgreSQL_INCLUDE_DIRS} )
|
|
||||||
|
|
||||||
add_library(eedb_db ${SOURCE})
|
add_library(eedb_db ${SOURCE})
|
||||||
target_link_libraries( eedb_db wt sqlpp-postgresql )
|
target_link_libraries( eedb_db wt sqlpp-postgresql )
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
#include <eedb/data/PgCategory.hpp>
|
#include <eedb/db/data/PgCategory.hpp>
|
||||||
|
|
||||||
#include <eedb/db/model/category.h>
|
#include <eedb/db/model/category.h>
|
||||||
#include <eedb/db/model/stat.h>
|
#include <eedb/db/model/stat.h>
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include <eedb/data/PgUser.hpp>
|
#include <eedb/db/data/PgUser.hpp>
|
||||||
|
|
||||||
#include <eedb/db/connection.hpp>
|
#include <eedb/db/connection.hpp>
|
||||||
|
|
||||||
452
src/eedb/db/data/PgUserAuth.cpp
Normal file
452
src/eedb/db/data/PgUserAuth.cpp
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
#include <eedb/db/data/PgUserAuth.hpp>
|
||||||
|
|
||||||
|
#include <eedb/db/connection.hpp>
|
||||||
|
#include <eedb/db/model/auth_identity.h>
|
||||||
|
#include <eedb/db/model/auth_info.h>
|
||||||
|
#include <eedb/db/model/auth_token.h>
|
||||||
|
#include <eedb/db/model/user.h>
|
||||||
|
#include <eedb/db/model/user_action.h>
|
||||||
|
#include <eedb/db/model/user_audit.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <experimental/string_view>
|
||||||
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
#include <boost/locale.hpp>
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <sqlpp11/sqlpp11.h>
|
||||||
|
|
||||||
|
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<details::PasswordHash> 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 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<details::User> 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
|
||||||
125
src/eedb/db/data/PgUserAuth.hpp
Normal file
125
src/eedb/db/data/PgUserAuth.hpp
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eedb/db/connection.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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 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<details::Token> 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<details::User> 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
|
||||||
@ -1,5 +1,5 @@
|
|||||||
#include <eedb/data/PgUser.hpp>
|
#include <eedb/db/data/PgUser.hpp>
|
||||||
#include <eedb/data/PgUsers.hpp>
|
#include <eedb/db/data/PgUsers.hpp>
|
||||||
|
|
||||||
#include <eedb/db/model/auth_identity.h>
|
#include <eedb/db/model/auth_identity.h>
|
||||||
#include <eedb/db/model/auth_info.h>
|
#include <eedb/db/model/auth_info.h>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <eedb/data/PgCategory.hpp>
|
#include <eedb/db/data/PgCategory.hpp>
|
||||||
|
|
||||||
class PgCategoriesTest : public testing::Test {
|
class PgCategoriesTest : public testing::Test {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <eedb/data/PgCategory.hpp>
|
#include <eedb/db/data/PgCategory.hpp>
|
||||||
|
|
||||||
#include <eedb/db/config.hpp>
|
#include <eedb/db/config.hpp>
|
||||||
#include <eedb/db/connection.hpp>
|
#include <eedb/db/connection.hpp>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user