diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs index 9e3fff950b..676f37aa47 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs @@ -159,6 +159,23 @@ public class IdentityServerSupportedLoginModel : LoginModel if (result.IsNotAllowed) { + var notAllowedUser = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ?? + await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress); + if (notAllowedUser != null) + { + using (CurrentTenant.Change(notAllowedUser.TenantId)) + { + await IdentityOptions.SetAsync(); + if ((notAllowedUser.ShouldChangePasswordOnNextLogin || + await UserManager.ShouldPeriodicallyChangePasswordAsync(notAllowedUser)) && + !await UserManager.CheckPasswordAsync(notAllowedUser, LoginInput.Password)) + { + Alerts.Danger(L["InvalidUserNameOrPassword"]); + return Page(); + } + } + } + Alerts.Warning(L["LoginIsNotAllowed"]); return Page(); } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index cef45c2f4f..57fcbe9e46 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -130,6 +130,23 @@ public class LoginModel : AccountPageModel if (result.IsNotAllowed) { + var notAllowedUser = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ?? + await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress); + if (notAllowedUser != null) + { + using (CurrentTenant.Change(notAllowedUser.TenantId)) + { + await IdentityOptions.SetAsync(); + if ((notAllowedUser.ShouldChangePasswordOnNextLogin || + await UserManager.ShouldPeriodicallyChangePasswordAsync(notAllowedUser)) && + !await UserManager.CheckPasswordAsync(notAllowedUser, LoginInput.Password)) + { + Alerts.Danger(L["InvalidUserNameOrPassword"]); + return Page(); + } + } + } + Alerts.Warning(L["LoginIsNotAllowed"]); return Page(); } diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs index 7aa540f420..ab2adb6c2f 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs @@ -1,8 +1,10 @@ -using System.Net; +using System; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Shouldly; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.Identity.AspNetCore; @@ -45,4 +47,36 @@ public class AbpSignInManager_Tests : AbpIdentityAspNetCoreTestBase result.ShouldBe("NotAllowed"); } + + [Fact] + public async Task Should_Return_NotAllowed_For_User_That_Should_Change_Password_Regardless_Of_Password() + { + // PreSignInCheck rejects users with ShouldChangePasswordOnNextLogin=true before the + // password is verified, so PasswordSignInAsync returns NotAllowed for both right and + // wrong passwords. Callers that surface a login error to the user (Login page) need + // to recheck the password themselves to distinguish the two cases. + var userManager = GetRequiredService(); + var unitOfWorkManager = GetRequiredService(); + + const string userName = "must-change-password"; + const string password = "1q2w3E*"; + + using (var uow = unitOfWorkManager.Begin()) + { + var user = new IdentityUser(Guid.NewGuid(), userName, userName + "@abp.io"); + user.SetShouldChangePasswordOnNextLogin(true); + (await userManager.CreateAsync(user, password)).Succeeded.ShouldBeTrue(); + await uow.CompleteAsync(); + } + + var withWrongPassword = await GetResponseAsStringAsync( + $"api/signin-test/password?userName={userName}&password=WRONG_PASSWORD" + ); + withWrongPassword.ShouldBe("NotAllowed"); + + var withCorrectPassword = await GetResponseAsStringAsync( + $"api/signin-test/password?userName={userName}&password={password}" + ); + withCorrectPassword.ShouldBe("NotAllowed"); + } }