sozsoft-platform/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSessionValidationMiddleware.cs
2026-10-19 00:47:36 +03:00

125 lines
4.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Sozsoft.Platform.DbStartup;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.Uow;
namespace Sozsoft.Platform.Identity;
public class PlatformSessionValidationMiddleware : IMiddleware, ITransientDependency
{
private const string SetupStatusPath = DatabaseReadinessGate.SetupPathPrefix + "/application-status";
private readonly IIdentitySessionRepository sessionRepository;
private readonly ICurrentTenant currentTenant;
private readonly IUnitOfWorkManager unitOfWorkManager;
private readonly IConfiguration configuration;
public PlatformSessionValidationMiddleware(
IIdentitySessionRepository sessionRepository,
ICurrentTenant currentTenant,
IUnitOfWorkManager unitOfWorkManager,
IConfiguration configuration)
{
this.sessionRepository = sessionRepository;
this.currentTenant = currentTenant;
this.unitOfWorkManager = unitOfWorkManager;
this.configuration = configuration;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (ShouldSkipSessionValidation(context))
{
await next(context);
return;
}
if (context.User.Identity?.IsAuthenticated != true)
{
await next(context);
return;
}
var userIdValue = context.User.FindFirst(AbpClaimTypes.UserId)?.Value;
var sessionId = context.User.FindFirst(PlatformClaimTypes.SessionId.Key)?.Value;
if (!Guid.TryParse(userIdValue, out var userId) || sessionId.IsNullOrWhiteSpace())
{
await next(context);
return;
}
var tenantId = ParseTenantId(context.User.FindFirst(AbpClaimTypes.TenantId)?.Value);
using (currentTenant.Change(tenantId))
{
try
{
using var uow = unitOfWorkManager.Begin(requiresNew: true, isTransactional: false);
var sessions = await sessionRepository.GetListAsync(userId: userId);
var isCurrentSession = sessions.Any(session => session.SessionId == sessionId);
await uow.CompleteAsync();
if (!isCurrentSession)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
}
catch (Exception ex) when (IsDatabaseUnavailableException(ex))
{
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
return;
}
}
await next(context);
}
/// <summary>
/// Kurulum endpointleri için oturum doğrulaması ne zaman atlanır.
///
/// - Durum sorgusu: anonim erişime açık, oturum bilgisine hiç ihtiyaç duymaz.
/// - Diğer /api/setup istekleri: yalnızca veritabanı erişilemezken atlanır. O durumda
/// oturum zaten doğrulanamaz (kayıtlar veritabanında) ve doğrulamaya çalışmak, tam da
/// veritabanını onaracak endpoint'i 503 ile bloke eder. Güvenlik kaybı yoktur: veritabanı
/// düşükken migration yalnızca kurulum parolasıyla çalışır, oturum yetki vermez
/// (bkz. SetupController.AuthorizeMigrateRequestAsync).
///
/// Veritabanı ayaktayken kurulum endpointleri de normal oturum doğrulamasından geçer;
/// böylece iptal edilmiş bir oturum migration tetikleyemez.
/// </summary>
private bool ShouldSkipSessionValidation(HttpContext context)
{
var path = context.Request.Path;
if (!path.StartsWithSegments(DatabaseReadinessGate.SetupPathPrefix))
{
return false;
}
return path.StartsWithSegments(SetupStatusPath) ||
!DatabaseReadinessChecker.GetStatus(configuration).IsReady;
}
private static Guid? ParseTenantId(string value)
{
return !string.IsNullOrWhiteSpace(value) && Guid.TryParse(value, out var id) ? id : null;
}
private static bool IsDatabaseUnavailableException(Exception exception)
{
return exception is SqlException ||
exception is TimeoutException ||
exception is TaskCanceledException ||
exception is InvalidOperationException && exception.InnerException != null &&
IsDatabaseUnavailableException(exception.InnerException);
}
}