From 6f3b20ffe2ed7e05338e5a451b360282a2345d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:43:19 +0300 Subject: [PATCH] =?UTF-8?q?Kickuser=20=C3=A7al=C4=B1=C5=9Fmas=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Identity/PlatformIdentityAppService.cs | 27 ++++++++++++ .../Seeds/LanguagesData.json | 12 ++++++ .../Seeds/ListFormSeeder_Administration.cs | 12 +++++- .../PlatformHttpApiHostModule.cs | 1 + ui/src/services/UiEvalService.tsx | 42 +++++++++++++++++++ ui/src/services/identity.service.ts | 6 +++ ui/src/views/list/useListFormColumns.ts | 2 +- 7 files changed, 100 insertions(+), 2 deletions(-) diff --git a/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs b/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs index 3f704e3..c737b16 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs @@ -7,6 +7,7 @@ using Sozsoft.Platform.Entities; using Sozsoft.Platform.Extensions; using Sozsoft.Platform.Identity.Dto; using Microsoft.AspNetCore.Authorization; +using OpenIddict.Abstractions; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; using Volo.Abp.Guids; @@ -20,6 +21,8 @@ public class PlatformIdentityAppService : ApplicationService { public IIdentityUserAppService IdentityUserAppService { get; } private readonly IIdentityUserRepository identityUserRepository; + private readonly IIdentitySessionRepository identitySessionRepository; + private readonly IOpenIddictTokenManager openIddictTokenManager; public IRepository permissionRepository { get; } public IRepository branchRepository { get; } public IRepository branchUsersRepository { get; } @@ -31,6 +34,8 @@ public class PlatformIdentityAppService : ApplicationService public PlatformIdentityAppService( IIdentityUserAppService identityUserAppService, IIdentityUserRepository identityUserRepository, + IIdentitySessionRepository identitySessionRepository, + IOpenIddictTokenManager openIddictTokenManager, IRepository permissionRepository, IRepository branchRepository, IRepository branchUsersRepository, @@ -41,6 +46,8 @@ public class PlatformIdentityAppService : ApplicationService { this.IdentityUserAppService = identityUserAppService; this.identityUserRepository = identityUserRepository; + this.identitySessionRepository = identitySessionRepository; + this.openIddictTokenManager = openIddictTokenManager; this.workHourRepository = workHourRepository; this.permissionRepository = permissionRepository; this.branchRepository = branchRepository; @@ -231,4 +238,24 @@ public class PlatformIdentityAppService : ApplicationService user.Claims.Remove(claim); } + + /// + /// Kullanıcıyı anlık olarak oturumdan atar: + /// 1. AbpSessions kaydını siler → concurrent lisans slotunu serbest bırakır. + /// 2. OpenIddict tokenlarını revoke eder → access token anında geçersiz olur. + /// + public async Task KickUserAsync(Guid userId) + { + using (CurrentTenant.Change(CurrentTenant.Id)) + { + // 1. AbpSessions temizle + var sessions = await identitySessionRepository.GetListAsync(userId: userId); + foreach (var session in sessions) + await identitySessionRepository.DeleteAsync(session); + + // 2. OpenIddict tokenlarını revoke et + await foreach (var token in openIddictTokenManager.FindBySubjectAsync(userId.ToString())) + await openIddictTokenManager.TryRevokeAsync(token); + } + } } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 90bc637..8a086fb 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -1038,6 +1038,12 @@ "en": "Hangfire Refresh", "tr": "Hangfire Yenile" }, + { + "resourceName": "Platform", + "key": "App.Platform.KickUser", + "en": "Kick User", + "tr": "Kullanıcıyı At" + }, { "resourceName": "Platform", "key": "DeleteConfirmation", @@ -3060,6 +3066,12 @@ "en": "Redis cache is being cleared.", "tr": "Redis önbelleği temizleniyor." }, + { + "resourceName": "Platform", + "key": "App.KickUser.Message", + "en": "User is being kicked.", + "tr": "Kullanıcı atılıyor." + }, { "resourceName": "Platform", "key": "LoginEndDate", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs index ced3fce..d734959 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs @@ -2146,6 +2146,16 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep ColumnOptionJson = DefaultColumnOptionJson(), PermissionJson = DefaultPermissionJson(listFormName), PagerOptionJson = DefaultPagerOptionJson, + CommandColumnJson = JsonSerializer.Serialize(new CommandColumnDto[] { + new() { + ButtonPosition= UiCommandButtonPositionTypeEnum.CommandColumn, + Hint = "App.Platform.KickUser", + Text = "App.Platform.KickUser", + AuthName=listFormName, + OnClick = "UiEvalService.ApiKickUser(e.row.data.UserId, gridRef);", + IsVisible = true, + }, + }), } ); @@ -2172,7 +2182,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep SourceDbType = DbType.Guid, FieldName = "SessionId", CaptionName = "App.Listform.ListformField.SessionId", - Width = 250, + Width = 275, ListOrderNo = 2, Visible = true, IsActive = true, diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs index 58bea6f..2955ff0 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs @@ -95,6 +95,7 @@ public class PlatformHttpApiHostModule : AbpModule options.AddAudiences(PlatformConsts.AppName); options.UseLocalServer(); options.UseAspNetCore(); + options.EnableTokenEntryValidation(); }); }); diff --git a/ui/src/services/UiEvalService.tsx b/ui/src/services/UiEvalService.tsx index d790274..af28661 100644 --- a/ui/src/services/UiEvalService.tsx +++ b/ui/src/services/UiEvalService.tsx @@ -3,6 +3,7 @@ import { generateBackgroundWorkers } from '@/services/background-worker.service' import { getLocalization } from '@/services/localization.service' import { store } from '@/store' import { clearRedisCache } from './languageText.service' +import { kickUser } from './identity.service' export abstract class UiEvalService { static Init = () => { @@ -84,6 +85,47 @@ export abstract class UiEvalService { }, ) } + + static ApiKickUser = (userId: string, gridRef?: any) => { + // Get store state directly instead of using hook + const state = store.getState() + const { texts, config } = state.abpConfig + + // Create translate function similar to useLocalization hook + const translate = ( + localizationKey: string, + params?: Record, + defaultResourceName?: string, + ): string => { + if (!texts) { + return localizationKey + } + return getLocalization( + texts, + defaultResourceName ?? config?.localization?.defaultResourceName, + localizationKey, + params, + ) + } + + const asyncCall = async () => { + await kickUser(userId) + if (gridRef?.current) { + gridRef.current.instance().refresh() + } + } + + asyncCall() + + toast.push( + + {translate('::App.KickUser.Message')} + , + { + placement: 'top-end', + }, + ) + } } const _global = (window || global) as any diff --git a/ui/src/services/identity.service.ts b/ui/src/services/identity.service.ts index d626d45..1d069a2 100644 --- a/ui/src/services/identity.service.ts +++ b/ui/src/services/identity.service.ts @@ -93,3 +93,9 @@ export const deleteClaimUser = (id: string, userId?: string) => method: 'DELETE', url: `/api/app/platform-identity/${id}/claim-user/${userId}`, }) + +export const kickUser = (userId: string) => + apiService.fetchData({ + method: 'POST', + url: `/api/app/platform-identity/kick-user/${userId}`, + }) diff --git a/ui/src/views/list/useListFormColumns.ts b/ui/src/views/list/useListFormColumns.ts index ff5aec2..69dc86f 100644 --- a/ui/src/views/list/useListFormColumns.ts +++ b/ui/src/views/list/useListFormColumns.ts @@ -442,8 +442,8 @@ const useListFormColumns = ({ const item = { visible: visibleFunc, - hint: action.hint, icon: action.icon, + hint: translate('::' + action.hint), text: translate('::' + action.text), onClick: (e: any) => { if (typeof e.event?.preventDefault === 'function') {