Release v1.0.0 #16
.envDbInitializer.csDependencyInjection.csnuget.config
.gitea/workflows
.gitignoreApiDto
ApiDto.csproj
Backend.slnDockerfileCommon
Requests
Responses
Endpoint
Backend.httpISaveSettings.cs
README.mdCommon
Attributes
BadRequestResponseAttribute.csCacheMaxAgeAttribute.csLocalhostAttribute.csMaintenanceModeIgnoreAttribute.csNotFoundResponseAttribute.csSwaggerDefaultAttribute.csTokenAuthenticationAttribute.cs
Exceptions
Interfaces
MapperDto
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.cs
WeatherForecastController.csSync
WeatherForecast.cswwwroot
css
swagger
Security
SqlData
Application
Application.csprojDependencyInjection.cs
Common
Cqrs
Campus
Queries
Discipline
Queries
Faculty
Queries
Group
Queries
LectureHall
Queries
Professor
Queries
GetProfessorDetails
GetProfessorDetailsBySearch
GetProfessorList
Schedule
Interfaces
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
EntityTypeConfigurations
Persistence.csprojUberDbContext.cs@ -22,7 +22,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I
|
|||||||
accessTokenService.GenerateToken(userId);
|
accessTokenService.GenerateToken(userId);
|
||||||
|
|
||||||
private static string GetAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token";
|
private static string GetAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token";
|
||||||
private static string GetFirstAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token_first";
|
internal static string GetFirstAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token_first";
|
||||||
|
|
||||||
private Task SetAuthTokenDataToCache(AuthToken data, CancellationToken cancellation) =>
|
private Task SetAuthTokenDataToCache(AuthToken data, CancellationToken cancellation) =>
|
||||||
cache.SetAsync(
|
cache.SetAsync(
|
||||||
@ -49,23 +49,23 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I
|
|||||||
if (countFailedLogin > 5)
|
if (countFailedLogin > 5)
|
||||||
{
|
{
|
||||||
logger.LogWarning(
|
logger.LogWarning(
|
||||||
"Multiple failed login attempts detected for user ID {UserId} from IP {UserIp}. Attempt: #{AttemptNumber}. Possible account compromise.",
|
"Multiple unsuccessful login attempts for user ID {UserId} from IP {UserIp}. Attempt count: {AttemptNumber}.",
|
||||||
user.Id,
|
user.Id,
|
||||||
requestContext.Ip,
|
requestContext.Ip,
|
||||||
countFailedLogin);
|
countFailedLogin);
|
||||||
|
|
||||||
throw new SecurityException($"There are many incorrect attempts to access the account. Try again after {(int)cacheSaveTime.TotalMinutes} minutes.");
|
throw new SecurityException("Too many unsuccessful login attempts. Please try again later.");
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation(
|
logger.LogInformation(
|
||||||
"Failed login attempt for user ID {UserId} from IP {UserIp} with User-Agent {UserAgent} and Fingerprint {Fingerprint} Attempt: #{AttemptNumber}.",
|
"Login attempt failed for user ID {UserId}. IP: {UserIp}, User-Agent: {UserAgent}, Fingerprint: {Fingerprint}. Attempt count: {AttemptNumber}.",
|
||||||
user.Id,
|
user.Id,
|
||||||
requestContext.Ip,
|
requestContext.Ip,
|
||||||
requestContext.UserAgent,
|
requestContext.UserAgent,
|
||||||
requestContext.Fingerprint,
|
requestContext.Fingerprint,
|
||||||
countFailedLogin);
|
countFailedLogin);
|
||||||
|
|
||||||
throw new SecurityException("Invalid username/email or password");
|
throw new SecurityException("Authentication failed. Please check your credentials.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GenerateAuthTokensAsync(CookieOptionsParameters cookieOptions, HttpContext context, RequestContextInfo requestContext, string userId, CancellationToken cancellation = default)
|
private async Task GenerateAuthTokensAsync(CookieOptionsParameters cookieOptions, HttpContext context, RequestContextInfo requestContext, string userId, CancellationToken cancellation = default)
|
||||||
@ -86,7 +86,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I
|
|||||||
cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime));
|
cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime));
|
||||||
|
|
||||||
logger.LogInformation(
|
logger.LogInformation(
|
||||||
"Successful login attempt for user ID {UserId} from IP {UserIp} with User-Agent {UserAgent} and Fingerprint {Fingerprint}.",
|
"Login successful for user ID {UserId}. IP: {UserIp}, User-Agent: {UserAgent}, Fingerprint: {Fingerprint}.",
|
||||||
authToken.UserId,
|
authToken.UserId,
|
||||||
authToken.Ip,
|
authToken.Ip,
|
||||||
authToken.UserAgent,
|
authToken.UserAgent,
|
||||||
@ -100,23 +100,25 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I
|
|||||||
var firstTokenAuth = await cache.GetAsync<FirstAuthToken?>(GetFirstAuthCacheKey(requestContext.Fingerprint), cancellationToken: cancellation);
|
var firstTokenAuth = await cache.GetAsync<FirstAuthToken?>(GetFirstAuthCacheKey(requestContext.Fingerprint), cancellationToken: cancellation);
|
||||||
|
|
||||||
if (firstTokenAuth == null || authenticator != firstTokenAuth.TwoFactorAuthenticator)
|
if (firstTokenAuth == null || authenticator != firstTokenAuth.TwoFactorAuthenticator)
|
||||||
throw new SecurityException("The session time has expired");
|
throw new SecurityException("Session expired. Please log in again.");
|
||||||
|
|
||||||
switch (firstTokenAuth.TwoFactorAuthenticator)
|
switch (firstTokenAuth.TwoFactorAuthenticator)
|
||||||
{
|
{
|
||||||
case TwoFactorAuthenticator.Totp:
|
case TwoFactorAuthenticator.Totp:
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(firstTokenAuth.Secret))
|
if (string.IsNullOrEmpty(firstTokenAuth.Secret))
|
||||||
throw new InvalidOperationException("The user's secrets for data processing were not transferred.");
|
throw new InvalidOperationException("Required authentication data is missing.");
|
||||||
|
|
||||||
var totp = new TotpService(firstTokenAuth.Secret);
|
var totp = new TotpService(firstTokenAuth.Secret);
|
||||||
|
|
||||||
if (!totp.VerifyToken(code))
|
if (!totp.VerifyToken(code))
|
||||||
throw new SecurityException("The entered code is incorrect.");
|
throw new SecurityException("Invalid verification code. Please try again.");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case TwoFactorAuthenticator.None:
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException("The system failed to understand the authorization method.");
|
throw new InvalidOperationException("Unsupported authorization method.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await GenerateAuthTokensAsync(cookieOptions, context, requestContext, firstTokenAuth.UserId, cancellation);
|
await GenerateAuthTokensAsync(cookieOptions, context, requestContext, firstTokenAuth.UserId, cancellation);
|
||||||
@ -162,7 +164,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I
|
|||||||
cookieOptions.DropCookie(context, CookieNames.AccessToken);
|
cookieOptions.DropCookie(context, CookieNames.AccessToken);
|
||||||
cookieOptions.DropCookie(context, CookieNames.RefreshToken);
|
cookieOptions.DropCookie(context, CookieNames.RefreshToken);
|
||||||
|
|
||||||
logger.LogWarning("Token validation failed for user ID {UserId}. Invalid token used from IP {UserIp} with User-Agent {UserAgent} and Fingerprint {Fingerprint}. Possible account compromise. Reason: {Reason}.",
|
logger.LogWarning("Token validation failed for user ID {UserId}. IP: {UserIp}, User-Agent: {UserAgent}, Fingerprint: {Fingerprint}. Reason: {Reason}.",
|
||||||
authToken.UserId,
|
authToken.UserId,
|
||||||
authToken.Ip,
|
authToken.Ip,
|
||||||
authToken.UserAgent,
|
authToken.UserAgent,
|
||||||
|
Reference in New Issue
Block a user