diff --git a/src/cpp/HTTPInterface/ElopageWebhook.cpp b/src/cpp/HTTPInterface/ElopageWebhook.cpp index 958c7437f..29eff8087 100644 --- a/src/cpp/HTTPInterface/ElopageWebhook.cpp +++ b/src/cpp/HTTPInterface/ElopageWebhook.cpp @@ -3,9 +3,23 @@ #include "Poco/Net/HTTPServerResponse.h" #include "Poco/DeflatingStream.h" #include "Poco/URI.h" +#include "Poco/Data/Binding.h" + +using namespace Poco::Data::Keywords; + +#include "../SingletonManager/ConnectionManager.h" +#include "../SingletonManager/ErrorManager.h" +#include "../SingletonManager/SessionManager.h" #include "../ServerConfig.h" +#include "../tasks/PrepareEmailTask.h" +#include "../tasks/SendEmailTask.h" + +#include "../model/EmailVerificationCode.h" + + + void ElopageWebhook::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) { @@ -94,12 +108,155 @@ HandleElopageRequestTask::HandleElopageRequestTask(Poco::Net::NameValueCollectio { } +bool HandleElopageRequestTask::validateInput() +{ + auto sm = SessionManager::getInstance(); + if (mEmail == "" || !sm->isValid(mEmail, VALIDATE_EMAIL)) { + addError(new Error(__FUNCTION__, "email is invalid or empty")); + return false; + } + if (mFirstName == "" || !sm->isValid(mFirstName, VALIDATE_NAME)) { + addError(new Error(__FUNCTION__, "first name is invalid or empty")); + return false; + } + + if (mLastName == "" || !sm->isValid(mLastName, VALIDATE_NAME)) { + addError(new Error(__FUNCTION__, "last name is invalid or empty")); + return false; + } + + return true; +} + + +void HandleElopageRequestTask::writeUserIntoDB() +{ + auto cm = ConnectionManager::getInstance(); + auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER); + Poco::Data::Statement insert(session); + insert << "INSERT INTO users (email, first_name, last_name) VALUES(?, ?, ?);", + use(mEmail), use(mFirstName), use(mLastName); + try { + insert.execute(); + } + catch (Poco::Exception& ex) { + addError(new ParamError(__FUNCTION__, "mysql error", ex.displayText().data())); + } +} + +int HandleElopageRequestTask::getUserIdFromDB() +{ + auto cm = ConnectionManager::getInstance(); + auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER); + Poco::Data::Statement select(session); + int user_id = 0; + select << "SELECT id from users where email = ?;", + into(user_id), use(mEmail); + try { + select.execute(); + } + catch (Poco::Exception& ex) { + addError(new ParamError(__FUNCTION__, "mysql error selecting from db", ex.displayText().data())); + } + + return user_id; +} + +bool HandleElopageRequestTask::createEmailVerificationCode() +{ + // create email verification code + uint32_t* code_p = (uint32_t*)&mEmailVerificationCode; + for (int i = 0; i < sizeof(mEmailVerificationCode) / 4; i++) { + code_p[i] = randombytes_random(); + } + return mEmailVerificationCode != 0; +} + int HandleElopageRequestTask::run() { - printf("[HandleElopageRequestTask::run]\n"); - for (auto it = mRequestData.begin(); it != mRequestData.end(); it++) { - printf("%s => %s\n", it->first.data(), it->second.data()); + // get input data + + mEmail = mRequestData.get("payer[email]", ""); + mFirstName = mRequestData.get("payer[first_name]", ""); + mLastName = mRequestData.get("payer[last_name]", ""); + std::string order_id = mRequestData.get("order_id", ""); + + addError(new ParamError("HandleElopageRequestTask", "order_id", order_id.data())); + + // validate input + if (!validateInput()) { + // if input is invalid we can stop now + sendErrorsAsEmail(); + return -1; } - printf("[HandleElopageRequestTask::run] end\n"); + + // if user exist we can stop now + if (getUserIdFromDB()) { + sendErrorsAsEmail(); + return -2; + } + + // if user with this email didn't exist + // we can create a new user and send a email to him + + // prepare email in advance + // create connection to email server + UniLib::controller::TaskPtr prepareEmail(new PrepareEmailTask(ServerConfig::g_CPUScheduler)); + prepareEmail->scheduleTask(prepareEmail); + + // write user entry into db + writeUserIntoDB(); + + // get user id from db + int user_id = getUserIdFromDB(); + // we didn't get a user_id, something went wrong + if (!user_id) { + addError(new Error("User loadEntryDBId", "user_id is zero")); + sendErrorsAsEmail(); + return -3; + } + + EmailVerificationCode emailVerification(user_id); + + // create email verification code + if (!emailVerification.getCode()) { + // exit if email verification code is empty + addError(new Error("Email verification", "code is empty, error in random?")); + sendErrorsAsEmail(); + return -4; + } + + // write email verification code into db + UniLib::controller::TaskPtr saveEmailVerificationCode(new ModelInsertTask((ModelBase*)&emailVerification)); + saveEmailVerificationCode->scheduleTask(saveEmailVerificationCode); + + // send email to user + auto message = new Poco::Net::MailMessage; + + message->addRecipient(Poco::Net::MailRecipient(Poco::Net::MailRecipient::PRIMARY_RECIPIENT, mEmail)); + message->setSubject("Gradido: E-Mail Verification"); + std::stringstream ss; + ss << "Hallo " << mFirstName << " " << mLastName << "," << std::endl << std::endl; + ss << "Du oder jemand anderes hat sich soeben mit dieser E-Mail Adresse bei Elopage für Gradido angemeldet. " << std::endl; + ss << "Um dein Gradido Konto anzulegen und deine E-Mail zu bestätigen," << std::endl; + ss << "klicke bitte auf den Link: https://gradido2.dario-rekowski.de/account/checkEmail/" << mEmailVerificationCode << std::endl; + ss << "oder kopiere den Code: " << mEmailVerificationCode << " selbst dort hinein." << std::endl << std::endl; + ss << "Mit freundlichen Grüße" << std::endl; + ss << "Dario, Gradido Server Admin" << std::endl; + + + message->addContent(new Poco::Net::StringPartSource(ss.str())); + + UniLib::controller::TaskPtr sendEmail(new SendEmailTask(message, ServerConfig::g_CPUScheduler, 1)); + sendEmail->setParentTaskPtrInArray(prepareEmail, 0); + sendEmail->setParentTaskPtrInArray(saveEmailVerificationCode, 1); + sendEmail->scheduleTask(sendEmail); + + // if errors occured, send via email + //if (errorCount() > 1) { + sendErrorsAsEmail(); + //} + + return 0; } \ No newline at end of file diff --git a/src/cpp/HTTPInterface/ElopageWebhook.h b/src/cpp/HTTPInterface/ElopageWebhook.h index 1af5fa587..2f926b515 100644 --- a/src/cpp/HTTPInterface/ElopageWebhook.h +++ b/src/cpp/HTTPInterface/ElopageWebhook.h @@ -4,6 +4,7 @@ #include "Poco/Net/HTTPRequestHandler.h" #include "../tasks/CPUTask.h" +#include "../model/ErrorList.h" #include "Poco/Net/NameValueCollection.h" @@ -13,7 +14,7 @@ public: void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response); }; -class HandleElopageRequestTask : public UniLib::controller::CPUTask +class HandleElopageRequestTask : public UniLib::controller::CPUTask, protected ErrorList { public: HandleElopageRequestTask(Poco::Net::NameValueCollection& requestData); @@ -23,7 +24,18 @@ public: int run(); protected: - Poco::Net::NameValueCollection mRequestData; + + // return true if at least one entry in db with this email exist + bool validateInput(); + void writeUserIntoDB(); + int getUserIdFromDB(); + bool createEmailVerificationCode(); + + Poco::Net::NameValueCollection mRequestData; + std::string mEmail; + std::string mFirstName; + std::string mLastName; + Poco::UInt64 mEmailVerificationCode; }; diff --git a/src/cpp/SingletonManager/ErrorManager.cpp b/src/cpp/SingletonManager/ErrorManager.cpp index 2cbb742a1..43de78cfe 100644 --- a/src/cpp/SingletonManager/ErrorManager.cpp +++ b/src/cpp/SingletonManager/ErrorManager.cpp @@ -4,37 +4,8 @@ #include "Poco/Net/SecureSMTPClientSession.h" #include "Poco/Net/StringPartSource.h" -SendErrorMessage::~SendErrorMessage() -{ - if (mMessage) { - delete mMessage; - mMessage = nullptr; - } -} +#include "../model/ErrorList.h" -int SendErrorMessage::run() -{ - auto mailClientSession = new Poco::Net::SecureSMTPClientSession(ServerConfig::g_EmailAccount.url, ServerConfig::g_EmailAccount.port); - mailClientSession->login(); - mailClientSession->startTLS(ServerConfig::g_SSL_CLient_Context); - - - mailClientSession->login(Poco::Net::SMTPClientSession::AUTH_LOGIN, ServerConfig::g_EmailAccount.username, ServerConfig::g_EmailAccount.password); - - try { - mMessage->setSender(ServerConfig::g_EmailAccount.sender); - mailClientSession->sendMessage(*mMessage); - mailClientSession->close(); - } - catch (Poco::Exception& exc) { - printf("[SendErrorMessage::%s] error sending error message to admin: %s\n", - __FUNCTION__, exc.displayText().data()); - return -1; - } - return 0; -} - -// ------------------------------------------------------------------------------------ ErrorManager* ErrorManager::getInstance() { diff --git a/src/cpp/SingletonManager/ErrorManager.h b/src/cpp/SingletonManager/ErrorManager.h index 442ccdbd8..870dc8006 100644 --- a/src/cpp/SingletonManager/ErrorManager.h +++ b/src/cpp/SingletonManager/ErrorManager.h @@ -38,7 +38,6 @@ public: protected: ErrorManager(); - // access mutex Poco::Mutex mWorkingMutex; std::map*> mErrorsMap; @@ -46,20 +45,6 @@ protected: }; -class SendErrorMessage : public UniLib::controller::CPUTask -{ -public: - SendErrorMessage(Poco::Net::MailMessage* message, UniLib::controller::CPUSheduler* scheduler) - : UniLib::controller::CPUTask(scheduler), mMessage(message) {} - ~SendErrorMessage(); - - virtual int run(); - - -protected: - Poco::Net::MailMessage* mMessage; - -}; #endif //DR_LUA_WEB_MODULE_CONNECTION_MANAGER_H \ No newline at end of file diff --git a/src/cpp/model/EmailVerificationCode.cpp b/src/cpp/model/EmailVerificationCode.cpp new file mode 100644 index 000000000..ebb34a48d --- /dev/null +++ b/src/cpp/model/EmailVerificationCode.cpp @@ -0,0 +1,60 @@ +#include "EmailVerificationCode.h" + +#include "sodium.h" + +using namespace Poco::Data::Keywords; + +EmailVerificationCode::EmailVerificationCode(int user_id) + : mUserId(user_id), mEmailVerificationCode(0) +{ + createEmailVerificationCode(); +} + +EmailVerificationCode::EmailVerificationCode() + : mUserId(0), mEmailVerificationCode(0) +{ + +} + +EmailVerificationCode::~EmailVerificationCode() +{ + +} + + +void EmailVerificationCode::createEmailVerificationCode() +{ + uint32_t* code_p = (uint32_t*)&mEmailVerificationCode; + for (int i = 0; i < sizeof(mEmailVerificationCode) / 4; i++) { + code_p[i] = randombytes_random(); + } +} + +Poco::Data::Statement EmailVerificationCode::insertIntoDB(Poco::Data::Session session) +{ + Poco::Data::Statement insert(session); + + lock(); + insert << "INSERT INTO " << getTableName() + << " (user_id, verification_code) VALUES(?,?)" + , bind(mUserId), bind(mEmailVerificationCode); + unlock(); + return insert; +} + +Poco::Data::Statement EmailVerificationCode::updateIntoDB(Poco::Data::Session session) +{ + throw Poco::Exception("EmailVerificationCode::updateIntoDB not implemented"); +} + + +Poco::Data::Statement EmailVerificationCode::loadFromDB(Poco::Data::Session session, std::string& fieldName) +{ + Poco::Data::Statement select(session); + + select << "SELECT user_id, verification_code FROM " << getTableName() + << " where " << fieldName << " = ?" + , into(mUserId), into(mEmailVerificationCode); + + return select; +} \ No newline at end of file diff --git a/src/cpp/model/EmailVerificationCode.h b/src/cpp/model/EmailVerificationCode.h new file mode 100644 index 000000000..0baf88038 --- /dev/null +++ b/src/cpp/model/EmailVerificationCode.h @@ -0,0 +1,33 @@ +#ifndef GRADIDO_LOGIN_SERVER_MODEL_EMAIL_VERIFICATION_CODE_INCLUDE +#define GRADIDO_LOGIN_SERVER_MODEL_EMAIL_VERIFICATION_CODE_INCLUDE + +#define EMAIL_VERIFICATION_CODE_SIZE 8 + +#include "ModelBase.h" +#include "Poco/Types.h" + + +class EmailVerificationCode : public ModelBase +{ +public: + EmailVerificationCode(int user_id); + EmailVerificationCode(); + ~EmailVerificationCode(); + + // generic db operations + const char* getTableName() { return "email_opt_in"; } + Poco::Data::Statement insertIntoDB(Poco::Data::Session session); + Poco::Data::Statement updateIntoDB(Poco::Data::Session session); + Poco::Data::Statement loadFromDB(Poco::Data::Session session, std::string& fieldName); + + + inline Poco::UInt64 getCode() const { return mEmailVerificationCode; } +protected: + void createEmailVerificationCode(); + + // data type must be a multiple of 4 + Poco::UInt64 mEmailVerificationCode; + int mUserId; +}; + +#endif //GRADIDO_LOGIN_SERVER_MODEL_EMAIL_VERIFICATION_CODE_INCLUDE \ No newline at end of file diff --git a/src/cpp/model/ErrorList.cpp b/src/cpp/model/ErrorList.cpp index 0232e440c..5d5651a2d 100644 --- a/src/cpp/model/ErrorList.cpp +++ b/src/cpp/model/ErrorList.cpp @@ -1,5 +1,42 @@ #include "ErrorList.h" +#include "../ServerConfig.h" + +#include "Poco/Net/MailMessage.h" + +SendErrorMessage::~SendErrorMessage() +{ + if (mMessage) { + delete mMessage; + mMessage = nullptr; + } +} + +int SendErrorMessage::run() +{ + auto mailClientSession = new Poco::Net::SecureSMTPClientSession(ServerConfig::g_EmailAccount.url, ServerConfig::g_EmailAccount.port); + mailClientSession->login(); + mailClientSession->startTLS(ServerConfig::g_SSL_CLient_Context); + + + mailClientSession->login(Poco::Net::SMTPClientSession::AUTH_LOGIN, ServerConfig::g_EmailAccount.username, ServerConfig::g_EmailAccount.password); + + try { + mMessage->setSender(ServerConfig::g_EmailAccount.sender); + mailClientSession->sendMessage(*mMessage); + mailClientSession->close(); + } + catch (Poco::Exception& exc) { + printf("[SendErrorMessage::%s] error sending error message to admin: %s\n", + __FUNCTION__, exc.displayText().data()); + return -1; + } + return 0; +} + +// ------------------------------------------------------------------------------------ + + ErrorList::ErrorList() { @@ -79,4 +116,26 @@ std::string ErrorList::getErrorsHtml() } res += ""; return res; +} + + +void ErrorList::sendErrorsAsEmail() +{ + auto message = new Poco::Net::MailMessage(); + message->setSender("gradido_loginServer@gradido.net"); + message->addRecipient(Poco::Net::MailRecipient(Poco::Net::MailRecipient::PRIMARY_RECIPIENT, "***REMOVED***")); + message->setSubject("Error from Gradido Login Server"); + + std::string content; + while (mErrorStack.size() > 0) { + auto error = mErrorStack.top(); + mErrorStack.pop(); + content += error->getString(); + delete error; + } + + message->addContent(new Poco::Net::StringPartSource(content)); + UniLib::controller::TaskPtr sendErrorMessageTask(new SendErrorMessage(message, ServerConfig::g_CPUScheduler)); + sendErrorMessageTask->scheduleTask(sendErrorMessageTask); + } \ No newline at end of file diff --git a/src/cpp/model/ErrorList.h b/src/cpp/model/ErrorList.h index 5ee7a8cd1..543ade706 100644 --- a/src/cpp/model/ErrorList.h +++ b/src/cpp/model/ErrorList.h @@ -13,6 +13,11 @@ #include "Error.h" #include +#include "../tasks/CPUTask.h" + +#include "Poco/Net/SecureSMTPClientSession.h" +#include "Poco/Net/StringPartSource.h" + class ErrorList : public IErrorCollection { public: @@ -38,8 +43,26 @@ public: void printErrors(); std::string getErrorsHtml(); + void sendErrorsAsEmail(); + protected: std::stack mErrorStack; }; +class SendErrorMessage : public UniLib::controller::CPUTask +{ +public: + SendErrorMessage(Poco::Net::MailMessage* message, UniLib::controller::CPUSheduler* scheduler) + : UniLib::controller::CPUTask(scheduler), mMessage(message) {} + + ~SendErrorMessage(); + + virtual int run(); + + +protected: + Poco::Net::MailMessage* mMessage; + +}; + #endif // DR_LUA_WEB_MODULE_ERROR_ERROR_LIST_H diff --git a/src/cpp/model/ModelBase.cpp b/src/cpp/model/ModelBase.cpp new file mode 100644 index 000000000..627be5bb3 --- /dev/null +++ b/src/cpp/model/ModelBase.cpp @@ -0,0 +1,50 @@ +#include "ModelBase.h" + +#include "../ServerConfig.h" + +#include "../SingletonManager/ConnectionManager.h" + +ModelInsertTask::ModelInsertTask(Poco::AutoPtr model) + : UniLib::controller::CPUTask(ServerConfig::g_CPUScheduler), mModel(model) +{ +} + +int ModelInsertTask::run() +{ + auto session = ConnectionManager::getInstance()->getConnection(CONNECTION_MYSQL_LOGIN_SERVER); + auto insert = mModel->insertIntoDB(session); + + try { + insert.execute(); + } + catch (Poco::Exception& ex) { + mModel->lock(); + mModel->addError(new ParamError(mModel->getTableName(), "mysql error by inserting", ex.displayText().data())); + mModel->unlock(); + } + return 0; +} + +// --------------------------------------------------------------------------------------------------- + +void ModelBase::duplicate() +{ + lock(); + mReferenceCount++; + //printf("[ModelBase::duplicate] new value: %d\n", mReferenceCount); + unlock(); +} + +void ModelBase::release() +{ + lock(); + mReferenceCount--; + //printf("[ModelBase::release] new value: %d\n", mReferenceCount); + if (0 == mReferenceCount) { + unlock(); + delete this; + return; + } + unlock(); + +} \ No newline at end of file diff --git a/src/cpp/model/ModelBase.h b/src/cpp/model/ModelBase.h new file mode 100644 index 000000000..9d498301f --- /dev/null +++ b/src/cpp/model/ModelBase.h @@ -0,0 +1,51 @@ +#ifndef GRADIDO_LOGIN_SERVER_MODEL_INTERFACE_INCLUDE +#define GRADIDO_LOGIN_SERVER_MODEL_INTERFACE_INCLUDE + +#include "Poco/Data/Session.h" + +#include "../tasks/MultithreadContainer.h" +#include "../tasks/CPUTask.h" + +#include "../MySQL/MysqlTable.h" + + + +class ModelBase : public UniLib::lib::MultithreadContainer, public ErrorList +{ +public: + ModelBase(int id) :mID(id), mReferenceCount(1) {} + ModelBase() : mID(0), mReferenceCount(1) {} + + virtual const char* getTableName() = 0; + virtual Poco::Data::Statement insertIntoDB(Poco::Data::Session session) = 0; + virtual Poco::Data::Statement updateIntoDB(Poco::Data::Session session) = 0; + virtual Poco::Data::Statement loadFromDB(Poco::Data::Session session, std::string& fieldName) = 0; + + inline void setID(int id) { lock(); mID = id; unlock(); } + inline int getID() { lock(); int id = mID; unlock(); return id; } + + // for poco auto ptr + void duplicate(); + void release(); +protected: + int mID; + + // for poco auto ptr + int mReferenceCount; + +}; + +class ModelInsertTask : public UniLib::controller::CPUTask +{ +public: + ModelInsertTask(Poco::AutoPtr model); + + int run(); + const char* getResourceType() const { return "ModelInsertTask"; }; + +protected: + Poco::AutoPtr mModel; + +}; + +#endif //GRADIDO_LOGIN_SERVER_MODEL_INTERFACE_INCLUDE \ No newline at end of file diff --git a/src/cpp/model/Session.cpp b/src/cpp/model/Session.cpp index 198873982..f86c31f69 100644 --- a/src/cpp/model/Session.cpp +++ b/src/cpp/model/Session.cpp @@ -518,7 +518,7 @@ const char* Session::translateSessionStateToString(SessionStates state) void Session::createEmailVerificationCode() { uint32_t* code_p = (uint32_t*)&mEmailVerificationCode; - for (int i = 0; i < EMAIL_VERIFICATION_CODE_SIZE / 4; i++) { + for (int i = 0; i < sizeof(mEmailVerificationCode) / 4; i++) { code_p[i] = randombytes_random(); } diff --git a/src/cpp/model/Session.h b/src/cpp/model/Session.h index c3b5f8316..7bf10cf84 100644 --- a/src/cpp/model/Session.h +++ b/src/cpp/model/Session.h @@ -21,7 +21,7 @@ #include "Poco/Net/IPAddress.h" #include "Poco/Net/HTTPCookie.h" -#define EMAIL_VERIFICATION_CODE_SIZE 8 + class WriteEmailVerification; diff --git a/src/cpp/model/User.cpp b/src/cpp/model/User.cpp index 7a0c79fb1..2f8f9d3f3 100644 --- a/src/cpp/model/User.cpp +++ b/src/cpp/model/User.cpp @@ -453,8 +453,15 @@ Poco::Data::Statement User::insertIntoDB(Poco::Data::Session session) //Poco::Data::BLOB pwd(&mPasswordHashed[0], crypto_shorthash_BYTES); //printf("[User::insertIntoDB] password hashed: %llu\n", mPasswordHashed); - insert << "INSERT INTO users (email, first_name, last_name, password) VALUES(?, ?, ?, ?);", - use(mEmail), use(mFirstName), use(mLastName), bind(mPasswordHashed); + if (mPasswordHashed) { + insert << "INSERT INTO users (email, first_name, last_name, password) VALUES(?, ?, ?, ?);", + use(mEmail), use(mFirstName), use(mLastName), bind(mPasswordHashed); + } + else { + insert << "INSERT INTO users (email, first_name, last_name) VALUES(?, ?, ?);", + use(mEmail), use(mFirstName), use(mLastName); + } + return insert; } diff --git a/src/cpp/model/UserPassphrase.cpp b/src/cpp/model/UserPassphrase.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/cpp/model/UserPassphrase.h b/src/cpp/model/UserPassphrase.h new file mode 100644 index 000000000..e69de29bb