Hi,
Both advisories are fixed in lodash 4.18.0+. @abp/lodash already allows any 4.x ("lodash": "^4.17.21"), so you can force the safe version in your own project right now:
package.json (use overrides for npm/pnpm):{
"resolutions": {
"lodash": "^4.18.1"
}
}
yarn install
abp install-libs
abp install-libs copies the physical files from node_modules into wwwroot/libs, so once lodash is bumped there, your wwwroot/libs/lodash/lodash.min.js will be the patched version and both advisories should clear.
We'll also raise the floor in @abp/lodash for future releases.
Thanks
Your current question has been refunded.
Thanks.
Hi,
Great to hear the workaround is working for you. The fix is queued for 10.2.2, so once you move to that version you can drop the two custom pages.
On managing tenant users from the host: right now the Users page is always scoped to the current tenant, so you have to be signed in as a tenant admin to edit / disable / delete users of that tenant.
Thanks again for the detailed logs and HAR on the 2FA issue — that helped a lot.
Thanks
Hi,
Thanks for the log and HAR — with the password correct this time, the flow tells the full story:
[INF] AuthenticationScheme: Identity.TwoFactorUserId signed in. ← login wrote the 2FA cookie
[INF] Executed handler method OnPostAsync, returned RedirectToPageResult
[INF] Redirecting to ./SendSecurityCode.
[INF] POST /Account/Login - 302
[INF] Executing handler method SendSecurityCodeModel.OnGetAsync
[INF] Executed handler method OnGetAsync, returned RedirectToPageResult ← bounced back
[INF] Redirecting to ./Login.
[INF] GET /Account/SendSecurityCode - 302
We've reproduced and confirmed this is a real bug in Account.Pro when Shared mode is combined with 2FA. Here's exactly what happens:
FindSharedUserByNameAsync locates the user across tenants. If the user row only lives under a tenant (no TenantId IS NULL row in AbpUsers), it returns the tenant user. The password check succeeds, 2FA is required, and ASP.NET Core Identity writes the .AspNetCore.Identity.TwoFactorUserId cookie. That cookie, by design of AspNetCore Identity, only carries the UserId — no TenantId.GET /Account/SendSecurityCode the user isn't authenticated yet, so AbpAccountTenantResolveContributor (Shared mode) short-circuits CurrentTenant to null.SendSecurityCode.OnGetAsync calls SignInManager.GetTwoFactorAuthenticationUserAsync(), which internally calls UserManager.FindByIdAsync(userId). That call goes through the default EF IMultiTenant filter (WHERE TenantId IS NULL), so the tenant-owned user row is filtered out, the method returns null, and the page redirects back to /Account/Login.Non-2FA accounts don't hit this because they complete the login in a single POST and get the full Identity.Application cookie (which does carry TenantId), so no tenant context is ever lost.
We'll fix this in 10.2.2. In the meantime, as a drop-in workaround on your side, override the two affected Razor Pages in your Blazor host project. The overrides disable the IMultiTenant filter while fetching the 2FA user, then switch CurrentTenant to the user's tenant for the rest of the handler. I compiled both files against the 10.2 Volo.Abp.Account.Pro.Public.Web assembly to make sure they build cleanly:
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Volo.Abp.Account;
using Volo.Abp.Account.Public.Web.Pages.Account;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace YourProject.Web.Pages.Account;
[ExposeServices(typeof(SendSecurityCodeModel))]
public class CustomSendSecurityCodeModel : SendSecurityCodeModel
{
[UnitOfWork]
public override async Task<IActionResult> OnGetAsync()
{
var dataFilter = LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
Volo.Abp.Identity.IdentityUser user;
using (dataFilter.Disable<IMultiTenant>())
{
user = await SignInManager.GetTwoFactorAuthenticationUserAsync();
}
if (user == null)
{
return RedirectToPage("./Login");
}
using (CurrentTenant.Change(user.TenantId))
{
Providers = (await AccountAppService.GetTwoFactorProvidersAsync(new GetTwoFactorProvidersInput
{
UserId = user.Id,
Token = await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider,
nameof(Microsoft.AspNetCore.Identity.SignInResult.RequiresTwoFactor))
})).Select(userProvider => new SelectListItem
{
Text = userProvider,
Value = userProvider
}).ToList();
}
return Page();
}
[UnitOfWork]
public override async Task<IActionResult> OnPostAsync()
{
var dataFilter = LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
Volo.Abp.Identity.IdentityUser user;
using (dataFilter.Disable<IMultiTenant>())
{
user = await SignInManager.GetTwoFactorAuthenticationUserAsync();
}
if (user == null)
{
return RedirectToAction("Login");
}
using (CurrentTenant.Change(user.TenantId))
{
return await base.OnPostAsync();
}
}
}
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
using Volo.Abp.Account.Public.Web.Pages.Account;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.Uow;
namespace YourProject.Web.Pages.Account;
[ExposeServices(typeof(VerifySecurityCodeModel))]
public class CustomVerifySecurityCodeModel : VerifySecurityCodeModel
{
public CustomVerifySecurityCodeModel(ICurrentPrincipalAccessor currentPrincipalAccessor)
: base(currentPrincipalAccessor)
{
}
[UnitOfWork]
public override async Task OnGetAsync()
{
var dataFilter = LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
Volo.Abp.Identity.IdentityUser user;
using (dataFilter.Disable<IMultiTenant>())
{
user = await SignInManager.GetTwoFactorAuthenticationUserAsync();
}
if (user == null)
{
throw new UserFriendlyException(L["VerifySecurityCodeNotLoggedInErrorMessage"]);
}
using (CurrentTenant.Change(user.TenantId))
{
IsRememberBrowserEnabled = await IsRememberBrowserEnabledAsync();
}
}
[UnitOfWork]
public override async Task<IActionResult> OnPostAsync()
{
var dataFilter = LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
Volo.Abp.Identity.IdentityUser user;
using (dataFilter.Disable<IMultiTenant>())
{
user = await SignInManager.GetTwoFactorAuthenticationUserAsync();
}
if (user == null)
{
return RedirectToPage("./Login");
}
using (CurrentTenant.Change(user.TenantId))
{
return await base.OnPostAsync();
}
}
}
Drop both files into your Blazor host project (any namespace is fine, the [ExposeServices] attribute tells ABP's DI to replace the built-in pages). No Startup / Module changes needed. Please give it a try and let me know if 2FA login works end-to-end for you.
Thanks
Hi,
Got your log and HAR, thanks. The good news is this isn't a 2FA bug — the login never actually reaches the 2FA step. The log shows a plain password mismatch for user tony:
[INF] Executing handler method Volo.Abp.Account.Public.Web.Pages.Account.LoginModel.OnPostAsync
...
[DBG] Invalid password for user.
[INF] Executed handler method OnPostAsync, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult.
[INF] Request finished HTTP/2 POST .../Account/Login - 200 null text/html
The HAR confirms it from the browser side:
/Account/* in the whole capture — POST /Account/Login returning 200 (no redirect, no Location header)./Account/SendSecurityCode at all..AspNetCore.Identity.TwoFactorUserId cookie is ever set in any response.<div role="alert" class="alert alert-danger alert-dismissible fade show">
Invalid username or password!
</div>
So what you're seeing as "redirect back to login" is actually the Login page re-rendering itself with the Invalid username or password alert after SignInManager.PasswordSignInAsync returned SignInResult.Failed. The RequiresTwoFactor branch is never hit, which is why you never see the 2FA screen.
My guess on why this correlates with 2FA-enabled accounts: shared-mode user lookup prefers the host user when multiple matches exist. If your 2FA-enabled users were created/migrated in a way that left a different password hash on the host row vs the tenant row, the shared-mode login will now check against the host row — and the password you remember (set on the tenant row) won't match.
Could you try the following and let me know:
tony's password from the host Identity Users page (make sure you're viewing Host users, not a tenant). Then try logging in with the new password.Thanks
Hi,
Elsa Studio is a UI project that can be used in Blazor Server and WASM projects. It can be viewed as a standalone project, and currently it is still challenging to fully integrate it into our own project.
Elsa has its own user/role/tenant system, and it is different from ABP:
Tenant entity, ITenantStore, ITenantsProvider, ITenantResolver, and /tenants CRUD endpoints.string? TenantId, but ABP uses Guid?.http://schemas.microsoft.com/identity/claims/tenantid claim (CustomClaimTypes.TenantId), which is not the same claim name that ABP writes by default.So all Elsa permissions in AbpElsaPermissionDefinitionProvider are registered as MultiTenancySides.Host for now, and the elsa-pro.md doc also says the module is "not yet fully integrated".
Tenant users logging into Elsa Studio to manage their own workflows is not supported yet. To make it work properly we need a mapping layer between ABP tenants and Elsa tenants, write the Elsa tenant claim to the token, open the permissions to the Tenant side, and optionally bridge ABP's tenant connection string to Elsa's Tenant.Configuration for per-tenant workflow databases.
For now, the suggested way to use the module is:
TenantId into the workflow as an input/variable, and let the workflow branch on it when needed.ICurrentTenant.Change(tenantId) to switch to the target tenant before calling any ABP service (repositories, managers, email, etc.).TenantId, so the workflow naturally runs in the context of the right tenant. This is the same pattern used in the ElsaDemoApp sample.This way a single set of workflow definitions can serve all tenants, and you don't need to work around the multi-tenancy limitation on the Elsa side.
We have already seen the 3.6.0 changes on the Elsa side: TenantsOptions.IsEnabled, "" as the default tenant id, null as tenant-agnostic, the TenantDeleted event, tenant id added to the stored trigger index, etc. These are good improvements, but the multi-tenancy model on the Elsa side is still moving. If we build the integration right now, we will likely have to redo parts of it after the next change on their side.
We will keep the Host-only setup for now, and do a proper multi-tenant integration once Elsa's multi-tenancy is stable. We will update the module and the docs at that time.
Thanks
hi
We fixed it. Can you try again?
Thanks.
hi
Yes, You have to enable the Web Socket feature.
Thanks.