Claude güncellemesi Login.Result düzeltmeleri

This commit is contained in:
Sedat ÖZTÜRK 2026-08-09 12:48:21 +03:00
parent b9550acda2
commit 2368ee3825
6 changed files with 65 additions and 27 deletions

View file

@ -86,9 +86,11 @@ public class PlatformAccountController : AccountController
});
var result = new PlatformLoginResult(signInResult, LP);
if (result.PResult != PlatformLoginResultType.Succeeded && user.AccessFailedCount >= maxAccessFailedCount)
// Captcha yalnızca gerçek parola hatasında gösterilir. Lisans limiti, tenant pasif,
// çalışma saati gibi PreSignInCheck sonuçları ezilirse kullanıcı asıl sebebi göremez.
if (result.PResult == PlatformLoginResultType.Failed && user.AccessFailedCount >= maxAccessFailedCount)
{
// Display captcha
result.PResult = PlatformLoginResultType.ShowCaptcha;
}

View file

@ -27,6 +27,9 @@ public static class PlatformEventIds
public static EventId UserCannotSignInConcurrentUserLimit =
new(19, PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit);
public static EventId UserCannotSignInBranchLimit =
new(20, PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_BranchLimit);
}

View file

@ -17,9 +17,11 @@ using Sozsoft.Platform.Extensions;
namespace Sozsoft.Platform.Identity;
/// <summary>
/// AbpSessions tablosundaki atıl (idle) oturumları temizleyen Hangfire job'ı.
/// Hem Hangfire (DoWorkAsync) hem de PlatformBackgroundWorker altyapısından (ISessionCleanupWorker) çağrılabilir.
/// Atıl sayılma eşiği: OpenIddict refresh token ömrü + 5 dakika.
/// AbpSessions tablosundaki atıl (idle) oturumları temizler; böylece kapatılan tarayıcıların
/// tuttuğu concurrent user lisansları serbest kalır.
/// WorkerType = SessionCleanupWorker olan kayıt üzerinden PlatformBackgroundWorker tarafından çalıştırılır.
/// Atıl sayılma eşiği: OpenIddict refresh token ömrü + 5 dakika. Aktif kullanıcılarda her
/// refresh token isteğinde oturum satırı yeniden oluşturulduğu için SignedIn güncel kalır.
/// </summary>
public class PlatformSessionCleanupWorker : ISessionCleanupWorker, ITransientDependency
{

View file

@ -33,6 +33,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
{
private const string UserLicenseProductName = "Public.products.userLicense";
private const string BranchLicenseProductName = "Public.products.branchHosting";
private const string ApprovedOrderStatus = "Approved";
private readonly IClock clock;
private readonly IRepository<IpRestriction, Guid> repositoryIp;
@ -289,16 +290,24 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
/// Tenant'a ait aktif Order'lardaki kullanıcı lisansı limitini aşmamak için login'i engeller.
/// AbpSessions tablosundaki aktif oturumları sayar (farklı UserId'lere göre distinct).
/// Kullanıcının kendisinin zaten oturumu varsa (refresh senaryosu) yeni concurrent user sayılmaz.
/// Yalnızca Internal kullanıcılar lisans tüketir.
/// </summary>
public async Task<bool> CheckConcurrentLimitAsync(IdentityUser user)
{
// Eğer tenantId yoksa, kullanıcı tenant'a bağlı değil demektir.
// Eğer tenantId yoksa, kullanıcı tenant'a bağlı değil demektir (host).
// Bu durumda concurrent limit kontrolü yapılmaz.
if (!user.TenantId.HasValue)
{
return true;
}
// External kullanıcılar sayıma dahil edilmediği için limitle de kısıtlanmazlar.
// (Aksi halde lisans dolu iken lisans tüketmeyen kullanıcılar da bloke olurdu.)
if (user.GetUserType() != PlatformConsts.UserType.Internal)
{
return true;
}
var maxConcurrentUsers = await GetActiveUserLicenseQuantityAsync(user.TenantId.Value);
// Tenant bağlamını explicit olarak set et — hem password hem refresh token akışlarında
@ -306,7 +315,10 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
using (currentTenant.Change(user.TenantId))
{
using var uow = unitOfWorkManager.Begin(requiresNew: true, isTransactional: false);
var sessions = await identitySessionRepository.GetListAsync();
// maxResultCount explicit verilir; aksi halde sayfalama limiti sessizce
// eksik sayıma ve lisans limitinin aşılmasına yol açabilir.
var sessions = await identitySessionRepository.GetListAsync(maxResultCount: int.MaxValue);
// Kullanıcının kendi oturumu varsa (refresh) o kişi tekrar sayılmaz.
var activeUserIds = sessions
@ -316,11 +328,16 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
.ToList();
// Aktif session kullanıcılarını IdentityUserManager üzerinden topluca yükle
// ve yalnızca UserType = 0 (Internal) olanları concurrent limite dahil et.
var otherActiveUserCount = (await userManager.Users
.Where(u => activeUserIds.Contains(u.Id))
.ToListAsync())
.Count(u => u.GetUserType() == PlatformConsts.UserType.Internal);
// ve yalnızca UserType = Internal olanları concurrent limite dahil et.
// (UserType ExtraProperties içinde tutulduğu için filtre bellekte uygulanır.)
var otherActiveUserCount = activeUserIds.Count == 0
? 0
: (await userManager.Users
.Where(u => activeUserIds.Contains(u.Id))
.ToListAsync())
.Count(u => u.GetUserType() == PlatformConsts.UserType.Internal);
await uow.CompleteAsync();
if (otherActiveUserCount >= maxConcurrentUsers)
{
@ -329,8 +346,6 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
user.TenantId, maxConcurrentUsers, otherActiveUserCount);
return false;
}
await uow.CompleteAsync();
}
return true;
@ -349,7 +364,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
if (activeBranchCount > branchLicenseQuantity)
{
Logger.LogWarning(PlatformEventIds.UserCannotSignInConcurrentUserLimit,
Logger.LogWarning(PlatformEventIds.UserCannotSignInBranchLimit,
"Tenant {TenantId} branch license quantity of {Limit} is lower than active branch count {ActiveCount}.",
tenantId, branchLicenseQuantity, activeBranchCount);
return false;
@ -376,19 +391,28 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
return await GetActiveOrderItemQuantityAsync(tenantId, BranchLicenseProductName);
}
/// <summary>
/// Tenant'ın yürürlükteki (onaylı, aktif ve lisans tarihi geçerli) siparişlerindeki
/// ilgili ürün adedini toplar. Order kayıtları host tarafında tutulduğu için
/// tenant filtresi kapatılır ve TenantId ile açıkça filtrelenir.
/// </summary>
private async Task<int> GetActiveOrderItemQuantityAsync(Guid tenantId, string productName)
{
using (currentTenant.Change(null))
{
var orderQuery = await orderRepository.WithDetailsAsync(order => order.Items);
var activeOrders = await orderQuery
.Where(order => order.TenantId == tenantId && order.ApprovalStatus == "Approved" && order.IsActive)
.ToListAsync();
var now = clock.Now;
// Include gerekmiyor; toplam veritabanında hesaplandığı için GetQueryableAsync yeterli.
var orderQuery = await orderRepository.GetQueryableAsync();
return activeOrders
return await orderQuery
.Where(order => order.TenantId == tenantId
&& order.ApprovalStatus == ApprovedOrderStatus
&& order.IsActive
&& (order.LicenseStartTime == null || order.LicenseStartTime <= now)
&& (order.LicenseEndTime == null || order.LicenseEndTime >= now))
.SelectMany(order => order.Items)
.Where(item => item.ProductName == productName)
.Sum(item => item.Quantity);
.SumAsync(item => (int?)item.Quantity) ?? 0;
}
}
}

View file

@ -149,8 +149,10 @@ public class PlatformTokenController : TokenController
var result = new PlatformLoginResult(signInResult, LP);
if (!signInResult.Succeeded)
{
// Max deneme sayisini gectiyse kapca goster
if (user.AccessFailedCount >= maxAccessFailedCount)
// Max deneme sayisini gectiyse kapca goster.
// Yalnızca gerçek parola hatasında; lisans limiti, tenant pasif, çalışma saati gibi
// PreSignInCheck sonuçları ezilirse kullanıcı asıl sebebi göremez.
if (result.PResult == PlatformLoginResultType.Failed && user.AccessFailedCount >= maxAccessFailedCount)
{
result.PResult = PlatformLoginResultType.ShowCaptcha;
}
@ -263,11 +265,13 @@ Your login code: {twoFactorToken}";
{
await ReplaceUserSessionAsync(user, request.ClientId, sessionId, checkConcurrentLimit: true);
}
catch (UserFriendlyException) { throw; }
catch (Exception ex)
{
Logger.LogWarning(ex, "Failed to manage AbpSessions. UserId: {UserId}", user.Id);
throw new UserFriendlyException(PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit);
// Oturum satırı oluşmazsa PlatformSessionValidationMiddleware sonraki her isteği 401'e
// düşürür; bu yüzden hata yutulmaz. Lisans limiti zaten UserFriendlyException olarak gelir,
// diğer hataları lisans hatası gibi göstermek yanıltıcı olur.
Logger.LogError(ex, "Failed to manage AbpSessions. UserId: {UserId}", user.Id);
throw;
}
}

View file

@ -12,7 +12,10 @@ const PlatformLoginResultType = {
NotAllowedIp: 19, // IpRestriction tablosu, Mesaj: Identity:IpRestrictionError
LoginEndDateDue: 20, // LoginEndDate:>Today, Buton: ShowExtendMyLoginButton, Mesaj: Identity:LoginEndDateError, >LoginEndDate
ShowCaptcha: 21, // AccessFailedCount>AccountCaptchaMaxFailedAccessAttempts, Sayfa: Show Captcha
ConcurrentUserLimit: 22, // MaxConcurrentUsers limiti dolduğunda, Mesaj: Identity:ConcurrentUserLimitError
TenantIsPassive: 22, // Tenant pasif, Mesaj: Identity:TenantIsPassive
NotAllowedWorkHour: 23, // WorkHour tanimi disinda giris, Mesaj: Identity:LoginNotAllowed_WorkHour
BranchLimit: 24, // Aktif sube sayisi sube lisansini asiyor, Mesaj: Identity:BranchLimitError
ConcurrentUserLimit: 25, // Aktif kullanici lisansi dolduğunda, Mesaj: Identity:ConcurrentUserLimitError
}
export default PlatformLoginResultType