Release v1.0.0 #16
.envnuget.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
Common
CookieNames.cs
DependencyInjection.csDomain
Caching
CookieOptionsParameters.csOAuth2
OAuthProvider.csOAuthUser.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
@ -61,7 +61,7 @@ public static class DependencyInjection
|
|||||||
providers.Add(provider, (clientId, secret));
|
providers.Add(provider, (clientId, secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
services.AddSingleton(provider => new OAuthService(provider.GetRequiredService<ILogger<OAuthService>>(), providers));
|
services.AddSingleton(provider => new OAuthService(provider.GetRequiredService<ILogger<OAuthService>>(), providers, configuration["SECURITY_ENCRYPTION_TOKEN"]!));
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,15 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Mirea.Api.Security.Services;
|
namespace Mirea.Api.Security.Services;
|
||||||
|
|
||||||
public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider, (string ClientId, string Secret)> providers)
|
public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider, (string ClientId, string Secret)> providers, string secretKey)
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<OAuthProvider, OAuthProviderUrisData> ProviderData = new()
|
private static readonly Dictionary<OAuthProvider, OAuthProviderUrisData> ProviderData = new()
|
||||||
{
|
{
|
||||||
@ -97,6 +99,12 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
|
|||||||
return userInfo?.MapToInternalUser();
|
return userInfo?.MapToInternalUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetHmacString(RequestContextInfo contextInfo, string secretKey)
|
||||||
|
{
|
||||||
|
var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey));
|
||||||
|
return Convert.ToBase64String(hmac.ComputeHash(
|
||||||
|
Encoding.UTF8.GetBytes($"{contextInfo.Fingerprint}_{contextInfo.Ip}_{contextInfo.UserAgent}")));
|
||||||
|
}
|
||||||
|
|
||||||
public Uri GetProviderRedirect(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUri, OAuthProvider provider)
|
public Uri GetProviderRedirect(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUri, OAuthProvider provider)
|
||||||
{
|
{
|
||||||
@ -106,7 +114,7 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
|
|||||||
"&response_type=code" +
|
"&response_type=code" +
|
||||||
$"&redirect_uri={redirectUri}" +
|
$"&redirect_uri={redirectUri}" +
|
||||||
$"&scope={ProviderData[provider].Scope}" +
|
$"&scope={ProviderData[provider].Scope}" +
|
||||||
$"&state={new RequestContextInfo(context, cookieOptions).Fingerprint}_{Enum.GetName(provider)}";
|
$"&state={GetHmacString(new RequestContextInfo(context, cookieOptions), secretKey)}_{Enum.GetName(provider)}";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -121,8 +129,6 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
|
|||||||
|
|
||||||
public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default)
|
public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default)
|
||||||
{
|
{
|
||||||
var requestContext = new RequestContextInfo(context, cookieOptions);
|
|
||||||
|
|
||||||
var partsState = state.Split('_');
|
var partsState = state.Split('_');
|
||||||
|
|
||||||
if (!Enum.TryParse<OAuthProvider>(partsState.Last(), true, out var provider) ||
|
if (!Enum.TryParse<OAuthProvider>(partsState.Last(), true, out var provider) ||
|
||||||
@ -133,9 +139,10 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
|
|||||||
throw new InvalidOperationException("Invalid authorization request.");
|
throw new InvalidOperationException("Invalid authorization request.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var fingerprint = string.Join("_", partsState.SkipLast(1));
|
var secretStateData = string.Join("_", partsState.SkipLast(1));
|
||||||
|
var secretData = GetHmacString(new RequestContextInfo(context, cookieOptions), secretKey);
|
||||||
|
|
||||||
if (requestContext.Fingerprint != fingerprint)
|
if (secretData != secretStateData)
|
||||||
{
|
{
|
||||||
logger.LogWarning("Fingerprint mismatch. Possible CSRF attack detected.");
|
logger.LogWarning("Fingerprint mismatch. Possible CSRF attack detected.");
|
||||||
throw new SecurityException("Suspicious activity detected. Please try again.");
|
throw new SecurityException("Suspicious activity detected. Please try again.");
|
||||||
|
Reference in New Issue
Block a user