Concurrent lisans sistemi SessionId düzenlemesi

This commit is contained in:
Sedat ÖZTÜRK 2026-06-19 11:49:49 +03:00
parent cb6b38f141
commit d9f51b1c71
14 changed files with 354 additions and 55 deletions

View file

@ -28,5 +28,6 @@ public class CreateUpdateTenantInput
public string Email { get; set; }
public string Website { get; set; }
public string MenuGroup { get; set; }
public int MaxConcurrentUsers { get; set; }
}

View file

@ -254,6 +254,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic
entity.SetEmail(input.Data.Email);
entity.SetWebsite(input.Data.Website);
entity.SetMenuGroup(input.Data.MenuGroup);
entity.SetMaxConcurrentUsers(input.Data.MaxConcurrentUsers);
await tenantRepository.UpdateAsync(entity, autoSave: true);
}
@ -289,6 +290,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic
entity.SetEmail(input.Data.Email);
entity.SetWebsite(input.Data.Website);
entity.SetMenuGroup(input.Data.MenuGroup);
entity.SetMaxConcurrentUsers(input.Data.MaxConcurrentUsers);
await tenantRepository.UpdateAsync(entity, autoSave: true);
}

View file

@ -5090,6 +5090,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Text = "App.Platform.HangfireLogin",
AuthName=listFormName,
Url= swaggerRootUrl + "/Account/Login",
Icon = "login",
IsVisible = true,
},
new() {
@ -5098,6 +5099,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Text = "App.Platform.HangfireOpen",
AuthName=listFormName,
Url= swaggerRootUrl + "/hangfire",
Icon = "runner",
IsVisible = true,
},
new() {
@ -5106,6 +5108,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Text = "App.Platform.HangfireRefresh",
AuthName=listFormName,
OnClick="UiEvalService.ApiGenerateBackgroundWorkers();",
Icon = "refresh",
IsVisible = true,
},
}),
@ -6137,6 +6140,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Text = "Swagger",
AuthName = listFormName,
Url= swaggerRootUrl + "/swagger/index.html",
Icon = "doc",
IsVisible = true
},
}),

View file

@ -19,5 +19,11 @@ public static class PlatformClaimTypes
public static KeyValuePair<string, string> IsVerified { get; set; }
= new KeyValuePair<string, string>(PlatformConsts.AbpIdentity.User.IsVerified, "platform.is_verified");
/// <summary>
/// Default: "platform.session_id".
/// </summary>
public static KeyValuePair<string, string> SessionId { get; set; }
= new KeyValuePair<string, string>("platform.session_id", "platform.session_id");
}

View file

