diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index f05dc1d3..80d4f5e4 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -93,45 +93,53 @@ protected override async Task OnInitializedAsync() { - _togglepassword = Localizer["ShowPassword"]; - - if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"])) + try { - _allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]); - } + _togglepassword = Localizer["ShowPassword"]; - if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"])) - { - _allowexternallogin = true; - } - - if (PageState.QueryString.ContainsKey("returnurl")) - { - _returnUrl = PageState.QueryString["returnurl"]; - } - - if (PageState.QueryString.ContainsKey("name")) - { - _username = PageState.QueryString["name"]; - } - - if (PageState.QueryString.ContainsKey("token")) - { - var user = new User(); - user.SiteId = PageState.Site.SiteId; - user.Username = _username; - user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]); - - if (user != null) + if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"])) { - await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username); - AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info); + _allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]); } - else + + if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"])) { - await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username); - AddModuleMessage(Localizer["Message.Account.NotVerfied"], MessageType.Warning); + _allowexternallogin = true; } + + if (PageState.QueryString.ContainsKey("returnurl")) + { + _returnUrl = PageState.QueryString["returnurl"]; + } + + if (PageState.QueryString.ContainsKey("name")) + { + _username = PageState.QueryString["name"]; + } + + if (PageState.QueryString.ContainsKey("token")) + { + var user = new User(); + user.SiteId = PageState.Site.SiteId; + user.Username = _username; + user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]); + + if (user != null) + { + await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username); + AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info); + } + else + { + await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username); + AddModuleMessage(Localizer["Message.Account.NotVerfied"], MessageType.Warning); + } + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading Login {Error}", ex.Message); + AddModuleMessage(Localizer["Error.LoadLogin"], MessageType.Error); } } @@ -145,65 +153,73 @@ private async Task Login() { - validated = true; - var interop = new Interop(JSRuntime); - if (await interop.FormValid(login)) + try { - var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password}; - - if (!twofactor) + validated = true; + var interop = new Interop(JSRuntime); + if (await interop.FormValid(login)) { - user = await UserService.LoginUserAsync(user, false, false); - } - else - { - user = await UserService.VerifyTwoFactorAsync(user, _code); - } + var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password}; - if (user.IsAuthenticated) - { - await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username); - - if (PageState.Runtime == Oqtane.Shared.Runtime.Server) + if (!twofactor) { - // server-side Blazor needs to post to the Login page so that the cookies are set correctly - var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl }; - string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/"); - await interop.SubmitForm(url, fields); + user = await UserService.LoginUserAsync(user, false, false); } else { - var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); - authstateprovider.NotifyAuthenticationChanged(); - NavigationManager.NavigateTo(NavigateUrl(_returnUrl, true)); + user = await UserService.VerifyTwoFactorAsync(user, _code); } - } - else - { - if (user.TwoFactorRequired) + + if (user.IsAuthenticated) { - twofactor = true; - validated = false; - AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info); - } - else - { - if (!twofactor) + await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username); + + if (PageState.Runtime == Oqtane.Shared.Runtime.Server) { - await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username); - AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error); + // server-side Blazor needs to post to the Login page so that the cookies are set correctly + var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl }; + string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/"); + await interop.SubmitForm(url, fields); } else { - await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username); - AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error); + var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); + authstateprovider.NotifyAuthenticationChanged(); + NavigationManager.NavigateTo(NavigateUrl(_returnUrl, true)); + } + } + else + { + if (user.TwoFactorRequired) + { + twofactor = true; + validated = false; + AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info); + } + else + { + if (!twofactor) + { + await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username); + AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error); + } + else + { + await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username); + AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error); + } } } } + else + { + AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning); + } } - else + catch (Exception ex) { - AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning); + await logger.LogError(ex, "Error Performing Login {Error}", ex.Message); + AddModuleMessage(Localizer["Error.Login"], MessageType.Error); } } @@ -214,26 +230,34 @@ private async Task Forgot() { - if (_username != string.Empty) + try { - var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId); - if (user != null) + if (_username != string.Empty) { - await UserService.ForgotPasswordAsync(user); - await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username); - AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info); + var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId); + if (user != null) + { + await UserService.ForgotPasswordAsync(user); + await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username); + AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info); + } + else + { + AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning); + } } else { - AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning); + AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info); } - } - else - { - AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info); - } - StateHasChanged(); + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message); + AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error); + } } private void Reset() diff --git a/Oqtane.Client/Modules/Admin/Logs/Detail.razor b/Oqtane.Client/Modules/Admin/Logs/Detail.razor index 7111572e..1953a72d 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Detail.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Detail.razor @@ -155,7 +155,7 @@ } } - if (log.PageId != null && log.ModuleId != null) + if (log.PageId != null && log.ModuleId != null && log.ModuleId != -1) { var pagemodule = await PageModuleService.GetPageModuleAsync(log.PageId.Value, log.ModuleId.Value); if (pagemodule != null) diff --git a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx index 0760ce56..33c9fb60 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx @@ -192,4 +192,13 @@ Use + + Error Loading Login + + + Error Performing Login + + + Error Resetting Password + \ No newline at end of file diff --git a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs index 1d456565..b7a3646f 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs +++ b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs @@ -34,7 +34,7 @@ namespace Oqtane.Themes.Controls protected async Task LogoutUser() { await UserService.LogoutUserAsync(PageState.User); - await LoggingService.Log(PageState.Alias, PageState.Page.PageId, PageState.ModuleId, PageState.User.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User.Username); + await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User.Username); PageState.User = null; var url = PageState.Alias.Path + "/" + PageState.Page.Path; diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index f34c1ce1..7377abfa 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -184,7 +184,7 @@ namespace Microsoft.Extensions.DependencyInjection options.SignIn.RequireConfirmedPhoneNumber = false; // User settings - options.User.RequireUniqueEmail = true; + options.User.RequireUniqueEmail = false; // changing to true will cause issues for legacy data options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; }); diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index 0e1f525d..ce34632f 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -199,56 +199,73 @@ namespace Oqtane.Extensions } User user = null; - var identityuser = await _identityUserManager.FindByEmailAsync(email); + bool duplicates = false; + IdentityUser identityuser = null; + try + { + identityuser = await _identityUserManager.FindByEmailAsync(email); + } + catch + { + // FindByEmailAsync will throw an error if the email matches multiple user accounts + duplicates = true; + } if (identityuser == null) { - if (bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:CreateUsers", "true"))) + if (duplicates) { - identityuser = new IdentityUser(); - identityuser.UserName = email; - identityuser.Email = email; - identityuser.EmailConfirmed = true; - var result = await _identityUserManager.CreateAsync(identityuser, DateTime.UtcNow.ToString("yyyy-MMM-dd-HH-mm-ss")); - if (result.Succeeded) + _logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Multiple Users Exist With Email Address {Email}. Login Denied.", email); + } + else + { + if (bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:CreateUsers", "true"))) { - user = new User + identityuser = new IdentityUser(); + identityuser.UserName = email; + identityuser.Email = email; + identityuser.EmailConfirmed = true; + var result = await _identityUserManager.CreateAsync(identityuser, DateTime.UtcNow.ToString("yyyy-MMM-dd-HH-mm-ss")); + if (result.Succeeded) { - SiteId = httpContext.GetAlias().SiteId, - Username = email, - DisplayName = email, - Email = email, - LastLoginOn = null, - LastIPAddress = "" - }; - user = _users.AddUser(user); + user = new User + { + SiteId = httpContext.GetAlias().SiteId, + Username = email, + DisplayName = email, + Email = email, + LastLoginOn = null, + LastIPAddress = "" + }; + user = _users.AddUser(user); - if (user != null) - { - var _notifications = httpContext.RequestServices.GetRequiredService(); - string url = httpContext.Request.Scheme + "://" + httpContext.GetAlias().Name; - string body = "You Recently Used An External Account To Sign In To Our Site.\n\n" + url + "\n\nThank You!"; - var notification = new Notification(user.SiteId, user, "User Account Notification", body); - _notifications.AddNotification(notification); + if (user != null) + { + var _notifications = httpContext.RequestServices.GetRequiredService(); + string url = httpContext.Request.Scheme + "://" + httpContext.GetAlias().Name; + string body = "You Recently Used An External Account To Sign In To Our Site.\n\n" + url + "\n\nThank You!"; + var notification = new Notification(user.SiteId, user, "User Account Notification", body); + _notifications.AddNotification(notification); - // add user login - await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType, providerKey, "")); + // add user login + await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType, providerKey, "")); - _logger.Log(user.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "User Added {User}", user); + _logger.Log(user.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "User Added {User}", user); + } + else + { + _logger.Log(user.SiteId, LogLevel.Error, "ExternalLogin", Enums.LogFunction.Create, "Unable To Add User {Email}", email); + } } else { - _logger.Log(user.SiteId, LogLevel.Error, "ExternalLogin", Enums.LogFunction.Create, "Unable To Add User {Email}", email); + _logger.Log(user.SiteId, LogLevel.Error, "ExternalLogin", Enums.LogFunction.Create, "Unable To Add Identity User {Email} {Error}", email, result.Errors.ToString()); } } else { - _logger.Log(user.SiteId, LogLevel.Error, "ExternalLogin", Enums.LogFunction.Create, "Unable To Add Identity User {Email} {Error}", email, result.Errors.ToString()); + _logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Creation Of New Users Is Disabled For This Site. User With Email Address {Email} Will First Need To Be Registered On The Site.", email); } } - else - { - _logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Creation Of New Users Is Disabled For This Site. User With Email Address {Email} Will First Need To Be Registered On The Site.", email); - } } else {