From ff6a810ad589126cceee37fa255af9b4f4b22a58 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 16 May 2025 11:13:03 -0400 Subject: [PATCH] Fix #4789 - allow user email verification to be managed by administrator --- Oqtane.Client/Modules/Admin/Login/Index.razor | 2 +- Oqtane.Client/Modules/Admin/Users/Edit.razor | 28 +++++++++---- .../Resources/Modules/Admin/Login/Index.resx | 4 +- .../Resources/Modules/Admin/Users/Edit.resx | 6 +++ Oqtane.Server/Controllers/UserController.cs | 10 ++++- Oqtane.Server/Managers/UserManager.cs | 39 ++++++++++++------- 6 files changed, 63 insertions(+), 26 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index f10c0203..f0a057d0 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -144,7 +144,7 @@ else user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]); if (user != null) { - await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username); + await logger.LogInformation(LogFunction.Security, "Email Verified For Username {Username}", _username); AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info); } else diff --git a/Oqtane.Client/Modules/Admin/Users/Edit.razor b/Oqtane.Client/Modules/Admin/Users/Edit.razor index 549059e4..c6401a47 100644 --- a/Oqtane.Client/Modules/Admin/Users/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Users/Edit.razor @@ -18,13 +18,13 @@
- +
- +
@@ -33,7 +33,7 @@
- +
@@ -42,13 +42,22 @@
- +
- + +
+ +
+
+
+
@@ -68,7 +77,7 @@ @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) {
- +
- +
@@ -167,6 +176,7 @@ private string _togglepassword = string.Empty; private string _confirm = string.Empty; private string _email = string.Empty; + private string _confirmed = string.Empty; private string _displayname = string.Empty; private string _timezoneid = string.Empty; private string _isdeleted; @@ -204,6 +214,7 @@ { _username = user.Username; _email = user.Email; + _confirmed = user.EmailConfirmed.ToString(); _displayname = user.DisplayName; _timezoneid = PageState.User.TimeZoneId; _isdeleted = user.IsDeleted.ToString(); @@ -255,6 +266,7 @@ user.Username = _username; user.Password = _password; user.Email = _email; + user.EmailConfirmed = bool.Parse(_confirmed); user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname; user.TimeZoneId = _timezoneid; if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) diff --git a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx index 0b1a8780..f1fb0c8a 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx @@ -121,10 +121,10 @@ Forgot Password - User Account Verified Successfully. You Can Now Login With Your Username And Password Below. + User Account Email Address Verified Successfully. You Can Now Login With Your Username And Password. - User Account Could Not Be Verified. Please Contact Your Administrator For Further Instructions. + User Account Email Address Could Not Be Verified. Please Contact Your Administrator For Further Instructions. User Account Linked Successfully. You Can Now Login With Your External Login Below. diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx index df4ccc95..ff38ec85 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx @@ -216,4 +216,10 @@ The user's time zone + + Confirmed? + + + Indicates if the user's email is verified + \ No newline at end of file diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 37648c4f..7789a2d3 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -140,6 +140,7 @@ namespace Oqtane.Controllers filtered.LastLoginOn = user.LastLoginOn; filtered.LastIPAddress = user.LastIPAddress; filtered.TwoFactorRequired = user.TwoFactorRequired; + filtered.EmailConfirmed = user.EmailConfirmed; filtered.Roles = user.Roles; filtered.CreatedBy = user.CreatedBy; filtered.CreatedOn = user.CreatedOn; @@ -200,10 +201,15 @@ namespace Oqtane.Controllers [Authorize] public async Task Put(int id, [FromBody] User user) { - if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && user.UserId == id && _users.GetUser(user.UserId, false) != null + var existing = _userManager.GetUser(user.UserId, user.SiteId); + if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && user.UserId == id && existing != null && (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || User.Identity.Name == user.Username)) { - user.EmailConfirmed = User.IsInRole(RoleNames.Admin); + // only administrators can update the email confirmation + if (!User.IsInRole(RoleNames.Admin)) + { + user.EmailConfirmed = existing.EmailConfirmed; + } user = await _userManager.UpdateUser(user); } else diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs index 84679d23..5e1e6e64 100644 --- a/Oqtane.Server/Managers/UserManager.cs +++ b/Oqtane.Server/Managers/UserManager.cs @@ -65,7 +65,12 @@ namespace Oqtane.Managers { user.SiteId = siteid; user.Roles = GetUserRoles(user.UserId, user.SiteId); - user.SecurityStamp = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult()?.SecurityStamp; + var identityuser = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult(); + if (identityuser != null) + { + user.SecurityStamp = identityuser.SecurityStamp; + user.EmailConfirmed = identityuser.EmailConfirmed; + } user.Settings = _settings.GetSettings(EntityNames.User, user.UserId) .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); } @@ -245,22 +250,30 @@ namespace Oqtane.Managers { identityuser.Email = user.Email; await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated - - // if email address changed and it is not confirmed, verification is required for new email address - if (!user.EmailConfirmed) - { - 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); - } } 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."; + 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);