@ -1,14 +1,18 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Sozsoft.Platform.Entities;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Sozsoft.Platform.Identity;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity;
using Volo.Abp.Users;
namespace Sozsoft.Platform.Extensions;
@ -29,6 +33,20 @@ public class PlatformApplicationConfigurationContributor : IApplicationConfigura
var customComponentRepository = context.ServiceProvider.GetRequiredService<IRepository<CustomComponent, Guid>>();
var customComponents = await customComponentRepository.GetListAsync() ?? throw new EntityNotFoundException("RecordNotFound");
context.ApplicationConfiguration.SetProperty("customComponents", customComponents);
var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>();
if (currentUser.Id.HasValue)
{
var sessionRepository = context.ServiceProvider.GetRequiredService<IIdentitySessionRepository>();
var sessions = await sessionRepository.GetListAsync(userId: currentUser.Id.Value);
var claimSessionId = currentUser.FindClaim(PlatformClaimTypes.SessionId.Key)?.Value;
var sessionId = sessions.Exists(session => session.SessionId == claimSessionId)
? claimSessionId
: sessions.OrderByDescending(session => session.SignedIn).FirstOrDefault()?.SessionId;
context.ApplicationConfiguration.CurrentUser.SessionId = sessionId;
context.ApplicationConfiguration.SetProperty("currentUserSessionId", sessionId);
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Threading;
namespace Sozsoft.Platform.Identity;
public static class PlatformSessionClaimContext
{
private static readonly AsyncLocal<string> CurrentSessionId = new();
public static string SessionId => CurrentSessionId.Value;
public static IDisposable Change(string sessionId)
{
var previous = CurrentSessionId.Value;
CurrentSessionId.Value = sessionId;
return new DisposeAction(() => CurrentSessionId.Value = previous);
}
private sealed class DisposeAction : IDisposable
{
private readonly Action action;
public DisposeAction(Action action)
{
this.action = action;
}
public void Dispose()
{
action();
}
}
}

View file

@ -10,6 +10,7 @@ using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.TenantManagement;
using Volo.Abp.Timing;
using Volo.Abp.Uow;
using Sozsoft.Platform.Extensions;
@ -27,26 +28,31 @@ public class PlatformSessionCleanupWorker : ISessionCleanupWorker, ITransientDep
private readonly ICurrentTenant currentTenant;
private readonly ILogger<PlatformSessionCleanupWorker> logger;
private readonly IOptions<OpenIddictServerOptions> openIddictOptions;
private readonly IUnitOfWorkManager unitOfWorkManager;
private readonly IClock clock;
public PlatformSessionCleanupWorker(
IIdentitySessionRepository sessionRepo,
ITenantRepository tenantRepository,
ICurrentTenant currentTenant,
ILogger<PlatformSessionCleanupWorker> logger,
IOptions<OpenIddictServerOptions> openIddictOptions)
IOptions<OpenIddictServerOptions> openIddictOptions,
IUnitOfWorkManager unitOfWorkManager,
IClock clock)
{
this.sessionRepo = sessionRepo;
this.tenantRepository = tenantRepository;
this.currentTenant = currentTenant;
this.logger = logger;
this.openIddictOptions = openIddictOptions;
this.unitOfWorkManager = unitOfWorkManager;
this.clock = clock;
}
[UnitOfWork]
public virtual async Task StartAsync(CancellationToken cancellationToken = default)
{
var refreshTokenLifetime = openIddictOptions.Value.RefreshTokenLifetime ?? TimeSpan.FromMinutes(90);
var cutoff = DateTime.Now - refreshTokenLifetime - TimeSpan.FromMinutes(5);
var cutoff = clock.Now - refreshTokenLifetime - TimeSpan.FromMinutes(5);
try
{
@ -62,6 +68,8 @@ public class PlatformSessionCleanupWorker : ISessionCleanupWorker, ITransientDep
{
using (currentTenant.Change(tenantId))
{
using var uow = unitOfWorkManager.Begin(requiresNew: true, isTransactional: false);
var sessions = await sessionRepo.GetListAsync(cancellationToken: cancellationToken);
var staleSessions = sessions
.Where(s => (s.LastAccessed ?? s.SignedIn) < cutoff)
@ -76,8 +84,10 @@ public class PlatformSessionCleanupWorker : ISessionCleanupWorker, ITransientDep
tenantId,
session.UserId,
session.SignedIn,
DateTime.UtcNow);
clock.Now);
}
await uow.CompleteAsync(cancellationToken);
}
}

View file

@ -8,7 +8,9 @@ using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.Uow;
using System.Linq;
using Volo.Abp;
namespace Sozsoft.Platform.Identity;
@ -23,15 +25,18 @@ public class PlatformSessionRevocationHandler :
{
private readonly IIdentitySessionRepository sessionRepo;
private readonly ICurrentTenant currentTenant;
private readonly IUnitOfWorkManager unitOfWorkManager;
private readonly ILogger<PlatformSessionRevocationHandler> logger;
public PlatformSessionRevocationHandler(
IIdentitySessionRepository sessionRepo,
ICurrentTenant currentTenant,
IUnitOfWorkManager unitOfWorkManager,
ILogger<PlatformSessionRevocationHandler> logger)
{
this.sessionRepo = sessionRepo;
this.currentTenant = currentTenant;
this.unitOfWorkManager = unitOfWorkManager;
this.logger = logger;
}
@ -45,14 +50,20 @@ public class PlatformSessionRevocationHandler :
// Token'dan tenant ID'yi al.
var tenantId = ParseTenantId(context.GenericTokenPrincipal?.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId)?.Value);
var sessionId = context.GenericTokenPrincipal?.Claims.FirstOrDefault(c => c.Type == PlatformClaimTypes.SessionId.Key)?.Value;
try
{
// Sadece token'ın ait olduğu tenant'taki oturumları sil.
using (currentTenant.Change(tenantId))
{
using var uow = unitOfWorkManager.Begin(requiresNew: true, isTransactional: false);
var sessions = await sessionRepo.GetListAsync(userId: userGuid);
foreach (var session in sessions)
var sessionsToDelete = sessionId.IsNullOrWhiteSpace()
? sessions
: sessions.Where(session => session.SessionId == sessionId).ToList();
foreach (var session in sessionsToDelete)
{
await sessionRepo.DeleteAsync(session);
@ -63,6 +74,8 @@ public class PlatformSessionRevocationHandler :
session.SignedIn,
DateTime.UtcNow);
}
await uow.CompleteAsync();
}
}
catch (Exception ex)

