From 4586614e11b1d4967c9a0a84a69f2f7e530466e7 Mon Sep 17 00:00:00 2001 From: "s.agibalov" Date: Fri, 6 Mar 2026 18:25:03 +0300 Subject: [PATCH] DEV-1280: preauth double password fix --- Constants.cs | 1 + Controllers/AccountController.cs | 17 +++++++++++++++++ Core/ApplicationCacheKeyFactory.cs | 5 +++++ .../API/DTO/UserProfileAuthenticatorsDto.cs | 9 +++++---- Services/Caching/ApplicationCache.cs | 18 +++++++++++++++++- Services/Caching/ApplicationCacheConfig.cs | 1 + 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/Constants.cs b/Constants.cs index 0a118e2..d902088 100644 --- a/Constants.cs +++ b/Constants.cs @@ -9,6 +9,7 @@ public class Constants public const string COOKIE_NAME = "multifactor"; public const string SESSION_EXPIRED_PASSWORD_USER_KEY = "multifactor:expired-password:user"; public const string SESSION_EXPIRED_PASSWORD_CIPHER_KEY = "multifactor:expired-password:cipher"; + public const string PREAUTHENTICATION_AUTHN_SUCCEED_KEY = "multifactor:preauthentication-authn-succesd:user"; public const string CAPTCHA_TOKEN = "responseToken"; public const string PWD_RECOVERY_COOKIE = "PSession"; diff --git a/Controllers/AccountController.cs b/Controllers/AccountController.cs index ea2f157..bc802a2 100644 --- a/Controllers/AccountController.cs +++ b/Controllers/AccountController.cs @@ -112,6 +112,14 @@ public async Task Login(LoginModel model, SingleSignOnDto sso) if (adValidationResult.IsAuthenticated) { var identity = adValidationResult.GetIdentity(model.UserName); + + if (Configuration.Current.PreAuthnMode) + { + _applicationCache.SetPreauthenticationAuthn( + ApplicationCacheKeyFactory.CreatePreAuthenticationAuthnSucceedKey(identity), + true); + } + if (sso.HasSamlSession() && adValidationResult.IsBypass) { return ByPassSamlSession(identity, sso.SamlSessionId); @@ -392,6 +400,15 @@ private ActionResult RedirectToCredValidationAfter2FA(string accessToken) { var handler = new JwtSecurityTokenHandler(); var token = handler.ReadJwtToken(accessToken); + + var authCacheResult = _applicationCache.GetPreauthenticationAuthn(ApplicationCacheKeyFactory.CreatePreAuthenticationAuthnSucceedKey(token.Subject)); + if (!authCacheResult.IsEmpty && authCacheResult.Value) + { + _applicationCache.Remove(ApplicationCacheKeyFactory.CreatePreAuthenticationAuthnSucceedKey(token.Subject)); + _authService.SignIn(accessToken); + return RedirectToAction("Index", "Home"); + } + var usernameClaims = token.Claims.FirstOrDefault(claim => claim.Type == MultiFactorClaims.RawUserName); // for the password entry step diff --git a/Core/ApplicationCacheKeyFactory.cs b/Core/ApplicationCacheKeyFactory.cs index 1ec5562..a8b99fc 100644 --- a/Core/ApplicationCacheKeyFactory.cs +++ b/Core/ApplicationCacheKeyFactory.cs @@ -11,5 +11,10 @@ public static string CreateExpiredPwdCipherKey(string identity) { return $"{Constants.SESSION_EXPIRED_PASSWORD_CIPHER_KEY}:{identity}"; } + + public static string CreatePreAuthenticationAuthnSucceedKey(string identity) + { + return $"{Constants.PREAUTHENTICATION_AUTHN_SUCCEED_KEY}:{identity}"; + } } } \ No newline at end of file diff --git a/Services/API/DTO/UserProfileAuthenticatorsDto.cs b/Services/API/DTO/UserProfileAuthenticatorsDto.cs index 5fea807..fec854a 100644 --- a/Services/API/DTO/UserProfileAuthenticatorsDto.cs +++ b/Services/API/DTO/UserProfileAuthenticatorsDto.cs @@ -11,10 +11,11 @@ public class UserProfileAuthenticatorsDto public UserProfileAuthenticatorDto[] GetAuthenticators() { - return TotpAuthenticators - .Concat(TelegramAuthenticators) - .Concat(MobileAppAuthenticators) - .Concat(PhoneAuthenticators) + return Enumerable.Empty() + .Concat(TotpAuthenticators ?? Enumerable.Empty()) + .Concat(TelegramAuthenticators ?? Enumerable.Empty()) + .Concat(MobileAppAuthenticators ?? Enumerable.Empty()) + .Concat(PhoneAuthenticators ?? Enumerable.Empty()) .ToArray(); } } diff --git a/Services/Caching/ApplicationCache.cs b/Services/Caching/ApplicationCache.cs index bc97fe3..d2b7bea 100644 --- a/Services/Caching/ApplicationCache.cs +++ b/Services/Caching/ApplicationCache.cs @@ -40,7 +40,23 @@ public CachedItem GetIdentity(string key) ? new CachedItem(value) : CachedItem.Empty; } - + public void SetPreauthenticationAuthn(string key, bool value) + { + var options = new MemoryCacheEntryOptions() + .SetAbsoluteExpiration(_config.PreauthenticationAuthnExpiration) + .SetSize(1); + _cache.Set(key, value, options); + } + + public CachedItem GetPreauthenticationAuthn(string key) + { + if (string.IsNullOrWhiteSpace(key)) + return CachedItem.Empty; + return _cache.TryGetValue(key, out bool value) + ? new CachedItem(value) + : CachedItem.Empty; + } + public CachedItem Get(string key) { return _cache.TryGetValue(key, out string value) diff --git a/Services/Caching/ApplicationCacheConfig.cs b/Services/Caching/ApplicationCacheConfig.cs index 8d68257..554723c 100644 --- a/Services/Caching/ApplicationCacheConfig.cs +++ b/Services/Caching/ApplicationCacheConfig.cs @@ -7,5 +7,6 @@ public class ApplicationCacheConfig public TimeSpan AbsoluteExpiration { get; set; } = TimeSpan.FromMinutes(2); public TimeSpan SupportInfoExpiration { get; set; } = TimeSpan.FromMinutes(60); public TimeSpan SupportInfoEmptyExpiration { get; set; } = TimeSpan.FromMinutes(5); + public TimeSpan PreauthenticationAuthnExpiration { get; set; } = TimeSpan.FromMinutes(10); } } \ No newline at end of file