From 1573b1f70c6f50574ee0c3375be85e66bc4038cb Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 15 Jun 2020 17:23:44 +0200 Subject: [PATCH] add email for verification with duration in text (for example: you have registered for 10 days), --- src/cpp/controller/EmailVerificationCode.cpp | 2 + src/cpp/controller/EmailVerificationCode.h | 1 + src/cpp/lib/DataTypeConverter.cpp | 32 +++++++++ src/cpp/lib/DataTypeConverter.h | 6 ++ src/cpp/model/email/Email.cpp | 65 ++++++++++++++++--- src/cpp/model/email/Email.h | 25 +++---- src/cpp/model/table/EmailOptIn.cpp | 12 ++-- src/cpp/model/table/EmailOptIn.h | 4 +- src/cpp/tasks/VerificationEmailResendTask.cpp | 59 +++++++++++++++++ src/cpp/tasks/VerificationEmailResendTask.h | 37 +++++++++++ 10 files changed, 217 insertions(+), 26 deletions(-) create mode 100644 src/cpp/tasks/VerificationEmailResendTask.cpp create mode 100644 src/cpp/tasks/VerificationEmailResendTask.h diff --git a/src/cpp/controller/EmailVerificationCode.cpp b/src/cpp/controller/EmailVerificationCode.cpp index 9c5bc1313..21a1b5372 100644 --- a/src/cpp/controller/EmailVerificationCode.cpp +++ b/src/cpp/controller/EmailVerificationCode.cpp @@ -101,4 +101,6 @@ namespace controller { link += std::to_string(getModel()->getCode()); return link; } + + } \ No newline at end of file diff --git a/src/cpp/controller/EmailVerificationCode.h b/src/cpp/controller/EmailVerificationCode.h index 893dd15ac..b8fc49b14 100644 --- a/src/cpp/controller/EmailVerificationCode.h +++ b/src/cpp/controller/EmailVerificationCode.h @@ -24,6 +24,7 @@ namespace controller { inline Poco::AutoPtr getModel() { return _getModel(); } std::string getLink(); + inline Poco::Timespan getAge() { return Poco::DateTime() - getModel()->getCreated(); } protected: EmailVerificationCode(model::table::EmailOptIn* dbModel); diff --git a/src/cpp/lib/DataTypeConverter.cpp b/src/cpp/lib/DataTypeConverter.cpp index 852e64c50..07b7c3a0c 100644 --- a/src/cpp/lib/DataTypeConverter.cpp +++ b/src/cpp/lib/DataTypeConverter.cpp @@ -2,6 +2,7 @@ #include #include "sodium.h" +#include // needed for memset in linux #include @@ -169,4 +170,35 @@ namespace DataTypeConverter mm->releaseMemory(hex); return hexString; } + + std::string convertTimespanToLocalizedString(Poco::Timespan duration, LanguageCatalog* lang) + { + assert(lang); + int value = 0; + std::string result; + std::string unit_name; + if (duration.days() > 0) { + value = duration.days(); + unit_name = "Day"; + } + else if (duration.hours() > 0) { + value = duration.hours(); + unit_name = "Hour"; + } + else if (duration.minutes() > 0) { + value = duration.minutes(); + unit_name = "Minute"; + } + else { + value = duration.seconds(); + unit_name = "Second"; + } + result = std::to_string(value); + result += " "; + std::string unit_plural = unit_name; + unit_plural += "s"; + result += lang->ngettext(unit_name.data(), unit_plural.data(), value); + + return result; + } } \ No newline at end of file diff --git a/src/cpp/lib/DataTypeConverter.h b/src/cpp/lib/DataTypeConverter.h index 5ca654802..b7d6360a8 100644 --- a/src/cpp/lib/DataTypeConverter.h +++ b/src/cpp/lib/DataTypeConverter.h @@ -4,6 +4,9 @@ #include #include "../SingletonManager/MemoryManager.h" +#include "Poco/Timespan.h" +#include "../SingletonManager/LanguageManager.h" + namespace DataTypeConverter { @@ -27,6 +30,9 @@ namespace DataTypeConverter { const char* numberParseStateToString(NumberParseState state); + + //! \brief convert duration in string showing seconds, minutes, hours or days + std::string convertTimespanToLocalizedString(Poco::Timespan duration, LanguageCatalog* lang); }; #endif // __GRADIDO_LOGIN_SERVER_LIB_DATA_TYPE_CONVERTER_H diff --git a/src/cpp/model/email/Email.cpp b/src/cpp/model/email/Email.cpp index 8dbba0657..95586842d 100644 --- a/src/cpp/model/email/Email.cpp +++ b/src/cpp/model/email/Email.cpp @@ -5,6 +5,8 @@ #include "../TransactionBase.h" +#include "../lib/DataTypeConverter.h" + namespace model { const static char EmailText_emailVerification[] = {u8"\ @@ -18,6 +20,28 @@ Mit freundlichen Grüßen\n\ Dario, Gradido Server Admin\n\ "}; +const static char EmailText_emailVerificationResend[] = { u8"\ +Hallo [first_name] [last_name],\n\ +\n\ +Du oder jemand anderes hat sich vor 7 Tagen mit dieser E-Mail Adresse bei Gradido registriert.\n\ +Wenn du es warst, klicke bitte auf den Link: [link]\n\ +oder kopiere den obigen Link in Dein Browserfenster.\n\ +\n\ +Mit freundlichen Grüßen\n\ +Dario, Gradido Server Admin\n\ +" }; + +const static char EmailText_emailVerificationResendAfterLongTime[] = { u8"\ +Hallo [first_name] [last_name],\n\ +\n\ +Du oder jemand anderes hat sich vor [duration] mit dieser E-Mail Adresse bei Gradido registriert.\n\ +Wenn du es warst, klicke bitte auf den Link: [link] um dein Konto zu aktivieren\n\ +oder kopiere den obigen Link in Dein Browserfenster.\n\ +\n\ +Mit freundlichen Grüßen\n\ +Dario, Gradido Server Admin\n\ +" }; + const static char EmailText_emailVerificationOldElopageTransaction[] = { u8"\ Hallo [first_name] [last_name],\n\ \n\ @@ -132,6 +156,7 @@ Gradido Login-Server\n\ mt.setParameter("charset", "utf-8"); const char* messageTemplate = nullptr; + std::string content_string; switch (mType) { case EMAIL_DEFAULT: @@ -147,6 +172,8 @@ Gradido Login-Server\n\ break; case EMAIL_USER_VERIFICATION_CODE: + case EMAIL_USER_VERIFICATION_CODE_RESEND: + case EMAIL_USER_VERIFICATION_CODE_RESEND_AFTER_LONG_TIME: case EMAIL_USER_REGISTER_OLD_ELOPAGE: case EMAIL_ADMIN_USER_VERIFICATION_CODE: case EMAIL_ADMIN_USER_VERIFICATION_CODE_RESEND: @@ -162,7 +189,13 @@ Gradido Login-Server\n\ mailMessage->setSubject(langCatalog->gettext_str("Gradido: E-Mail Verification")); messageTemplate = EmailText_emailVerification; - if (mType == EMAIL_ADMIN_USER_VERIFICATION_CODE) { + if (EMAIL_USER_VERIFICATION_CODE_RESEND == mType) { + messageTemplate = EmailText_emailVerificationResend; + } + else if (EMAIL_USER_VERIFICATION_CODE_RESEND_AFTER_LONG_TIME == mType) { + messageTemplate = EmailText_emailVerificationResendAfterLongTime; + } + else if (mType == EMAIL_ADMIN_USER_VERIFICATION_CODE) { messageTemplate = EmailText_adminEmailVerification; } else if (mType == EMAIL_ADMIN_USER_VERIFICATION_CODE_RESEND) { @@ -172,14 +205,17 @@ Gradido Login-Server\n\ messageTemplate = EmailText_emailVerificationOldElopageTransaction; } - mailMessage->addContent( - new Poco::Net::StringPartSource(replaceUserNamesAndLink( - langCatalog->gettext(messageTemplate), - userTableModel->getFirstName(), - userTableModel->getLastName(), - mEmailVerificationCode->getLink() - ), mt.toString()) + content_string = replaceUserNamesAndLink( + langCatalog->gettext(messageTemplate), + userTableModel->getFirstName(), + userTableModel->getLastName(), + mEmailVerificationCode->getLink() ); + if (EMAIL_USER_VERIFICATION_CODE_RESEND_AFTER_LONG_TIME == mType) { + content_string = replaceDuration(content_string, mEmailVerificationCode->getAge(), langCatalog); + } + mailMessage->addContent(new Poco::Net::StringPartSource(content_string, mt.toString())); + break; case EMAIL_USER_RESET_PASSWORD: if (userTableModel.isNull() || mUser->getModel()->getEmail() == "") { @@ -292,6 +328,19 @@ Gradido Login-Server\n\ return result; } + std::string Email::replaceDuration(std::string src, Poco::Timespan duration, LanguageCatalog* lang) + { + static const char* functionName = "Email::replaceDuration"; + int findPos = src.find("[duration]"); + if (findPos != src.npos) { + src.replace(findPos, 10, DataTypeConverter::convertTimespanToLocalizedString(duration, lang)); + } + else { + addError(new Error(functionName, "no duration placeholder found")); + } + return src; + } + EmailType Email::convertTypeFromInt(int type) { if (type >= (int)EMAIL_MAX || type <= 0) { diff --git a/src/cpp/model/email/Email.h b/src/cpp/model/email/Email.h index 82ac50a47..e5380b2a1 100644 --- a/src/cpp/model/email/Email.h +++ b/src/cpp/model/email/Email.h @@ -24,17 +24,19 @@ namespace model { enum EmailType { - EMAIL_DEFAULT = 1, - EMAIL_ERROR = 2, - EMAIL_USER_VERIFICATION_CODE = 3, - EMAIL_ADMIN_USER_VERIFICATION_CODE = 4, - EMAIL_ADMIN_USER_VERIFICATION_CODE_RESEND = 5, - EMAIL_USER_RESET_PASSWORD = 6, - EMAIL_ADMIN_RESET_PASSWORD_REQUEST_WITHOUT_MEMORIZED_PASSPHRASE = 7, - EMAIL_NOTIFICATION_TRANSACTION_CREATION = 8, - EMAIL_NOTIFICATION_TRANSACTION_TRANSFER = 9, - EMAIL_USER_REGISTER_OLD_ELOPAGE = 10, - EMAIL_MAX = 11 + EMAIL_DEFAULT, + EMAIL_ERROR, + EMAIL_USER_VERIFICATION_CODE, + EMAIL_USER_VERIFICATION_CODE_RESEND, + EMAIL_USER_VERIFICATION_CODE_RESEND_AFTER_LONG_TIME, + EMAIL_ADMIN_USER_VERIFICATION_CODE, + EMAIL_ADMIN_USER_VERIFICATION_CODE_RESEND, + EMAIL_USER_RESET_PASSWORD, + EMAIL_ADMIN_RESET_PASSWORD_REQUEST_WITHOUT_MEMORIZED_PASSPHRASE, + EMAIL_NOTIFICATION_TRANSACTION_CREATION, + EMAIL_NOTIFICATION_TRANSACTION_TRANSFER, + EMAIL_USER_REGISTER_OLD_ELOPAGE, + EMAIL_MAX }; class Email: public ErrorList @@ -57,6 +59,7 @@ namespace model { std::string replaceUserNamesAndLink(const char* src, const std::string& first_name, const std::string& last_name, const std::string& link); std::string replaceEmail(const char* src, const std::string& email); std::string replaceAmount(const char* src, Poco::Int64 gradido_cent); + std::string replaceDuration(std::string src, Poco::Timespan duration, LanguageCatalog* lang); AutoPtr mEmailVerificationCode; AutoPtr mUser; diff --git a/src/cpp/model/table/EmailOptIn.cpp b/src/cpp/model/table/EmailOptIn.cpp index 256366436..844c12e43 100644 --- a/src/cpp/model/table/EmailOptIn.cpp +++ b/src/cpp/model/table/EmailOptIn.cpp @@ -25,7 +25,7 @@ namespace model { } EmailOptIn::EmailOptIn(const EmailOptInTuple& tuple) - : ModelBase(tuple.get<0>()), mUserId(tuple.get<1>()), mEmailVerificationCode(tuple.get<2>()), mType(tuple.get<3>()), mResendCount(tuple.get<4>()) + : ModelBase(tuple.get<0>()), mUserId(tuple.get<1>()), mEmailVerificationCode(tuple.get<2>()), mType(tuple.get<3>()), mCreated(tuple.get<4>()), mResendCount(tuple.get<5>()) { } @@ -53,9 +53,9 @@ namespace model { { Poco::Data::Statement select(session); - select << "SELECT id, user_id, verification_code, email_opt_in_type_id, resend_count FROM " << getTableName() + select << "SELECT id, user_id, verification_code, email_opt_in_type_id, created, resend_count FROM " << getTableName() << " where " << fieldName << " = ?" - , into(mID), into(mUserId), into(mEmailVerificationCode), into(mType), into(mResendCount); + , into(mID), into(mUserId), into(mEmailVerificationCode), into(mType), into(mCreated), into(mResendCount); return select; @@ -76,7 +76,7 @@ namespace model { { Poco::Data::Statement select(session); - select << "SELECT id, user_id, verification_code, email_opt_in_type_id, resend_count FROM " << getTableName() + select << "SELECT id, user_id, verification_code, email_opt_in_type_id, created, resend_count FROM " << getTableName() << " where " << fieldName << " = ?"; @@ -90,7 +90,7 @@ namespace model { throw Poco::NullValueException("EmailOptIn::_loadFromDB fieldNames empty or contain only one field"); } - select << "SELECT user_id, verification_code, email_opt_in_type_id, resend_count FROM " << getTableName() + select << "SELECT user_id, verification_code, email_opt_in_type_id, created, resend_count FROM " << getTableName() << " where " << fieldNames[0] << " = ? "; if (conditionType == MYSQL_CONDITION_AND) { for (int i = 1; i < fieldNames.size(); i++) { @@ -106,7 +106,7 @@ namespace model { addError(new ParamError("EmailOptIn::_loadFromDB", "condition type not implemented", conditionType)); } //<< " where " << fieldName << " = ?" - select , into(mUserId), into(mEmailVerificationCode), into(mType), into(mResendCount); + select , into(mUserId), into(mEmailVerificationCode), into(mType), into(mCreated), into(mResendCount); return select; diff --git a/src/cpp/model/table/EmailOptIn.h b/src/cpp/model/table/EmailOptIn.h index d32564710..af08c20fe 100644 --- a/src/cpp/model/table/EmailOptIn.h +++ b/src/cpp/model/table/EmailOptIn.h @@ -17,7 +17,7 @@ namespace model { EMAIL_OPT_IN_REGISTER_DIRECT = 3 }; - typedef Poco::Tuple EmailOptInTuple; + typedef Poco::Tuple EmailOptInTuple; class EmailOptIn : public ModelBase { @@ -35,6 +35,7 @@ namespace model { inline Poco::UInt64 getCode() const { return mEmailVerificationCode; } inline int getUserId() const { return mUserId; } inline int getResendCount() const { Poco::ScopedLock _lock(mWorkMutex); return mResendCount; } + inline Poco::DateTime getCreated() const { return mCreated; } inline EmailOptInType getType() const { return static_cast(mType);} inline void setCode(Poco::UInt64 code) { mEmailVerificationCode = code; } inline void setUserId(int user_Id) { mUserId = user_Id; } @@ -53,6 +54,7 @@ namespace model { // data type must be a multiple of 4 Poco::UInt64 mEmailVerificationCode; int mType; + Poco::DateTime mCreated; int mResendCount; }; diff --git a/src/cpp/tasks/VerificationEmailResendTask.cpp b/src/cpp/tasks/VerificationEmailResendTask.cpp new file mode 100644 index 000000000..9380527bf --- /dev/null +++ b/src/cpp/tasks/VerificationEmailResendTask.cpp @@ -0,0 +1,59 @@ +#include "VerificationEmailResendTask.h" + +#include "../controller/User.h" +#include "../controller/EmailVerificationCode.h" + +#include "../SingletonManager/EmailManager.h" + +VerificationEmailResendTask::VerificationEmailResendTask(int userId) + : mUserId(userId) + +{ + + +} + +VerificationEmailResendTask::~VerificationEmailResendTask() +{ + +} + + +int VerificationEmailResendTask::run() +{ + auto user = controller::User::create(); + if (1 == user->load(mUserId)) { + auto model = user->getModel(); + // if email is checked, we can exit + if (model->isEmailChecked()) { + return 1; + } + auto email_verification = controller::EmailVerificationCode::load(mUserId, model::table::EMAIL_OPT_IN_REGISTER_DIRECT); + if (nullptr == email_verification) { + email_verification = controller::EmailVerificationCode::create(mUserId, model::table::EMAIL_OPT_IN_REGISTER_DIRECT); + email_verification->getModel()->insertIntoDB(false); + } + auto em = EmailManager::getInstance(); + em->addEmail(new model::Email(email_verification, user, model::EMAIL_USER_VERIFICATION_CODE_RESEND)); + email_verification->getModel()->addResendCountAndUpdate(); + } + return 0; +} + + +VerificationEmailResendTimerTask::VerificationEmailResendTimerTask(int userId) + : mUserId(userId) +{ + +} + +VerificationEmailResendTimerTask::~VerificationEmailResendTimerTask() +{ + +} + +void VerificationEmailResendTimerTask::run() +{ + UniLib::controller::TaskPtr verificationResendTask(new VerificationEmailResendTask(mUserId)); + verificationResendTask->scheduleTask(verificationResendTask); +} \ No newline at end of file diff --git a/src/cpp/tasks/VerificationEmailResendTask.h b/src/cpp/tasks/VerificationEmailResendTask.h new file mode 100644 index 000000000..0d73c5a24 --- /dev/null +++ b/src/cpp/tasks/VerificationEmailResendTask.h @@ -0,0 +1,37 @@ +#ifndef __GRADIDO_LOGIN_SERVER_VERIFICATION_EMAIL_RESEND_TIMER_TASK__H +#define __GRADIDO_LOGIN_SERVER_VERIFICATION_EMAIL_RESEND_TIMER_TASK__H + +#include "CPUTask.h" +#include "Poco/Util/TimerTask.h" + +class VerificationEmailResendTask : public UniLib::controller::CPUTask +{ +public: + VerificationEmailResendTask(int userId); + ~VerificationEmailResendTask(); + + + const char* getResourceType() const { return "VerificationEmailResendTask"; }; + + //! from Poco::Util::TimerTask, called from timer if the time is there + //! load user from db, check if account is activated if not, send the email verification code a second time + int run(); + +protected: + int mUserId; + +}; + +class VerificationEmailResendTimerTask : public Poco::Util::TimerTask +{ +public: + VerificationEmailResendTimerTask(int userId); + ~VerificationEmailResendTimerTask(); + + void run(); + +protected: + int mUserId; +}; + +#endif //__GRADIDO_LOGIN_SERVER_VERIFICATION_EMAIL_RESEND_TIMER_TASK__H