fix #5349 - send verification email if unverified user attempts to login, add ability to enable/disable email verification per site
This commit is contained in:
@ -21,9 +21,7 @@ else
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<br /><br />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
@ -49,15 +47,11 @@ else
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
{
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<br /><br />
|
||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,15 @@
|
||||
<input id="email" class="form-control" @bind="@_email" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
||||
<div class="col-sm-9">
|
||||
@ -120,6 +129,7 @@
|
||||
private bool _initialized = false;
|
||||
private string _username = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _confirmed = "True";
|
||||
private string _displayname = string.Empty;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _notify = "True";
|
||||
@ -169,6 +179,7 @@
|
||||
user.Username = _username;
|
||||
user.Password = ""; // will be auto generated
|
||||
user.Email = _email;
|
||||
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.TimeZoneId = _timezoneid;
|
||||
user.PhotoFileId = null;
|
||||
|
@ -48,7 +48,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Confirmed?</Label>
|
||||
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
|
@ -74,10 +74,19 @@ else
|
||||
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirevalidemail" HelpText="Do you want to require registered users to validate their email address before they are allowed to log in?" ResourceKey="RequireValidEmail">Require Valid Email?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirevalidemail" class="form-select" @bind="@_requirevalidemail">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor?</Label>
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor Authentication?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||
<option value="false">@Localizer["Disabled"]</option>
|
||||
@ -490,6 +499,7 @@ else
|
||||
private string _allowregistration;
|
||||
private string _registerurl;
|
||||
private string _profileurl;
|
||||
private string _requirevalidemail;
|
||||
private string _twofactor;
|
||||
private string _cookiename;
|
||||
private string _cookieexpiration;
|
||||
@ -560,6 +570,7 @@ else
|
||||
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||
_requirevalidemail = SettingService.GetSetting(settings, "LoginOptions:RequireValidEmail", "true");
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
@ -685,6 +696,7 @@ else
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RequireValidEmail", _requirevalidemail, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||
|
@ -133,7 +133,7 @@
|
||||
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
|
||||
</data>
|
||||
<data name="Error.Login.Fail" xml:space="preserve">
|
||||
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification.</value>
|
||||
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That New User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification Containing Further Instructions.</value>
|
||||
</data>
|
||||
<data name="Message.Required.UserInfo" xml:space="preserve">
|
||||
<value>Please Provide All Required Fields</value>
|
||||
|
@ -162,4 +162,10 @@
|
||||
<data name="TimeZone.HelpText" xml:space="preserve">
|
||||
<value>The user's time zone</value>
|
||||
</data>
|
||||
<data name="Confirmed.Text" xml:space="preserve">
|
||||
<value>Verified?</value>
|
||||
</data>
|
||||
<data name="Confirmed.HelpText" xml:space="preserve">
|
||||
<value>Indicates if the user's email is verified</value>
|
||||
</data>
|
||||
</root>
|
@ -217,7 +217,7 @@
|
||||
<value>The user's time zone</value>
|
||||
</data>
|
||||
<data name="Confirmed.Text" xml:space="preserve">
|
||||
<value>Confirmed?</value>
|
||||
<value>Verified?</value>
|
||||
</data>
|
||||
<data name="Confirmed.HelpText" xml:space="preserve">
|
||||
<value>Indicates if the user's email is verified</value>
|
||||
|
@ -370,7 +370,13 @@
|
||||
<value>Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out.</value>
|
||||
</data>
|
||||
<data name="TwoFactor.Text" xml:space="preserve">
|
||||
<value>Two Factor?</value>
|
||||
<value>Two Factor Authentication?</value>
|
||||
</data>
|
||||
<data name="RequireValidEmail.HelpText" xml:space="preserve">
|
||||
<value>Do you want to require registered users to validate their email address before they are allowed to log in?</value>
|
||||
</data>
|
||||
<data name="RequireValidEmail.Text" xml:space="preserve">
|
||||
<value>Require Valid Email?</value>
|
||||
</data>
|
||||
<data name="Disabled" xml:space="preserve">
|
||||
<value>Disabled</value>
|
||||
|
@ -165,14 +165,13 @@ namespace Oqtane.Controllers
|
||||
bool allowregistration;
|
||||
if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin))
|
||||
{
|
||||
user.EmailConfirmed = true;
|
||||
user.IsAuthenticated = true;
|
||||
user.IsAuthenticated = true; // admins can add any existing user to a site
|
||||
allowregistration = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.EmailConfirmed = false;
|
||||
user.IsAuthenticated = false;
|
||||
user.EmailConfirmed = false; // standard users cannot specify that their email is verified
|
||||
user.IsAuthenticated = false; // existing users can only be added to a site if they provide a valid username and password
|
||||
allowregistration = _sites.GetSite(user.SiteId).AllowRegistration;
|
||||
}
|
||||
|
||||
|
@ -228,11 +228,12 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
options.Lockout.AllowedForNewUsers = false;
|
||||
|
||||
// SignIn settings
|
||||
options.SignIn.RequireConfirmedEmail = true;
|
||||
options.SignIn.RequireConfirmedEmail = false;
|
||||
options.SignIn.RequireConfirmedAccount = false;
|
||||
options.SignIn.RequireConfirmedPhoneNumber = false;
|
||||
|
||||
// User settings
|
||||
options.User.RequireUniqueEmail = false; // changing to true will cause issues for legacy data
|
||||
options.User.RequireUniqueEmail = false;
|
||||
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
|
||||
});
|
||||
|
||||
|
@ -180,7 +180,7 @@ namespace Oqtane.Managers
|
||||
if (User != null)
|
||||
{
|
||||
string siteName = _sites.GetSite(user.SiteId).Name;
|
||||
if (!user.EmailConfirmed)
|
||||
if (!user.EmailConfirmed && bool.Parse(_settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:RequireValidEmail", "true")))
|
||||
{
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
@ -252,29 +252,32 @@ namespace Oqtane.Managers
|
||||
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||
}
|
||||
|
||||
if (user.EmailConfirmed)
|
||||
if (bool.Parse(_settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:RequireValidEmail", "true")))
|
||||
{
|
||||
if (!identityuser.EmailConfirmed)
|
||||
if (user.EmailConfirmed)
|
||||
{
|
||||
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
|
||||
if (!identityuser.EmailConfirmed)
|
||||
{
|
||||
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
|
||||
|
||||
string body = "Dear " + user.DisplayName + ",\n\nThe Email Address For Your User Account Has Been Verified. You Can Now Login With Your Username And Password.";
|
||||
string body = "Dear " + user.DisplayName + ",\n\nThe Email Address For Your User Account Has Been Verified. You Can Now Login With Your Username And Password.";
|
||||
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
|
||||
_notifications.AddNotification(notification);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
identityuser.EmailConfirmed = false;
|
||||
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
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!";
|
||||
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
|
||||
_notifications.AddNotification(notification);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
identityuser.EmailConfirmed = false;
|
||||
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
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!";
|
||||
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
|
||||
_notifications.AddNotification(notification);
|
||||
}
|
||||
|
||||
user = _users.UpdateUser(user);
|
||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||
@ -354,15 +357,14 @@ namespace Oqtane.Managers
|
||||
if (!user.IsDeleted)
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
var twoFactorSetting = _settings.GetSetting(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor")?.SettingValue ?? "false";
|
||||
var twoFactorRequired = twoFactorSetting == "required" || user.TwoFactorRequired;
|
||||
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||
var twoFactorRequired = _settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired;
|
||||
if (twoFactorRequired)
|
||||
{
|
||||
var token = await _identityUserManager.GenerateTwoFactorTokenAsync(identityuser, "Email");
|
||||
user.TwoFactorCode = token;
|
||||
user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10);
|
||||
_users.UpdateUser(user);
|
||||
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||
string subject = _localizer["TwoFactorEmailSubject"];
|
||||
subject = subject.Replace("[SiteName]", siteName);
|
||||
string body = _localizer["TwoFactorEmailBody"].Value;
|
||||
@ -377,7 +379,7 @@ namespace Oqtane.Managers
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await _identityUserManager.IsEmailConfirmedAsync(identityuser))
|
||||
if (!bool.Parse(_settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:RequireValidEmail", "true")) || await _identityUserManager.IsEmailConfirmedAsync(identityuser))
|
||||
{
|
||||
user = GetUser(identityuser.UserName, alias.SiteId);
|
||||
if (user != null)
|
||||
@ -400,13 +402,25 @@ namespace Oqtane.Managers
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User {Username} Is Not An Active Member Of Site {SiteId}", user.Username, alias.SiteId);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Denied - User {Username} Is Not An Active Member Of Site {SiteId}", user.Username, alias.SiteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Email Address Not Verified {Username}", user.Username);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Denied - User Email Address Not Verified For {Username}", user.Username);
|
||||
|
||||
// send verification email again
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
string subject = _localizer["VerificationEmailSubject"];
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -538,8 +552,7 @@ namespace Oqtane.Managers
|
||||
if (user != null)
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
var twoFactorSetting = _settings.GetSetting(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor")?.SettingValue ?? "false";
|
||||
var twoFactorRequired = twoFactorSetting == "required" || user.TwoFactorRequired;
|
||||
var twoFactorRequired = _settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired;
|
||||
if (twoFactorRequired && user.TwoFactorCode == token && DateTime.UtcNow < user.TwoFactorExpiry)
|
||||
{
|
||||
user.IsAuthenticated = true;
|
||||
|
@ -16,5 +16,6 @@ namespace Oqtane.Repository
|
||||
void DeleteSetting(string entityName, int settingId);
|
||||
void DeleteSettings(string entityName, int entityId);
|
||||
string GetSettingValue(IEnumerable<Setting> settings, string settingName, string defaultValue);
|
||||
string GetSettingValue(string entityName, int entityId, string settingName, string defaultValue);
|
||||
}
|
||||
}
|
||||
|
@ -180,6 +180,19 @@ namespace Oqtane.Repository
|
||||
}
|
||||
}
|
||||
|
||||
public string GetSettingValue(string entityName, int entityId, string settingName, string defaultValue)
|
||||
{
|
||||
var setting = GetSetting(entityName, entityId, settingName);
|
||||
if (setting != null)
|
||||
{
|
||||
return setting.SettingValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsMaster(string EntityName)
|
||||
{
|
||||
return (EntityName == EntityNames.ModuleDefinition || EntityName == EntityNames.Host);
|
||||
|
Reference in New Issue
Block a user