diff --git a/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs b/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs
index 2661298405..6f950524f3 100644
--- a/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs
+++ b/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs
@@ -276,4 +276,37 @@ public static class AbpClaimsIdentityExtensions
return principal;
}
+
+ public static Guid? FindSessionId([NotNull] this IIdentity identity)
+ {
+ Check.NotNull(identity, nameof(identity));
+
+ var claimsIdentity = identity as ClaimsIdentity;
+
+ var sessionIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.SessionId);
+ if (sessionIdOrNull == null || sessionIdOrNull.Value.IsNullOrWhiteSpace())
+ {
+ return null;
+ }
+
+ if (Guid.TryParse(sessionIdOrNull.Value, out var guid))
+ {
+ return guid;
+ }
+
+ return null;
+ }
+
+ public static string? FindSessionId([NotNull] this ClaimsPrincipal principal)
+ {
+ Check.NotNull(principal, nameof(principal));
+
+ var sessionIdOrNull = principal.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.SessionId);
+ if (sessionIdOrNull == null || sessionIdOrNull.Value.IsNullOrWhiteSpace())
+ {
+ return null;
+ }
+
+ return sessionIdOrNull.Value;
+ }
}
diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs
index 63628bd33a..52f721a4e6 100644
--- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs
+++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs
@@ -87,9 +87,15 @@ public static class AbpClaimTypes
/// Default: "impersonator_username".
///
public static string ImpersonatorUserName { get; set; } = "impersonator_username";
-
+
///
/// Default: "picture".
///
public static string Picture { get; set; } = "picture";
+
+ ///
+ /// Default: "session_id".
+ ///
+ public static string SessionId { get; set; } = "session_id";
+
}
diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUserExtensions.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUserExtensions.cs
index b702bcffde..3007cc95c3 100644
--- a/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUserExtensions.cs
+++ b/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUserExtensions.cs
@@ -70,4 +70,26 @@ public static class CurrentUserExtensions
{
return currentUser.FindClaimValue(AbpClaimTypes.ImpersonatorUserName);
}
+
+ public static Guid GetSessionId([NotNull] this ICurrentUser currentUser)
+ {
+ var sessionId = currentUser.FindSessionId();
+ Debug.Assert(sessionId != null, "sessionId != null");
+ return sessionId!.Value;
+ }
+
+ public static Guid? FindSessionId([NotNull] this ICurrentUser currentUser)
+ {
+ var sessionId = currentUser.FindClaimValue(AbpClaimTypes.SessionId);
+ if (sessionId.IsNullOrWhiteSpace())
+ {
+ return null;
+ }
+ if (Guid.TryParse(sessionId, out var guid))
+ {
+ return guid;
+ }
+
+ return null;
+ }
}
diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySessionConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySessionConsts.cs
index 7182afa28e..96cb8df5c9 100644
--- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySessionConsts.cs
+++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySessionConsts.cs
@@ -2,7 +2,11 @@ namespace Volo.Abp.Identity;
public class IdentitySessionConsts
{
- public static int MaxDeviceLength { get; set; } = 128;
+ public static int MaxSessionIdLength { get; set; } = 128;
+
+ public static int MaxDeviceLength { get; set; } = 64;
+
+ public static int MaxDeviceInfoLength { get; set; } = 64;
public static int MaxClientIdLength { get; set; } = 64;
diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySessionDevices.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySessionDevices.cs
new file mode 100644
index 0000000000..989841d034
--- /dev/null
+++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySessionDevices.cs
@@ -0,0 +1,8 @@
+namespace Volo.Abp.Identity;
+
+public static class IdentitySessionDevices
+{
+ public const string Web = "Web";
+
+ public const string OAuth = "OAuth";
+}
diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentitySessionRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentitySessionRepository.cs
index 2cb0875674..b425b24c33 100644
--- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentitySessionRepository.cs
+++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentitySessionRepository.cs
@@ -1,9 +1,20 @@
using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Identity;
public interface IIdentitySessionRepository : IBasicRepository
{
+ Task FindAsync(string sessionId, CancellationToken cancellationToken = default);
+ Task GetAsync(string sessionId, CancellationToken cancellationToken = default);
+
+ Task> GetListAsync(Guid userId, CancellationToken cancellationToken = default);
+
+ Task DeleteAllAsync(Guid userId, Guid? exceptSessionId = null, CancellationToken cancellationToken = default);
+
+ Task DeleteAllAsync(Guid userId, string device, Guid? exceptSessionId = null, CancellationToken cancellationToken = default);
}
diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySession.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySession.cs
index 0dd53c5f74..9e63b8e0cc 100644
--- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySession.cs
+++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySession.cs
@@ -1,16 +1,21 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Domain.Entities;
+using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Identity;
-public class IdentitySession : BasicAggregateRoot
+public class IdentitySession : BasicAggregateRoot, IMultiTenant
{
+ public virtual string SessionId { get; protected set; }
+
///
- /// Web, CLI, STUDIO, ...
+ /// Web, OAuth ...
///
public virtual string Device { get; protected set; }
+ public virtual string DeviceInfo { get; protected set; }
+
public virtual Guid? TenantId { get; protected set; }
public virtual Guid UserId { get; protected set; }
@@ -30,16 +35,20 @@ public class IdentitySession : BasicAggregateRoot
public IdentitySession(
Guid id,
+ string sessionId,
string device,
+ string deviceInfo,
Guid userId,
Guid? tenantId,
string clientId,
string ipAddresses,
DateTime signedIn,
- DateTime? lastAccessed)
+ DateTime? lastAccessed = null)
{
Id = id;
+ SessionId = sessionId;
Device = device;
+ DeviceInfo = deviceInfo;
UserId = userId;
TenantId = tenantId;
ClientId = clientId;
diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySessionManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySessionManager.cs
new file mode 100644
index 0000000000..012ddd87ab
--- /dev/null
+++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySessionManager.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Services;
+using Volo.Abp.Users;
+
+namespace Volo.Abp.Identity;
+
+public class IdentitySessionManager : DomainService
+{
+ protected IIdentitySessionRepository IdentitySessionRepository { get; }
+ protected ICurrentUser CurrentUser { get; }
+
+ public IdentitySessionManager(IIdentitySessionRepository identitySessionRepository, ICurrentUser currentUser)
+ {
+ IdentitySessionRepository = identitySessionRepository;
+ CurrentUser = currentUser;
+ }
+
+ public virtual async Task CreateAsync(
+ string sessionId,
+ string device,
+ string deviceInfo,
+ Guid userId,
+ Guid? tenantId,
+ string clientId,
+ string ipAddresses)
+ {
+ Check.NotNullOrWhiteSpace(sessionId, nameof(sessionId));
+ Check.NotNullOrWhiteSpace(device, nameof(device));
+
+ var session = new IdentitySession(
+ GuidGenerator.Create(),
+ sessionId,
+ device,
+ deviceInfo,
+ userId,
+ tenantId,
+ clientId,
+ ipAddresses,
+ Clock.Now
+ );
+
+ return await IdentitySessionRepository.InsertAsync(session);
+ }
+
+ public virtual async Task UpdateAsync(IdentitySession session)
+ {
+ await IdentitySessionRepository.UpdateAsync(session);
+ }
+
+ public virtual async Task> GetListAsync(Guid userId)
+ {
+ return await IdentitySessionRepository.GetListAsync(userId);
+ }
+
+ public virtual async Task GetAsync(Guid id)
+ {
+ return await IdentitySessionRepository.GetAsync(id);
+ }
+
+ public virtual async Task FindAsync(Guid id)
+ {
+ return await IdentitySessionRepository.FindAsync(id);
+ }
+
+ public virtual async Task GetAsync(string sessionId)
+ {
+ return await IdentitySessionRepository.GetAsync(sessionId);
+ }
+
+ public virtual async Task FindAsync(string sessionId)
+ {
+ return await IdentitySessionRepository.FindAsync(sessionId);
+ }
+
+ public virtual async Task RevokeAsync(Guid id)
+ {
+ var session = await IdentitySessionRepository.GetAsync(id);
+ await IdentitySessionRepository.DeleteAsync(session);
+ }
+
+ public virtual async Task RevokeAsync(string sessionId)
+ {
+ var session = await IdentitySessionRepository.GetAsync(sessionId);
+ await IdentitySessionRepository.DeleteAsync(session);
+ }
+
+ public virtual async Task RevokeAllAsync(Guid userId, Guid? exceptSessionId = null)
+ {
+ await IdentitySessionRepository.DeleteAllAsync(userId, exceptSessionId);
+ }
+
+ public virtual async Task RevokeAllAsync(Guid userId, string device, Guid? exceptSessionId = null)
+ {
+ await IdentitySessionRepository.DeleteAllAsync(userId, device, exceptSessionId);
+ }
+}
diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentitySessionRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentitySessionRepository.cs
index e86e57d0ee..5a583531aa 100644
--- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentitySessionRepository.cs
+++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentitySessionRepository.cs
@@ -1,4 +1,11 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
@@ -12,4 +19,34 @@ public class EfCoreIdentitySessionRepository : EfCoreRepository FindAsync(string sessionId, CancellationToken cancellationToken = default)
+ {
+ return await (await GetDbSetAsync()).FirstOrDefaultAsync(x => x.SessionId == sessionId, GetCancellationToken(cancellationToken));
+ }
+
+ public virtual async Task GetAsync(string sessionId, CancellationToken cancellationToken = default)
+ {
+ var session = await FindAsync(sessionId, cancellationToken);
+ if (session == null)
+ {
+ throw new EntityNotFoundException(typeof(IdentitySession));
+ }
+
+ return session;
+ }
+
+ public virtual async Task> GetListAsync(Guid userId, CancellationToken cancellationToken = default)
+ {
+ return await (await GetDbSetAsync()).Where(x => x.UserId == userId).ToListAsync(GetCancellationToken(cancellationToken));
+ }
+
+ public virtual async Task DeleteAllAsync(Guid userId, Guid? exceptSessionId = null, CancellationToken cancellationToken = default)
+ {
+ await DeleteDirectAsync(x => x.UserId == userId && x.Id != exceptSessionId, cancellationToken);
+ }
+
+ public virtual async Task DeleteAllAsync(Guid userId, string device, Guid? exceptSessionId = null, CancellationToken cancellationToken = default)
+ {
+ await DeleteDirectAsync(x => x.UserId == userId && x.Device == device && x.Id != exceptSessionId, cancellationToken);
+ }
}
diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs
index 6258f1b24b..f94f4690cc 100644
--- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs
+++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs
@@ -285,11 +285,14 @@ public static class IdentityDbContextModelBuilderExtensions
b.ConfigureByConvention();
+ b.Property(x => x.SessionId).HasMaxLength(IdentitySessionConsts.MaxSessionIdLength).IsRequired();
b.Property(x => x.Device).HasMaxLength(IdentitySessionConsts.MaxDeviceLength).IsRequired();
+ b.Property(x => x.DeviceInfo).HasMaxLength(IdentitySessionConsts.MaxDeviceInfoLength);
b.Property(x => x.ClientId).HasMaxLength(IdentitySessionConsts.MaxClientIdLength);
b.Property(x => x.IpAddresses).HasMaxLength(IdentitySessionConsts.MaxIpAddressesLength);
- b.HasIndex(x => new { x.Device });
+ b.HasIndex(x => x.SessionId);
+ b.HasIndex(x => x.Device );
b.HasIndex(x => new { x.TenantId, x.UserId });
b.ApplyObjectExtensionMappings();
diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySessionRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySessionRepository.cs
index 9f873165a0..222d1d432b 100644
--- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySessionRepository.cs
+++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySessionRepository.cs
@@ -1,4 +1,10 @@
using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MongoDB.Driver;
+using MongoDB.Driver.Linq;
+using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.MongoDB;
@@ -11,4 +17,39 @@ public class MongoIdentitySessionRepository : MongoDbRepository FindAsync(string sessionId, CancellationToken cancellationToken = default)
+ {
+ return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken)))
+ .As>()
+ .FirstOrDefaultAsync(x => x.SessionId == sessionId, GetCancellationToken(cancellationToken));
+ }
+
+ public virtual async Task GetAsync(string sessionId, CancellationToken cancellationToken = default)
+ {
+ var session = await FindAsync(sessionId, cancellationToken);
+ if (session == null)
+ {
+ throw new EntityNotFoundException(typeof(IdentitySession));
+ }
+
+ return session;
+ }
+
+ public virtual async Task> GetListAsync(Guid userId, CancellationToken cancellationToken = default)
+ {
+ return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken)))
+ .As>()
+ .Where(x => x.UserId == userId).ToListAsync(GetCancellationToken(cancellationToken));
+ }
+
+ public virtual async Task DeleteAllAsync(Guid userId, Guid? exceptSessionId = null, CancellationToken cancellationToken = default)
+ {
+ await DeleteDirectAsync(x => x.UserId == userId && x.Id != exceptSessionId, cancellationToken);
+ }
+
+ public virtual async Task DeleteAllAsync(Guid userId, string device, Guid? exceptSessionId = null, CancellationToken cancellationToken = default)
+ {
+ await DeleteDirectAsync(x => x.UserId == userId && x.Device == device && x.Id != exceptSessionId, cancellationToken);
+ }
}