Release v1.0.0 #16
.editorconfig.envnuget.config
.gitea/workflows
.gitignoreApiDto
ApiDto.csproj
Backend.slnDockerfileCommon
AuthRoles.csCacheType.csDatabaseType.csOAuthProvider.csPairPeriodTime.csPasswordPolicy.csTwoFactorAuthentication.cs
Requests
Responses
Endpoint
Backend.httpISaveSettings.cs
README.mdCommon
Attributes
BadRequestResponseAttribute.csCacheMaxAgeAttribute.csLocalhostAttribute.csMaintenanceModeIgnoreAttribute.csNotFoundResponseAttribute.csSwaggerDefaultAttribute.csTokenAuthenticationAttribute.cs
Exceptions
Interfaces
MapperDto
AvailableProvidersConverter.csPairPeriodTimeConverter.csPasswordPolicyConverter.csTwoFactorAuthenticationConverter.cs
Services
Configuration
Core
BackgroundTasks
Middleware
CacheMaxAgeMiddleware.csCookieAuthorizationMiddleware.csCustomExceptionHandlerMiddleware.csJwtRevocationMiddleware.csMaintenanceModeMiddleware.cs
Startup
Model
SwaggerOptions
Validation
Controllers
BaseController.cs
Endpoint.csprojProgram.csConfiguration
V1
AuthController.csCampusController.csDisciplineController.csFacultyController.csGroupController.csImportController.csLectureHallController.csProfessorController.csScheduleController.csSecurityController.cs
WeatherForecastController.csSync
WeatherForecast.cswwwroot/css/swagger
Security
Common
CookieNames.cs
DependencyInjection.csDomain
Caching
CookieOptionsParameters.csOAuth2
OAuthProvider.csOAuthUser.csPasswordPolicy.csRequestContextInfo.csTwoFactorAuthenticator.csUser.csInterfaces
Properties
Security.csprojServices
SqlData
Application
Application.csprojDependencyInjection.cs
Common
Cqrs
Campus/Queries
GetCampusBasicInfoList
CampusBasicInfoDto.csCampusBasicInfoVm.csGetCampusBasicInfoListQuery.csGetCampusBasicInfoListQueryHandler.cs
GetCampusDetails
Discipline/Queries
GetDisciplineDetails
GetDisciplineList
Faculty/Queries/GetFacultyList
Group/Queries
GetGroupDetails
GetGroupList
LectureHall/Queries
GetLectureHallDetails
GetLectureHallList
Professor/Queries
GetProfessorDetails
GetProfessorDetailsBySearch
GetProfessorList
Schedule/Queries/GetScheduleList
Interfaces/DbContexts
Domain
Domain.csproj
Schedule
Migrations
MysqlMigrations
Migrations
20240601023106_InitialMigration.Designer.cs20240601023106_InitialMigration.cs20241027034820_RemoveUnusedRef.Designer.cs20241027034820_RemoveUnusedRef.csUberDbContextModelSnapshot.cs
MysqlMigrations.csprojPsqlMigrations
Migrations
20240601021702_InitialMigration.Designer.cs20240601021702_InitialMigration.cs20241027032753_RemoveUnusedRef.Designer.cs20241027032753_RemoveUnusedRef.csUberDbContextModelSnapshot.cs
PsqlMigrations.csprojSqliteMigrations
Persistence
Common
BaseDbContext.csConfigurationResolver.csDatabaseProvider.csDbContextFactory.csModelBuilderExtensions.cs
Contexts/Schedule
CampusDbContext.csDisciplineDbContext.csFacultyDbContext.csGroupDbContext.csLectureHallDbContext.csLessonAssociationDbContext.csLessonDbContext.csProfessorDbContext.csSpecificWeekDbContext.csTypeOfOccupationDbContext.cs
DbInitializer.csDependencyInjection.csEntityTypeConfigurations
Mark.cs
Persistence.csprojUberDbContext.csMysql/Schedule
CampusConfiguration.csDisciplineConfiguration.csFacultyConfiguration.csGroupConfiguration.csLectureHallConfiguration.csLessonAssociationConfiguration.csLessonConfiguration.csProfessorConfiguration.csSpecificWeekConfiguration.csTypeOfOccupationConfiguration.cs
Postgresql/Schedule
CampusConfiguration.csDisciplineConfiguration.csFacultyConfiguration.csGroupConfiguration.csLectureHallConfiguration.csLessonAssociationConfiguration.csLessonConfiguration.csProfessorConfiguration.csSpecificWeekConfiguration.csTypeOfOccupationConfiguration.cs
Sqlite/Schedule
@ -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<int?>(GetAttemptFailedCountKey(fingerprint), cancellation) ?? 1;
|
||||
var failedLoginCacheExpiration = TimeSpan.FromHours(1);
|
||||
|
||||
var failedLoginCacheName = $"{requestContext.Fingerprint}_login_failed";
|
||||
var countFailedLogin = await cache.GetAsync<int?>(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.");
|
||||
}
|
||||
|
Reference in New Issue
Block a user