Kickuser çalışması

This commit is contained in:
Sedat ÖZTÜRK 2026-04-28 15:43:19 +03:00
parent 8234b1de2a
commit 6f3b20ffe2
7 changed files with 100 additions and 2 deletions

View file

@ -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<PermissionDefinitionRecord, Guid> permissionRepository { get; }
public IRepository<Branch, Guid> branchRepository { get; }
public IRepository<BranchUsers, Guid> branchUsersRepository { get; }
@ -31,6 +34,8 @@ public class PlatformIdentityAppService : ApplicationService
public PlatformIdentityAppService(
IIdentityUserAppService identityUserAppService,
IIdentityUserRepository identityUserRepository,
IIdentitySessionRepository identitySessionRepository,
IOpenIddictTokenManager openIddictTokenManager,
IRepository<PermissionDefinitionRecord, Guid> permissionRepository,
IRepository<Branch, Guid> branchRepository,
IRepository<BranchUsers, Guid> 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);
}
/// <summary>
/// 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.
/// </summary>
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);
}
}
}

View file

@ -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",

View file

@ -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,

View file

@ -95,6 +95,7 @@ public class PlatformHttpApiHostModule : AbpModule
options.AddAudiences(PlatformConsts.AppName);
options.UseLocalServer();
options.UseAspNetCore();
options.EnableTokenEntryValidation();
});
});

View file

@ -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<string, string | number>,
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(
<Notification type="success" duration={2000}>
{translate('::App.KickUser.Message')}
</Notification>,
{
placement: 'top-end',
},
)
}
}
const _global = (window || global) as any

View file

@ -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}`,
})

View file

@ -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') {