The custom Style="max-height: 600px; overflow-y: auto;" on MudTabs produced an inner
scrollbar in addition to mud-dialog's own scrollbar, resulting in a double scrolling
experience inside the permissions modal. Let mud-dialog handle overflow natively.
- MudMenu: switch ActivatorContent to MenuContext.ToggleAsync pattern (v9 breaking)
- MudForm: rename Validate to ValidateAsync (v9.1 obsoletion)
- MudInput: replace AutoGrow with Sizing for textarea
- DataGrid: add white-space:nowrap on header to prevent CJK characters stacking vertically
- BlockUI: shrink loading spinner inside mud-dialog
- Search field: switch Label to Placeholder so the text shows next to the magnifier icon
Top-level "Select All in All Tabs" already used `Indeterminate` to show
a partial-grant state. The per-tab "Select All in This Tab" checkbox
was a plain two-state binding to `Permissions.All(x => x.IsGranted)`,
so a tab with some (but not all) permissions granted rendered as
unchecked - reading as "no permissions granted" to QA.
Bind `Indeterminate` to "any granted but not all granted" to match the
top-level behavior.
Replaces the TOTP-based Email/Phone 2FA providers under
TokenOptions.DefaultEmailProvider / DefaultPhoneProvider with
DataProtector-backed single-use equivalents.
- Encrypt the 6-digit code via IDataProtector (purpose chain isolated per
provider + token purpose), store ciphertext + absolute UTC expiration
(unix seconds) in the user token table
- Remove the stored entry on successful validation (true single-use)
- Concurrency race (ConcurrencyStamp failure) returns false instead of 500
- Configurable TokenLifespan (default 3 minutes) via Options
AbpSingleActiveTokenProvider.GenerateAsync now checks the IdentityResult
from UserManager.UpdateAsync so a silent persistence failure no longer
returns a token that was not saved.
Related to #25314.
Switching CurrentTenant to user.TenantId in PasswordSignInAsync without refreshing IdentityOptions meant that lockout, password policy and other tenant-scoped options used host values during the base sign-in call. Call IdentityOptions.SetAsync inside the tenant switch so downstream checks use the user's tenant configuration.
- Override IdentityUserManager.FindByIdAsync to fall back to a cross-tenant lookup in shared user sharing strategy so any caller that hits FindByIdAsync from a non-matching tenant context (including base SignInManager internals for TwoFactorSignInAsync and TwoFactorRecoveryCodeSignInAsync) can still resolve a tenant user by id
- Drop the now-redundant AbpSignInManager.GetTwoFactorAuthenticationUserAsync override; the base implementation works automatically through the new FindByIdAsync behavior
- Cover the new FindByIdAsync behavior with unit tests
Guards against regressing the data-access contract behind the 2FA redirect bug: login must find a tenant user by user name from a host context, and the 2FA mid-flow must then resolve the same tenant user by id from the same host context.
Exercises the full cookie round-trip: writes a TwoFactorUserId cookie carrying a tenant user id, then verifies that AbpSignInManager.GetTwoFactorAuthenticationUserAsync returns the tenant user when CurrentTenant is null.
- Add IdentityUserManager.FindSharedUserByIdAsync to resolve a user by id across tenants in shared user sharing strategy
- Override AbpSignInManager.GetTwoFactorAuthenticationUserAsync to use it so the 2FA mid-flow can still find a tenant-scoped user when CurrentTenant is host
- Cover the new method with unit tests
- Replace FormattedStringValueExtracter.Extract with LastIndexOf in
PermissionGrantCacheItem and ResourcePermissionGrantCacheItem to
eliminate repeated string tokenization and object allocations on
every cache key parse (~12,000 calls per request with 4000+ permissions)
- Add fast-path in SimpleStateCheckerManager.InternalIsEnabledAsync to
skip DI scope creation when both StateCheckers and GlobalStateCheckers
are empty, avoiding thousands of unnecessary scope allocations
- Optimize PermissionChecker.IsGrantedAsync(string[]) and
ResourcePermissionChecker.IsGrantedAsync(string[], resourceName, resourceKey)
to load all permission definitions once via GetPermissionsAsync /
GetResourcePermissionsAsync instead of N individual GetOrNullAsync calls,
and use batch StateCheckerManager.IsEnabledAsync for state checking
- Optimize AbpApplicationConfigurationAppService.GetAuthConfigAsync to
pre-load all permission names into a HashSet for O(1) lookup instead
of N async GetOrNullAsync calls inside the loop
- Fix GetResourcePermissionsAsync to deduplicate by (ResourceName, Name)
instead of Name only, matching the actual uniqueness constraint of
resource permissions defined in PermissionDefinitionContext
Production impact (customer with 4000+ permissions): 10s+ -> ~682ms