From 6b7bfb1ce640231aed35312b1c3616c2f4daea4e Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 2 Dec 2025 18:13:09 +0800 Subject: [PATCH] Track and update user's last sign-in time --- .../Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs | 12 +++++++++++- .../Volo/Abp/Identity/IdentityUser.cs | 10 ++++++++++ .../Controllers/TokenController.Password.cs | 3 +++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs index f4b4a2d35a..90b39e9953 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; @@ -120,4 +123,11 @@ public class AbpSignInManager : SignInManager { return await base.SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor); } + + public override async Task SignInWithClaimsAsync(IdentityUser user, AuthenticationProperties authenticationProperties, IEnumerable additionalClaims) + { + user.SetLastSignInTime(DateTimeOffset.UtcNow); + await UserManager.UpdateAsync(user); + await base.SignInWithClaimsAsync(user, authenticationProperties, additionalClaims); + } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs index bf81984ed5..3f4a91117c 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs @@ -127,6 +127,11 @@ public class IdentityUser : FullAuditedAggregateRoot, IUser, IHasEntityVer /// public virtual DateTimeOffset? LastPasswordChangeTime { get; protected set; } + /// + /// Gets or sets the last sign-in time for the user. + /// + public virtual DateTimeOffset? LastSignInTime { get; protected set; } + //TODO: Can we make collections readonly collection, which will provide encapsulation. But... can work for all ORMs? /// @@ -409,6 +414,11 @@ public class IdentityUser : FullAuditedAggregateRoot, IUser, IHasEntityVer LastPasswordChangeTime = lastPasswordChangeTime; } + public virtual void SetLastSignInTime(DateTimeOffset? lastSignInTime) + { + LastSignInTime = lastSignInTime; + } + [CanBeNull] public virtual IdentityUserPasskey FindPasskey(byte[] credentialId) { diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs index 5059c7c5ca..07be60da44 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs @@ -405,6 +405,9 @@ public partial class TokenController } ); + user.SetLastSignInTime(DateTimeOffset.UtcNow); + await UserManager.UpdateAsync(user); + return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); }