From 7938eaf123760df9ac7321a59ce7458d60dd8ae0 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 15 Dec 2025 08:23:41 -0500 Subject: [PATCH] refactor new Forgot Username and Login Link methods --- Oqtane.Client/Modules/Admin/Login/Index.razor | 15 +- Oqtane.Client/Services/UserService.cs | 28 +-- Oqtane.Server/Controllers/UserController.cs | 36 ++-- Oqtane.Server/Managers/UserManager.cs | 162 +++++++++--------- 4 files changed, 114 insertions(+), 127 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index 460aceab..4a6fee49 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -243,6 +243,9 @@ else private void SetAction(string action) { _action = action; + _username = ""; + _password = ""; + _email = ""; ClearModuleMessage(); StateHasChanged(); } @@ -364,9 +367,7 @@ else { if (!string.IsNullOrEmpty(_username)) { - var user = new User { Username = _username }; - user = await UserService.ForgotPasswordAsync(user); - if (user != null) + if (await UserService.ForgotPasswordAsync(_username)) { await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username); AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info); @@ -394,9 +395,7 @@ else { if (!string.IsNullOrEmpty(_email)) { - var user = new User { Email = _email }; - user = await UserService.ForgotUsernameAsync(user); - if (user != null) + if (await UserService.ForgotUsernameAsync(_email)) { AddModuleMessage(Localizer["Message.ForgotUsername"], MessageType.Info); await logger.LogInformation(LogFunction.Security, "Username Reminder Notification Sent For Email {Email}", _email); @@ -424,9 +423,7 @@ else { if (!string.IsNullOrEmpty(_email)) { - var user = new User { Email = _email }; - user = await UserService.SendLoginLinkAsync(user); - if (user != null) + if (await UserService.SendLoginLinkAsync(_email)) { AddModuleMessage(Localizer["Message.SendLoginLink"], MessageType.Info); await logger.LogInformation(LogFunction.Security, "Login Link Sent To Email {Email}", _email); diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs index 78d8f0af..437a23a6 100644 --- a/Oqtane.Client/Services/UserService.cs +++ b/Oqtane.Client/Services/UserService.cs @@ -97,18 +97,18 @@ namespace Oqtane.Services Task VerifyEmailAsync(User user, string token); /// - /// Trigger a forgot-password e-mail for this . + /// Trigger a forgot-password e-mail. /// - /// + /// /// - Task ForgotPasswordAsync(User user); + Task ForgotPasswordAsync(string username); /// - /// Trigger a forgot-username e-mail for this . + /// Trigger a username reminder e-mail. /// - /// + /// /// - Task ForgotUsernameAsync(User user); + Task ForgotUsernameAsync(string email); /// /// Reset the password of this @@ -222,9 +222,9 @@ namespace Oqtane.Services /// /// Send a login link /// - /// + /// /// - Task SendLoginLinkAsync(User user); + Task SendLoginLinkAsync(string email); } [PrivateApi("Don't show in the documentation, as everything should use the Interface")] @@ -289,14 +289,14 @@ namespace Oqtane.Services return await PostJsonAsync($"{Apiurl}/verify?token={token}", user); } - public async Task ForgotPasswordAsync(User user) + public async Task ForgotPasswordAsync(string username) { - return await PostJsonAsync($"{Apiurl}/forgot", user); + return await GetJsonAsync($"{Apiurl}/forgotpassword?name={username}"); } - public async Task ForgotUsernameAsync(User user) + public async Task ForgotUsernameAsync(string email) { - return await PostJsonAsync($"{Apiurl}/forgotusername", user); + return await GetJsonAsync($"{Apiurl}/forgotusername?email={email}"); } public async Task ResetPasswordAsync(User user, string token) @@ -386,9 +386,9 @@ namespace Oqtane.Services await DeleteAsync($"{Apiurl}/login?id={userId}&provider={provider}&key={key}"); } - public async Task SendLoginLinkAsync(User user) + public async Task SendLoginLinkAsync(string email) { - return await PostJsonAsync($"{Apiurl}/loginlink", user); + return await GetJsonAsync($"{Apiurl}/loginlink?email={email}"); } } } diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 3d28e86e..57ea1641 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -294,26 +294,18 @@ namespace Oqtane.Controllers return user; } - // POST api//forgot - [HttpPost("forgot")] - public async Task Forgot([FromBody] User user) + // GET api//forgotpassword?name=x + [HttpGet("forgotpassword")] + public async Task ForgotPassword(string name) { - if (ModelState.IsValid) - { - return await _userManager.ForgotPassword(user); - } - return null; + return await _userManager.ForgotPassword(name); } - // POST api//forgotusername - [HttpPost("forgotusername")] - public async Task ForgotUsername([FromBody] User user) + // GET api//forgotusername?email=x + [HttpGet("forgotusername")] + public async Task ForgotUsername(string email) { - if (ModelState.IsValid) - { - return await _userManager.ForgotUsername(user); - } - return null; + return await _userManager.ForgotUsername(email); } // POST api//reset @@ -572,15 +564,11 @@ namespace Oqtane.Controllers } } - // POST api//loginlink - [HttpPost("loginlink")] - public async Task SendLoginLink([FromBody] User user) + // GET api//loginlink?email=x + [HttpGet("loginlink")] + public async Task SendLoginLink(string email) { - if (ModelState.IsValid) - { - return await _userManager.SendLoginLink(user); - } - return null; + return await _userManager.SendLoginLink(email); } } } diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs index 1f08e348..a9c2d5e0 100644 --- a/Oqtane.Server/Managers/UserManager.cs +++ b/Oqtane.Server/Managers/UserManager.cs @@ -28,8 +28,8 @@ namespace Oqtane.Managers Task LoginUser(User user, bool setCookie, bool isPersistent); Task LogoutUserEverywhere(User user); Task VerifyEmail(User user, string token); - Task ForgotPassword(User user); - Task ForgotUsername(User user); + Task ForgotPassword(string username); + Task ForgotUsername(string email); Task ResetPassword(User user, string token); User VerifyTwoFactor(User user, string token); Task ValidateUser(string username, string email, string password); @@ -41,7 +41,7 @@ namespace Oqtane.Managers Task> GetLogins(int userId, int siteId); Task AddLogin(User user, string token, string type, string key, string name); Task DeleteLogin(int userId, string provider, string key); - Task SendLoginLink(User user); + Task SendLoginLink(string email); } public class UserManager : IUserManager @@ -520,70 +520,72 @@ namespace Oqtane.Managers return user; } - public async Task ForgotPassword(User user) + public async Task ForgotPassword(string username) { - IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); - if (identityuser != null) + if (!string.IsNullOrEmpty(username)) { - string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser); - - var alias = _tenantManager.GetAlias(); - user = GetUser(user.Username, alias.SiteId); - string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); - string siteName = _sites.GetSite(alias.SiteId).Name; - string subject = _localizer["ForgotPasswordEmailSubject"]; - subject = subject.Replace("[SiteName]", siteName); - string body = _localizer["ForgotPasswordEmailBody"].Value; - body = body.Replace("[UserDisplayName]", user.DisplayName); - body = body.Replace("[URL]", url); - body = body.Replace("[SiteName]", siteName); - var notification = new Notification(_tenantManager.GetAlias().SiteId, user, subject, body); - _notifications.AddNotification(notification); - - _logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", user.Username); - return new User { UserId = user.UserId, Username = user.Username, Email = user.Email }; // minimal object - } - else - { - _logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Notification Failed For {Username}", user.Username); - return null; - } - } - - public async Task ForgotUsername(User user) - { - try - { - IdentityUser identityuser = await _identityUserManager.FindByEmailAsync(user.Email); + IdentityUser identityuser = await _identityUserManager.FindByNameAsync(username); if (identityuser != null) { + string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser); + var alias = _tenantManager.GetAlias(); - user = GetUser(identityuser.UserName, alias.SiteId); - string url = alias.Protocol + alias.Name + "/login?name=" + user.Username; + var user = GetUser(username, alias.SiteId); + string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); string siteName = _sites.GetSite(alias.SiteId).Name; - string subject = _localizer["ForgotUsernameEmailSubject"]; + string subject = _localizer["ForgotPasswordEmailSubject"]; subject = subject.Replace("[SiteName]", siteName); - string body = _localizer["ForgotUsernameEmailBody"].Value; + string body = _localizer["ForgotPasswordEmailBody"].Value; body = body.Replace("[UserDisplayName]", user.DisplayName); body = body.Replace("[URL]", url); body = body.Replace("[SiteName]", siteName); var notification = new Notification(_tenantManager.GetAlias().SiteId, user, subject, body); _notifications.AddNotification(notification); - _logger.Log(LogLevel.Information, this, LogFunction.Security, "Forgot Username Notification Sent For {Email}", user.Email); - return new User { UserId = user.UserId, Username = user.Username, Email = user.Email }; // minimal object - } - else - { - _logger.Log(LogLevel.Error, this, LogFunction.Security, "Forgot Username Notification Failed For {Email}", user.Email); - return null; + _logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", user.Username); + return true; } } - catch + + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Notification Failed For {Username}", username); + return false; + } + + public async Task ForgotUsername(string email) + { + try + { + if (!string.IsNullOrEmpty(email)) + { + IdentityUser identityuser = await _identityUserManager.FindByEmailAsync(email); + if (identityuser != null) + { + var alias = _tenantManager.GetAlias(); + var user = GetUser(identityuser.UserName, alias.SiteId); + string url = alias.Protocol + alias.Name + "/login?name=" + user.Username; + string siteName = _sites.GetSite(alias.SiteId).Name; + string subject = _localizer["ForgotUsernameEmailSubject"]; + subject = subject.Replace("[SiteName]", siteName); + string body = _localizer["ForgotUsernameEmailBody"].Value; + body = body.Replace("[UserDisplayName]", user.DisplayName); + body = body.Replace("[URL]", url); + body = body.Replace("[SiteName]", siteName); + var notification = new Notification(_tenantManager.GetAlias().SiteId, user, subject, body); + _notifications.AddNotification(notification); + + _logger.Log(LogLevel.Information, this, LogFunction.Security, "Forgot Username Notification Sent For {Email}", user.Email); + return true; + } + } + + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Forgot Username Notification Failed For {Email}", email); + return false; + } + catch (Exception ex) { // email may not be unique - _logger.Log(LogLevel.Error, this, LogFunction.Security, "Forgot Username Notification Failed For {Email}", user.Email); - return null; + _logger.Log(LogLevel.Error, this, LogFunction.Security, ex, "Forgot Username Notification Failed For {Email}", email); + return false; } } @@ -958,48 +960,48 @@ namespace Oqtane.Managers } } - public async Task SendLoginLink(User user) + public async Task SendLoginLink(string email) { try { - IdentityUser identityuser = await _identityUserManager.FindByEmailAsync(user.Email); - if (identityuser != null) + if (!string.IsNullOrEmpty(email)) { - var token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); + IdentityUser identityuser = await _identityUserManager.FindByEmailAsync(email); + if (identityuser != null) + { + var token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); - var alias = _tenantManager.GetAlias(); - user = GetUser(identityuser.UserName, alias.SiteId); - user.TwoFactorCode = token; - user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10); - _users.UpdateUser(user); + var alias = _tenantManager.GetAlias(); + var user = GetUser(identityuser.UserName, alias.SiteId); + user.TwoFactorCode = token; + user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10); + _users.UpdateUser(user); - string url = alias.Protocol + alias.Name + "/pages/loginlink?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); - string siteName = _sites.GetSite(alias.SiteId).Name; - string subject = _localizer["LoginLinkEmailSubject"]; - subject = subject.Replace("[SiteName]", siteName); - string body = _localizer["LoginLinkEmailBody"].Value; - body = body.Replace("[UserDisplayName]", user.DisplayName); - body = body.Replace("[URL]", url); - body = body.Replace("[SiteName]", siteName); - var notification = new Notification(_tenantManager.GetAlias().SiteId, user, subject, body); - _notifications.AddNotification(notification); + string url = alias.Protocol + alias.Name + "/pages/loginlink?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); + string siteName = _sites.GetSite(alias.SiteId).Name; + string subject = _localizer["LoginLinkEmailSubject"]; + subject = subject.Replace("[SiteName]", siteName); + string body = _localizer["LoginLinkEmailBody"].Value; + body = body.Replace("[UserDisplayName]", user.DisplayName); + body = body.Replace("[URL]", url); + body = body.Replace("[SiteName]", siteName); + var notification = new Notification(_tenantManager.GetAlias().SiteId, user, subject, body); + _notifications.AddNotification(notification); - _logger.Log(LogLevel.Information, this, LogFunction.Security, "Login Link Notification Sent To {Email}", user.Email); - return new User { UserId = user.UserId, Username = user.Username, Email = user.Email }; // minimal object - } - else - { - _logger.Log(LogLevel.Error, this, LogFunction.Security, "Login Link Notification Failed For {Email}", user.Email); - return null; + _logger.Log(LogLevel.Information, this, LogFunction.Security, "Login Link Notification Sent To {Email}", user.Email); + return true; // minimal object + } } + + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Login Link Notification Failed For {Email}", email); + return false; } - catch + catch (Exception ex) { // email may not be unique - _logger.Log(LogLevel.Error, this, LogFunction.Security, "Login Link Notification Failed For {Email}", user.Email); - return null; + _logger.Log(LogLevel.Error, this, LogFunction.Security, ex, "Login Link Notification Failed For {Email}", email); + return false; } } - } }