From 71c31c0bbb05d5085f76e4a7bb6cbaf584004475 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 05:46:27 +0300 Subject: [PATCH] refactor: separate the method of counting failed attempts --- Security/Services/AuthService.cs | 50 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index a505c09..17103e8 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -24,6 +24,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I private static string GetAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token"; private static string GetFirstAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token_first"; + private static string GetAttemptFailedCountKey(string fingerprint) => $"{fingerprint}_login_failed"; private Task SetAuthTokenDataToCache(AuthToken data, CancellationToken cancellation) => cache.SetAsync( @@ -47,34 +48,47 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I private Task RevokeAccessToken(string token) => revokedToken.AddTokenToRevokedAsync(token, accessTokenService.GetExpireDateTime(token)); - private async Task VerifyUserOrThrowError(RequestContextInfo requestContext, User user, string password, string username, - CancellationToken cancellation = default) + private async Task RecordFailedLoginAttempt(string fingerprint, string userId, CancellationToken cancellation) { - if ((user.Email.Equals(username, StringComparison.OrdinalIgnoreCase) || user.Username.Equals(username, StringComparison.OrdinalIgnoreCase)) && - passwordService.VerifyPassword(password, user.Salt, user.PasswordHash)) - return; + var failedLoginAttemptsCount = await cache.GetAsync(GetAttemptFailedCountKey(fingerprint), cancellation) ?? 1; + var failedLoginCacheExpiration = TimeSpan.FromHours(1); - var failedLoginCacheName = $"{requestContext.Fingerprint}_login_failed"; - var countFailedLogin = await cache.GetAsync(failedLoginCacheName, cancellation) ?? 1; - var cacheSaveTime = TimeSpan.FromHours(1); - - await cache.SetAsync(failedLoginCacheName, countFailedLogin + 1, slidingExpiration: cacheSaveTime, cancellationToken: cancellation); - - if (countFailedLogin > 5) + if (failedLoginAttemptsCount > 5) { logger.LogWarning( - "Multiple unsuccessful login attempts for user ID {UserId}. Attempt count: {AttemptNumber}.", - user.Id, - countFailedLogin); + "Multiple unsuccessful login attempts for user ID {UserId}. Fingerprint: {Fingerprint}. Attempt count: {AttemptNumber}.", + userId, + fingerprint, + failedLoginAttemptsCount); throw new SecurityException("Too many unsuccessful login attempts. Please try again later."); } logger.LogInformation( "Login attempt failed for user ID {UserId}. Fingerprint: {Fingerprint}. Attempt count: {AttemptNumber}.", - user.Id, - requestContext.Fingerprint, - countFailedLogin); + userId, + fingerprint, + failedLoginAttemptsCount); + + await cache.SetAsync(GetAttemptFailedCountKey(fingerprint), failedLoginAttemptsCount + 1, + slidingExpiration: failedLoginCacheExpiration, cancellationToken: cancellation); + } + + private Task ResetFailedLoginAttempts(string fingerprint, CancellationToken cancellation) => + cache.RemoveAsync(GetAttemptFailedCountKey(fingerprint), cancellation); + + private async Task VerifyUserOrThrowError(RequestContextInfo requestContext, User user, string password, string username, + CancellationToken cancellation = default) + { + if ((user.Email.Equals(username, StringComparison.OrdinalIgnoreCase) || + user.Username.Equals(username, StringComparison.OrdinalIgnoreCase)) && + passwordService.VerifyPassword(password, user.Salt, user.PasswordHash)) + { + await ResetFailedLoginAttempts(requestContext.Fingerprint, cancellation); + return; + } + + await RecordFailedLoginAttempt(requestContext.Fingerprint, user.Id, cancellation); throw new SecurityException("Authentication failed. Please check your credentials."); }