localized usermanager email messages and message formatting to include site name and link #3794

This commit is contained in:
mostafametwally 2024-02-16 22:04:12 +01:00
parent df2aac3946
commit b68fc6187f
2 changed files with 200 additions and 21 deletions

View File

@ -6,6 +6,7 @@ using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Localization;
using Oqtane.Enums; using Oqtane.Enums;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Models; using Oqtane.Models;
@ -29,8 +30,10 @@ namespace Oqtane.Managers
private readonly ISettingRepository _settings; private readonly ISettingRepository _settings;
private readonly ISyncManager _syncManager; private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly IStringLocalizer<UserManager> _localizer;
private readonly ISiteRepository _siteRepo;
public UserManager(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, IFileRepository files, IProfileRepository profiles, ISettingRepository settings, ISyncManager syncManager, ILogManager logger) public UserManager(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, IFileRepository files, IProfileRepository profiles, ISettingRepository settings, ISyncManager syncManager, ILogManager logger, IStringLocalizer<UserManager> localizer, ISiteRepository siteRepo)
{ {
_users = users; _users = users;
_roles = roles; _roles = roles;
@ -45,6 +48,8 @@ namespace Oqtane.Managers
_settings = settings; _settings = settings;
_syncManager = syncManager; _syncManager = syncManager;
_logger = logger; _logger = logger;
_localizer = localizer;
_siteRepo = siteRepo;
} }
public User GetUser(int userid, int siteid) public User GetUser(int userid, int siteid)
@ -148,21 +153,33 @@ namespace Oqtane.Managers
if (User != null) if (User != null)
{ {
string siteName = _siteRepo.GetSite(user.SiteId).Name;
if (!user.EmailConfirmed) if (!user.EmailConfirmed)
{ {
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; string subject = _localizer["VerificationEmailSubject"];
var notification = new Notification(user.SiteId, User, "User Account Verification", body); subject = subject.Replace("[SiteName]", siteName);
string body = _localizer["VerificationEmailBody"].Value;
body = body.Replace("[UserDisplayName]", user.DisplayName);
body = body.Replace("[URL]", url);
body = body.Replace("[SiteName]", siteName);
var notification = new Notification(alias.SiteId, User, subject, body);
_notifications.AddNotification(notification); _notifications.AddNotification(notification);
} }
else else
{ {
if (!user.SuppressNotification) if (!user.SuppressNotification)
{ {
string url = alias.Protocol + alias.Name; string url = alias.Protocol + alias.Name + "/login";
string body = "Dear " + user.DisplayName + ",\n\nA User Account Has Been Successfully Created For You With The Username " + user.Username + ". Please Visit " + url + " And Use The Login Option To Sign In. If You Do Not Know Your Password, Use The Forgot Password Option On The Login Page To Reset Your Account.\n\nThank You!"; string subject = _localizer["NoVerificationEmailSubject"];
var notification = new Notification(user.SiteId, User, "User Account Notification", body); subject = subject.Replace("[SiteName]", siteName);
string body = _localizer["NoVerificationEmailBody"].Value;
body = body.Replace("[UserDisplayName]", user.DisplayName);
body = body.Replace("[URL]", url);
body = body.Replace("[SiteName]", siteName);
body = body.Replace("[Username]", user.Username);
var notification = new Notification(alias.SiteId, User, subject, body);
_notifications.AddNotification(notification); _notifications.AddNotification(notification);
} }
} }
@ -307,11 +324,17 @@ namespace Oqtane.Managers
user.TwoFactorCode = token; user.TwoFactorCode = token;
user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10); user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10);
_users.UpdateUser(user); _users.UpdateUser(user);
var alias = _tenantManager.GetAlias();
string body = "Dear " + user.DisplayName + ",\n\nYou requested a secure verification code to log in to your account. Please enter the secure verification code on the site:\n\n" + token + string url = alias.Protocol + alias.Name;
"\n\nPlease note that the code is only valid for 10 minutes so if you are unable to take action within that time period, you should initiate a new login on the site." + string siteName = _siteRepo.GetSite(alias.SiteId).Name;
"\n\nThank You!"; string subject = _localizer["TwoFactorEmailSubject"];
var notification = new Notification(user.SiteId, user, "User Verification Code", body); subject = subject.Replace("[SiteName]", siteName);
string body = _localizer["TwoFactorEmailBody"].Value;
body = body.Replace("[UserDisplayName]", user.DisplayName);
body = body.Replace("[URL]", url);
body = body.Replace("[SiteName]", siteName);
body = body.Replace("[Token]", token);
var notification = new Notification(alias.SiteId, user, subject, body);
_notifications.AddNotification(notification); _notifications.AddNotification(notification);
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Verification Notification Sent For {Username}", user.Username); _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Verification Notification Sent For {Username}", user.Username);
@ -355,10 +378,14 @@ namespace Oqtane.Managers
user = _users.GetUser(user.Username); user = _users.GetUser(user.Username);
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser); string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nYou attempted multiple times unsuccessfully to log in to your account and it is now locked out. Please wait a few minutes and then try again... or use the link below to reset your password:\n\n" + url + string siteName = _siteRepo.GetSite(alias.SiteId).Name;
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." + string subject = _localizer["UserLockoutEmailSubject"];
"\n\nThank You!"; subject = subject.Replace("[SiteName]", siteName);
var notification = new Notification(user.SiteId, user, "User Lockout", body); string body = _localizer["UserLockoutEmailBody"].Value;
body = body.Replace("[UserDisplayName]", user.DisplayName);
body = body.Replace("[URL]", url);
body = body.Replace("[SiteName]", siteName);
var notification = new Notification(alias.SiteId, user, subject, body);
_notifications.AddNotification(notification); _notifications.AddNotification(notification);
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Lockout Notification Sent For {Username}", user.Username); _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Lockout Notification Sent For {Username}", user.Username);
} }
@ -404,12 +431,14 @@ namespace Oqtane.Managers
user = _users.GetUser(user.Username); user = _users.GetUser(user.Username);
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser); string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nYou recently requested to reset your password. Please use the link below to complete the process:\n\n" + url + string siteName = _siteRepo.GetSite(alias.SiteId).Name;
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." + string subject = _localizer["ForgotPasswordEmailSubject"];
"\n\nIf you did not request to reset your password you can safely ignore this message." + subject = subject.Replace("[SiteName]", siteName);
"\n\nThank You!"; string body = _localizer["ForgotPasswordEmailBody"].Value;
body = body.Replace("[UserDisplayName]", user.DisplayName);
var notification = new Notification(_tenantManager.GetAlias().SiteId, user, "User Password Reset", body); body = body.Replace("[URL]", url);
body = body.Replace("[SiteName]", siteName);
var notification = new Notification(_tenantManager.GetAlias().SiteId, user, subject, body);
_notifications.AddNotification(notification); _notifications.AddNotification(notification);
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", user.Username); _logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", user.Username);
} }

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ForgotPasswordEmailBody" xml:space="preserve">
<value>Dear [UserDisplayName]&lt;br&gt;&lt;br&gt;You recently requested to reset your password. Please use the link below to complete the process: &lt;b&gt;&lt;a href="[URL]"&gt;&lt;br&gt;&lt;br&gt;Click here to Reset Password&lt;/a&gt;&lt;/b&gt;&lt;br&gt;&lt;br&gt;Please note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site.&lt;br&gt;&lt;br&gt;If you did not request to reset your password you can safely ignore this message.&lt;br&gt;&lt;br&gt;Thank You!&lt;br&gt;[SiteName] team</value>
</data>
<data name="ForgotPasswordEmailSubject" xml:space="preserve">
<value>Password Reset Notification Sent For [SiteName]</value>
</data>
<data name="NoVerificationEmailBody" xml:space="preserve">
<value>Dear [UserDisplayName],&lt;br&gt;&lt;br&gt;A user account has been successfully created for you with the username &lt;b&gt;[Username]&lt;/b&gt;. Please &lt;b&gt;&lt;a href="[URL]"&gt;click here to login&lt;/a&gt;&lt;/b&gt;. If you do not know your password, use the forgot password option on the login page to reset your account.&lt;br&gt;&lt;br&gt;Thank You!&lt;br&gt;[SiteName] Team</value>
</data>
<data name="NoVerificationEmailSubject" xml:space="preserve">
<value>User Account Notification for [SiteName]</value>
</data>
<data name="TwoFactorEmailBody" xml:space="preserve">
<value>Dear [UserDisplayName] + ",&lt;br&gt;&lt;br&gt;You requested a secure verification code to log in to your account. Please enter the secure verification code on the site:&lt;br&gt;&lt;br&gt;&lt;b&gt;[Token] &lt;/b&gt;&lt;br&gt;&lt;br&gt;Please note that the code is only valid for 10 minutes so if you are unable to take action within that time period, you should initiate a new login on the [Alias].&lt;br&gt;&lt;br&gt;Thank You!&lt;br&gt;[SiteName] Team"</value>
</data>
<data name="TwoFactorEmailSubject" xml:space="preserve">
<value>User Verification Code for [SiteName]</value>
</data>
<data name="UserLockoutEmailBody" xml:space="preserve">
<value>Dear [UserDisplayName], &lt;br&gt;&lt;br&gt;You attempted multiple times unsuccessfully to log in to your account and it is now locked out. Please wait a few minutes and then try again... or use the link below to reset your password:&lt;br&gt;&lt;br&gt; &lt;b&gt;&lt;a href=[URL]&gt;Reset Password&lt;/a&gt;&lt;/b&gt;&lt;br&gt;&lt;br&gt;Please note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site &lt;a href="[SiteURL]"&gt;[SiteName]&lt;/a&gt;.&lt;br&gt;&lt;br&gt;Thank You!&lt;br&gt;[SiteName] Team</value>
</data>
<data name="UserLockoutEmailSubject" xml:space="preserve">
<value>User Lockout Notification for [SiteName]</value>
</data>
<data name="VerificationEmailBody" xml:space="preserve">
<value>Dear [UserDisplayName],&lt;br&gt;&lt;br&gt;In order to verify the email address associated to your user account, please click the link below:&lt;br&gt;&lt;br&gt; &lt;b&gt;&lt;a href="[URL]"&gt;Click Here To Verify&lt;/a&gt;&lt;/b&gt; &lt;br&gt;&lt;br&gt;If the link is not displayed please copy and paste the following link to your browser &lt;br&gt;&lt;br&gt; [URL] &lt;br&gt;&lt;br&gt;Thank You!&lt;br&gt;[SiteName] Team</value>
</data>
<data name="VerificationEmailSubject" xml:space="preserve">
<value>Email Verification for [SiteName]</value>
</data>
</root>