diff --git a/src/AuthService.cpp b/src/AuthService.cpp index a3f62d9..388ed7c 100644 --- a/src/AuthService.cpp +++ b/src/AuthService.cpp @@ -20,6 +20,8 @@ #include "KafkaManager.h" #include "Kafka_topics.h" +#include "SMTPMailerService.h" + namespace uCentral { class AuthService *AuthService::instance_ = nullptr; @@ -278,6 +280,26 @@ namespace uCentral { return uCentral::Utils::ToHex(SHA2_.digest()); } + bool AuthService::SendEmailToUser(const std::string &Email, EMAIL_REASON Reason) { + switch(Reason) { + case FORGOT_PASSWORD: { + MessageAttributes Attrs; + + Attrs[RECIPIENT_EMAIL] = "stephane.bourque@gmail.com"; + Attrs[LOGO] = "logo.jpg"; + + SMTPMailerService()->SendMessage("stephane.bourque@gmail.com", Attrs); } + break; + + case EMAIL_VERIFICATION: + break; + + default: + break; + } + return false; + } + bool AuthService::IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo) { return true; } diff --git a/src/AuthService.h b/src/AuthService.h index 86ebe91..7060d67 100644 --- a/src/AuthService.h +++ b/src/AuthService.h @@ -44,6 +44,11 @@ namespace uCentral{ INTERNAL_ERROR }; + enum EMAIL_REASON { + FORGOT_PASSWORD, + EMAIL_VERIFICATION + }; + static ACCESS_TYPE IntToAccessType(int C); static int AccessTypeToInt(ACCESS_TYPE T); @@ -75,6 +80,8 @@ namespace uCentral{ [[nodiscard]] bool UpdatePassword(const std::string &Admin, const std::string &UserName, const std::string & OldPassword, const std::string &NewPassword); [[nodiscard]] std::string ResetPassword(const std::string &Admin, const std::string &UserName); + bool SendEmailToUser(const std::string &Email, EMAIL_REASON Reason); + private: static AuthService *instance_; bool Secure_ = false ; diff --git a/src/RESTAPI_oauth2Handler.cpp b/src/RESTAPI_oauth2Handler.cpp index 6389356..c7cbce4 100644 --- a/src/RESTAPI_oauth2Handler.cpp +++ b/src/RESTAPI_oauth2Handler.cpp @@ -30,6 +30,19 @@ namespace uCentral { auto password = GetS(uCentral::RESTAPI::Protocol::PASSWORD, Obj); auto newPassword = GetS(uCentral::RESTAPI::Protocol::NEWPASSWORD, Obj); + ParseParameters(Request); + + if(GetParameter("forgotPassword","false") == "true") { + // Send an email to the userId + SecurityObjects::UserInfoAndPolicy UInfo; + AuthService()->SendEmailToUser(userId,AuthService::FORGOT_PASSWORD); + UInfo.webtoken.userMustChangePassword=true; + Poco::JSON::Object ReturnObj; + UInfo.webtoken.to_json(ReturnObj); + ReturnObject(Request, ReturnObj, Response); + return; + } + Poco::toLowerInPlace(userId); SecurityObjects::UserInfoAndPolicy UInfo; diff --git a/src/SMTPMailerService.cpp b/src/SMTPMailerService.cpp index 3a920d4..5d45e95 100644 --- a/src/SMTPMailerService.cpp +++ b/src/SMTPMailerService.cpp @@ -2,6 +2,7 @@ // Created by stephane bourque on 2021-06-17. // #include +#include #include "Poco/Net/MailMessage.h" #include "Poco/Net/MailRecipient.h" @@ -16,6 +17,7 @@ #include "Poco/Net/AcceptCertificateHandler.h" #include "SMTPMailerService.h" +#include "Utils.h" #include "Daemon.h" namespace uCentral { @@ -27,45 +29,98 @@ namespace uCentral { SenderLoginUserName_ = Daemon()->ConfigGetString("mailer.username"); SenderLoginPassword_ = Daemon()->ConfigGetString("mailer.password"); LoginMethod_ = Daemon()->ConfigGetString("mailer.loginmethod"); - MailHostPort_ = Daemon()->ConfigGetInt("mailer.port"); + MailHostPort_ = (int)Daemon()->ConfigGetInt("mailer.port"); + TemplateDir_ = Daemon()->ConfigPath("mailer.templates", Daemon()->DataDir()); + SenderThr_.start(*this); return 0; } void SMTPMailerService::Stop() { - + Running_ = false; + SenderThr_.wakeUp(); + SenderThr_.join(); } - bool SMTPMailerService::SendMessage(SMTPMailerService::MessageAttributes Attrs) { + bool SMTPMailerService::SendMessage(const std::string &Recipient, const MessageAttributes &Attrs) { + SubMutexGuard G(Mutex_); + + uint64_t Now = std::time(nullptr); + auto CE = Cache_.find(Poco::toLower(Recipient)); + if(CE!=Cache_.end()) { + // only allow messages to the same user within 2 minutes + if((CE->second.LastRequest-Now)<30) + return false; + if((CE->second.HowManyRequests>30)) + return false; + } + + Messages_.push_back(MessageEvent{.Posted=(uint64_t )std::time(nullptr), + .LastTry=0, + .Attrs=Attrs}); + return false; } - bool SMTPMailerService::SendIt() { + void SMTPMailerService::run() { + + Running_ = true; + while(Running_) { + Poco::Thread::trySleep(2000); + + { + SubMutexGuard G(Mutex_); + + uint64_t Now = std::time(nullptr); + + for(auto &i:Messages_) { + if(i.Sent==0 && (i.LastTry==0 || (Now-i.LastTry)>120)) { + if (SendIt(i.Attrs)) { + i.LastTry = i.Sent = std::time(nullptr); + } else + i.LastTry = std::time(nullptr); + } + } + + // Clean the list + std::remove_if(Messages_.begin(),Messages_.end(),[Now](MessageEvent &E){ return (E.Sent!=0 || ((Now-E.LastTry)>(24*60*60)));}); + } + } + } + + bool SMTPMailerService::SendIt(const MessageAttributes & Attrs) { try { Poco::SharedPtr ptrHandler = new Poco::Net::AcceptCertificateHandler(false); - /* - Poco::Net::MailMessage message; - message.setSender(Sender_); - message.addRecipient(MailRecipient(Poco::Net::MailRecipient::PRIMARY_RECIPIENT, recipient)); - message.setSubject("Hello from the POCO C++ Libraries"); + Poco::Net::MailMessage Message; + std::string Recipient = Attrs.find(RECIPIENT_EMAIL)->second; + Message.setSender(Sender_); + Message.addRecipient(Poco::Net::MailRecipient(Poco::Net::MailRecipient::PRIMARY_RECIPIENT, Recipient)); + Message.setSubject("Hello from the POCO C++ Libraries"); std::string content; content += "Hello "; - content += recipient; + content += Recipient; content += ",\r\n\r\n"; content += "This is a greeting from the POCO C++ Libraries.\r\n\r\n"; - std::string logo(reinterpret_cast(PocoLogo), sizeof(PocoLogo)); - message.addContent(new Poco::Net::StringPartSource(content)); - message.addAttachment("logo", new Poco::Net::StringPartSource(logo, "image/gif")); + Message.addContent(new Poco::Net::StringPartSource(content)); + auto Logo = Attrs.find(LOGO); + if(Logo!=Attrs.end()) { + Poco::File LogoFile(TemplateDir_ + "/" + Logo->second); + std::ifstream IF(LogoFile.path()); + std::ostringstream OS; + Poco::StreamCopier::copyStream(IF, OS); + Message.addAttachment("logo", new Poco::Net::StringPartSource(OS.str(), "image/jpeg")); + } + Poco::Net::SecureSMTPClientSession session(MailHost_,MailHostPort_); - Poco::Net::Context::Params P; - auto ptrContext = new Poco::Net::Context( Poco::Net::Context::CLIENT_USE, "", "", "", + auto ptrContext = Poco::AutoPtr + (new Poco::Net::Context(Poco::Net::Context::CLIENT_USE, "", "", "", Poco::Net::Context::VERIFY_RELAXED, 9, true, - "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH")); Poco::Net::SSLManager::instance().initializeClient(nullptr, ptrHandler, @@ -78,17 +133,15 @@ namespace uCentral { SenderLoginUserName_, SenderLoginPassword_ ); - session.sendMessage(message); + session.sendMessage(Message); session.close(); - */ + return true; } - catch (const Poco::Exception& exc) + catch (const Poco::Exception& E) { - std::cerr << exc.displayText() << std::endl; - return 1; + Logger_.log(E); } - return 0; - + return false; } } \ No newline at end of file diff --git a/src/SMTPMailerService.h b/src/SMTPMailerService.h index de1f5a5..17feb30 100644 --- a/src/SMTPMailerService.h +++ b/src/SMTPMailerService.h @@ -8,50 +8,91 @@ #include "SubSystemServer.h" namespace uCentral { - class SMTPMailerService : public SubSystemServer { - public: - enum MESSAGE_ATTRIBUTES { - RECIPIENT_EMAIL, - RECIPIENT_FIRST_NAME, - RECIPIENT_LAST_NAME, - RECIPIENT_INITIALS, - RECIPIENT_FULL_NAME, - RECIPIENT_SALUTATION, - SUBJECT, - SIGNATURE, - TEMPLATE, - LOGO - }; - typedef std::map MessageAttributes; + enum MESSAGE_ATTRIBUTES { + RECIPIENT_EMAIL, + RECIPIENT_FIRST_NAME, + RECIPIENT_LAST_NAME, + RECIPIENT_INITIALS, + RECIPIENT_FULL_NAME, + RECIPIENT_SALUTATION, + ACTION_LINK, + SUBJECT, + TEMPLATE_TXT, + TEMPLATE_HTML, + LOGO + }; - static SMTPMailerService *instance() { - if (instance_ == nullptr) { - instance_ = new SMTPMailerService; + static const std::map + MessageAttributeMap{ { RECIPIENT_EMAIL,"RECIPIENT_EMAIL"}, + { RECIPIENT_FIRST_NAME, "RECIPIENT_FIRST_NAME"}, + { RECIPIENT_LAST_NAME, "RECIPIENT_LAST_NAME"}, + { RECIPIENT_INITIALS, "RECIPIENT_INITIALS"}, + { RECIPIENT_FULL_NAME, "RECIPIENT_FULL_NAME"}, + { RECIPIENT_SALUTATION, "RECIPIENT_SALUTATION"}, + { ACTION_LINK, "ACTION_LINK"}, + { SUBJECT, "SUBJECT"}, + { TEMPLATE_TXT, "TEMPLATE_TXT"}, + { TEMPLATE_HTML, "TEMPLATE_HTML"}, + { LOGO, "LOGO"}}; + + inline const std::string & MessageAttributeToVar(MESSAGE_ATTRIBUTES Attr) { + static const std::string EmptyString{}; + auto E = MessageAttributeMap.find(Attr); + if(E == MessageAttributeMap.end()) + return EmptyString; + return E->second; + } + typedef std::map MessageAttributes; + + class SMTPMailerService : public SubSystemServer, Poco::Runnable { + public: + static SMTPMailerService *instance() { + if (instance_ == nullptr) { + instance_ = new SMTPMailerService; + } + return instance_; } - return instance_; - } - int Start() override; - void Stop() override; - bool SendMessage(MessageAttributes Attrs); - bool SendIt(); + struct MessageEvent { + uint64_t Posted=0; + uint64_t LastTry=0; + uint64_t Sent=0; + MessageAttributes Attrs; + }; - private: - static SMTPMailerService * instance_; - std::string MailHost_; - std::string Sender_; - int MailHostPort_=25; - std::string SenderLoginUserName_; - std::string SenderLoginPassword_; - std::string LoginMethod_ = "login"; - std::string LogoFileName_; + struct MessageCacheEntry { + uint64_t LastRequest=0; + uint64_t HowManyRequests=0; + }; - SMTPMailerService() noexcept: - SubSystemServer("SMTPMailer", "MAILER-SVR", "smtpmailer") - { - std::string E{"SHA512"}; - } + void run() override; + + int Start() override; + void Stop() override; + bool SendMessage(const std::string &Recipient, const MessageAttributes &Attrs); + bool SendIt(const MessageAttributes &Attrs); + + private: + static SMTPMailerService * instance_; + std::string MailHost_; + std::string Sender_; + int MailHostPort_=25; + std::string SenderLoginUserName_; + std::string SenderLoginPassword_; + std::string LoginMethod_ = "login"; + std::string LogoFileName_; + std::string TemplateDir_; + std::list Messages_; + std::map Cache_; + Poco::Thread SenderThr_; + std::atomic_bool Running_=false; + + SMTPMailerService() noexcept: + SubSystemServer("SMTPMailer", "MAILER-SVR", "smtpmailer") + { + std::string E{"SHA512"}; + } }; inline SMTPMailerService * SMTPMailerService() { return SMTPMailerService::instance(); } diff --git a/src/Utils.cpp b/src/Utils.cpp index ace5439..d696768 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -442,4 +442,28 @@ namespace uCentral::Utils { return "application/octet-stream"; } + std::string BinaryFileToHexString(const Poco::File &F) { + static const char hex[] = "0123456789abcdef"; + std::string Result; + try { + std::ifstream IF(F.path()); + + int Count = 0; + while (IF.good()) { + if (Count) + Result += ", "; + if ((Count % 32) == 0) + Result += "\r\n"; + Count++; + unsigned char C = IF.get(); + Result += "0x"; + Result += (char) (hex[(C & 0xf0) >> 4]); + Result += (char) (hex[(C & 0x0f)]); + } + } catch(...) { + + } + return Result; + } + } diff --git a/src/Utils.h b/src/Utils.h index 497d73f..1d05468 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -57,5 +57,6 @@ namespace uCentral::Utils { void ReplaceVariables( std::string & Content , const Types::StringPairVec & P); [[nodiscard]] std::string FindMediaType(const Poco::File &F); + [[nodiscard]] std::string BinaryFileToHexString( const Poco::File &F); } #endif // UCENTRALGW_UTILS_H diff --git a/templates/email_verification.html b/templates/email_verification.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/templates/email_verification.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/templates/email_verification.txt b/templates/email_verification.txt new file mode 100644 index 0000000..e69de29 diff --git a/templates/logo.jpg b/templates/logo.jpg new file mode 100644 index 0000000..6bce07d Binary files /dev/null and b/templates/logo.jpg differ diff --git a/templates/password_reset.html b/templates/password_reset.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/templates/password_reset.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/templates/password_reset.txt b/templates/password_reset.txt new file mode 100644 index 0000000..e69de29