View file

@ -0,0 +1,69 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Volo.Abp;
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 readonly IIdentitySessionRepository sessionRepository;
private readonly ICurrentTenant currentTenant;
private readonly IUnitOfWorkManager unitOfWorkManager;
public PlatformSessionValidationMiddleware(
IIdentitySessionRepository sessionRepository,
ICurrentTenant currentTenant,
IUnitOfWorkManager unitOfWorkManager)
{
this.sessionRepository = sessionRepository;
this.currentTenant = currentTenant;
this.unitOfWorkManager = unitOfWorkManager;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
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))
{
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;
}
}
await next(context);
}
private static Guid? ParseTenantId(string value)
{
return !string.IsNullOrWhiteSpace(value) && Guid.TryParse(value, out var id) ? id : null;
}
}

View file

@ -17,6 +17,7 @@ using Volo.Abp.MultiTenancy;
using Volo.Abp.Settings;
using Volo.Abp.TenantManagement;
using Volo.Abp.Timing;
using Volo.Abp.Uow;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
@ -37,6 +38,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
private readonly IdentityUserManager userManager;
private readonly IIdentitySessionRepository identitySessionRepository;
private readonly ICurrentTenant currentTenant;
private readonly IUnitOfWorkManager unitOfWorkManager;
public PlatformSignInManager(
IdentityUserManager userManager,
@ -53,7 +55,8 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
IRepository<WorkHour, Guid> repositoryWorkHour,
ITenantRepository tenantRepository,
IIdentitySessionRepository identitySessionRepository,
ICurrentTenant currentTenant
ICurrentTenant currentTenant,
IUnitOfWorkManager unitOfWorkManager
) : base(
userManager,
contextAccessor,
@ -72,6 +75,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
this.userManager = userManager;
this.identitySessionRepository = identitySessionRepository;
this.currentTenant = currentTenant;
this.unitOfWorkManager = unitOfWorkManager;
}
public async Task<bool> PreSignInCheckAsync(IdentityUser user)
@ -296,6 +300,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
// doğru tenant izolasyonu için güvenli bir şekilde değiştirilir.
using (currentTenant.Change(user.TenantId))
{
using var uow = unitOfWorkManager.Begin(requiresNew: true, isTransactional: false);
var sessions = await identitySessionRepository.GetListAsync();
// Bu kullanıcı hariç diğer distinct aktif kullanıcı sayısını hesapla.
@ -313,6 +318,8 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
user.TenantId, maxConcurrentUsers.Value, otherActiveUserCount);
return false;
}
await uow.CompleteAsync();
}
return true;

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Sozsoft.Platform.Extensions;
@ -37,6 +38,8 @@ public class PlatformTokenController : TokenController
private readonly ICaptchaManager captchaManager;
private readonly IPlatformSignInManager platformSignInManager;
private readonly IIdentitySessionRepository identitySessionRepository;
private readonly IOpenIddictTokenManager openIddictTokenManager;
private readonly IUnitOfWorkManager unitOfWorkManager;
private IStringLocalizer LP;
public PlatformTokenController(
@ -44,12 +47,16 @@ public class PlatformTokenController : TokenController
ICaptchaManager captchaManager,
IPlatformSignInManager platformSignInManager,
IIdentitySessionRepository identitySessionRepository,
IOpenIddictTokenManager openIddictTokenManager,
IUnitOfWorkManager unitOfWorkManager,
IStringLocalizer<PlatformResource> LP) : base()
{
this.emailSender = emailSender;
this.captchaManager = captchaManager;
this.platformSignInManager = platformSignInManager;
this.identitySessionRepository = identitySessionRepository;
this.openIddictTokenManager = openIddictTokenManager;
this.unitOfWorkManager = unitOfWorkManager;
this.LP = LP;
}
@ -235,43 +242,32 @@ Your login code: {twoFactorToken}";
protected override async Task<IActionResult> SetSuccessResultAsync(OpenIddictRequest request, IdentityUser user)
{
var result = await base.SetSuccessResultAsync(request, user);
await ManageIdentitySessionAsync(user, request);
return result;
var sessionId = request.IsRefreshTokenGrantType()
? PlatformSessionClaimContext.SessionId
: CreateSessionId();
using (PlatformSessionClaimContext.Change(sessionId))
{
var result = await base.SetSuccessResultAsync(request, user);
await ManageIdentitySessionAsync(user, request, sessionId);
return result;
}
}
private async Task ManageIdentitySessionAsync(IdentityUser user, OpenIddictRequest request)
private async Task ManageIdentitySessionAsync(IdentityUser user, OpenIddictRequest request, string sessionId)
{
// Refresh grant session yönetimi HandleRefreshTokenAsync'da yapılır.
if (request.IsRefreshTokenGrantType()) return;
try
{
using (CurrentTenant.Change(user.TenantId))
{
var existingSessions = await identitySessionRepository.GetListAsync(userId: user.Id);
if (existingSessions.Any())
{
foreach (var session in existingSessions)
{
await identitySessionRepository.DeleteAsync(session);
}
}
else
{
if (!await platformSignInManager.CheckConcurrentLimitAsync(user))
{
throw new UserFriendlyException(PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit);
}
}
await InsertSessionAsync(user, request.ClientId);
}
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);
}
}
@ -279,56 +275,185 @@ Your login code: {twoFactorToken}";
protected override async Task<IActionResult> HandleRefreshTokenAsync(OpenIddictRequest request)
{
var info = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
var tenantId = ParseTenantId(info.Principal?.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId)?.Value);
var userId = info.Principal?.GetClaim(OpenIddictConstants.Claims.Subject);
var tenantId = await ResolveRefreshTenantIdAsync(info);
var userGuid = await ResolveRefreshUserIdAsync(request, info, tenantId);
var sessionId = await ResolveRefreshSessionIdAsync(request, info, tenantId);
IdentityUser refreshUser = null;
using (CurrentTenant.Change(tenantId))
{
if (!userId.IsNullOrWhiteSpace() && Guid.TryParse(userId, out var userGuid))
if (userGuid.HasValue)
{
refreshUser = await UserManager.FindByIdAsync(userId);
if (refreshUser != null)
if (!await IsCurrentSessionAsync(tenantId, userGuid.Value, sessionId))
{
// Concurrent login limiti refresh token için de geçerlidir; yeni oturum açılmadan önce eskiler temizlenir.
if (!await platformSignInManager.CheckConcurrentLimitAsync(refreshUser))
throw new UserFriendlyException(PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit);
var existingSessions = await identitySessionRepository.GetListAsync(userId: userGuid);
foreach (var session in existingSessions)
{
await identitySessionRepository.DeleteAsync(session);
}
return InvalidRefreshSessionResult();
}
refreshUser = await UserManager.FindByIdAsync(userGuid.Value.ToString());
}
var result = await base.HandleRefreshTokenAsync(request);
IActionResult result;
using (PlatformSessionClaimContext.Change(sessionId.IsNullOrWhiteSpace() ? CreateSessionId() : sessionId))
{
result = await base.HandleRefreshTokenAsync(request);
}
if (result is Microsoft.AspNetCore.Mvc.SignInResult && refreshUser != null)
{
try
{
await InsertSessionAsync(refreshUser, request.ClientId);
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Failed to create IdentitySession after refresh token. UserId: {UserId}", refreshUser.Id);
}
await ReplaceUserSessionAsync(
refreshUser,
request.ClientId,
sessionId.IsNullOrWhiteSpace() ? CreateSessionId() : sessionId,
checkConcurrentLimit: false);
}
return result;
}
}
private async Task InsertSessionAsync(IdentityUser user, string clientId)
private async Task<Guid?> ResolveRefreshTenantIdAsync(AuthenticateResult info)
{
var tenantId = ParseTenantId(info.Principal?.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId)?.Value);
if (tenantId.HasValue)
{
return tenantId;
}
var tenant = await TenantConfigurationProvider.GetAsync(saveResolveResult: false);
return tenant?.Id;
}
private async Task<Guid?> ResolveRefreshUserIdAsync(OpenIddictRequest request, AuthenticateResult info, Guid? tenantId)
{
var userId = info.Principal?.GetClaim(OpenIddictConstants.Claims.Subject);
var userGuid = ParseUserId(userId);
if (userGuid.HasValue)
{
return userGuid;
}
if (request.RefreshToken.IsNullOrWhiteSpace())
{
return null;
}
using (CurrentTenant.Change(tenantId))
{
var token = await openIddictTokenManager.FindByReferenceIdAsync(request.RefreshToken);
if (token == null)
{
return null;
}
var subject = await openIddictTokenManager.GetSubjectAsync(token);
return ParseUserId(subject);
}
}
private async Task<string> ResolveRefreshSessionIdAsync(OpenIddictRequest request, AuthenticateResult info, Guid? tenantId)
{
var sessionId = GetSessionId(info.Principal);
if (!sessionId.IsNullOrWhiteSpace())
{
return sessionId;
}
if (request.RefreshToken.IsNullOrWhiteSpace())
{
return null;
}
using (CurrentTenant.Change(tenantId))
{
var token = await openIddictTokenManager.FindByReferenceIdAsync(request.RefreshToken);
if (token == null)
{
return null;
}
var properties = await openIddictTokenManager.GetPropertiesAsync(token);
return properties.TryGetValue(PlatformClaimTypes.SessionId.Key, out var propertySessionId)
? propertySessionId.GetString()
: null;
}
}
private static string CreateSessionId()
{
return Guid.NewGuid().ToString("N");
}
private static string GetSessionId(ClaimsPrincipal principal)
{
return principal?.FindFirst(PlatformClaimTypes.SessionId.Key)?.Value;
}
private async Task<bool> IsCurrentSessionAsync(Guid? tenantId, Guid userId, string sessionId)
{
if (sessionId.IsNullOrWhiteSpace())
{
return true;
}
using (CurrentTenant.Change(tenantId))
{
using var uow = unitOfWorkManager.Begin(requiresNew: true, isTransactional: false);
var sessions = await identitySessionRepository.GetListAsync(userId: userId);
var isCurrent = sessions.Any(session => session.SessionId == sessionId);
await uow.CompleteAsync();
return isCurrent;
}
}
private async Task ReplaceUserSessionAsync(
IdentityUser user,
string clientId,
string sessionId,
bool checkConcurrentLimit)
{
using (CurrentTenant.Change(user.TenantId))
{
using var uow = unitOfWorkManager.Begin(requiresNew: true, isTransactional: false);
var existingSessions = await identitySessionRepository.GetListAsync(userId: user.Id);
if (checkConcurrentLimit && !existingSessions.Any())
{
if (!await platformSignInManager.CheckConcurrentLimitAsync(user))
{
throw new UserFriendlyException(PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit);
}
}
foreach (var session in existingSessions)
{
await identitySessionRepository.DeleteAsync(session);
}
await InsertSessionAsync(user, clientId, sessionId);
await uow.CompleteAsync();
}
}
private IActionResult InvalidRefreshSessionResult()
{
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The refresh token session is no longer active."
});
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
private async Task InsertSessionAsync(IdentityUser user, string clientId, string sessionId)
{
var userAgent = HttpContext?.Request?.Headers["User-Agent"].ToString();
var deviceInfo = userAgent?.Length > 64 ? userAgent[..64] : userAgent;
var session = new IdentitySession(
GuidGenerator.Create(),
Guid.NewGuid().ToString("N"),
sessionId,
"Web",
deviceInfo,
user.Id,
@ -343,6 +468,9 @@ Your login code: {twoFactorToken}";
private static Guid? ParseTenantId(string value) =>
!string.IsNullOrWhiteSpace(value) && Guid.TryParse(value, out var id) ? id : null;
private static Guid? ParseUserId(string value) =>
!string.IsNullOrWhiteSpace(value) && Guid.TryParse(value, out var id) ? id : null;
}

View file

@ -89,6 +89,12 @@ public class PlatformUserClaimsPrincipalFactory : AbpUserClaimsPrincipalFactory,
identity.AddClaim(new Claim(PlatformConsts.AbpIdentity.User.IsVerified, user.GetIsVerified().ToString()));
if (!PlatformSessionClaimContext.SessionId.IsNullOrWhiteSpace())
{
identity.AddIfNotContains(
new Claim(PlatformClaimTypes.SessionId.Key, PlatformSessionClaimContext.SessionId));
}
return identity;
}
}

View file

@ -509,6 +509,7 @@ public class PlatformHttpApiHostModule : AbpModule
}
app.UseUnitOfWork();
app.UseMiddleware<PlatformSessionValidationMiddleware>();
app.UseDynamicClaims();
app.UseAuthorization();

View file

@ -140,6 +140,7 @@ export interface CurrentUserDto {
phoneNumber?: string
phoneNumberVerified: boolean
roles: string[]
sessionId: string
}
export interface DateTimeFormatDto {