From 70fb9ae4996e23eedca689d1729d00796a4fa923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:02:09 +0300 Subject: [PATCH] =?UTF-8?q?ConcurrentUserLimit=20tan=C4=B1mlamalas=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DynamicApi/CreateUpdateUserInput.cs | 1 + .../PlatformSignInResultExtensions.cs | 4 ++ .../ListForms/ListFormDynamicApiAppService.cs | 22 +++++--- .../Seeds/LanguagesData.json | 8 ++- .../PlatformConsts.cs | 3 ++ .../PlatformModuleExtensionConfigurator.cs | 1 + .../Extensions/AbpTenantExtensions.cs | 9 ++++ .../Identity/PlatformSignInResult.cs | 3 ++ .../PlatformEfCoreEntityExtensionMappings.cs | 9 ++++ ....cs => 20260427070853_Initial.Designer.cs} | 7 ++- ...9_Initial.cs => 20260427070853_Initial.cs} | 1 + .../PlatformDbContextModelSnapshot.cs | 5 ++ .../Identity/PlatformEventIds.cs | 3 ++ .../Identity/PlatformLoginResult.cs | 5 ++ .../Identity/PlatformLoginResultType.cs | 3 +- .../Identity/PlatformSignInManager.cs | 52 ++++++++++++++++++- ui/src/constants/login.result.enum.ts | 1 + 17 files changed, 126 insertions(+), 11 deletions(-) rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260426180109_Initial.Designer.cs => 20260427070853_Initial.Designer.cs} (99%) rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260426180109_Initial.cs => 20260427070853_Initial.cs} (99%) diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/DynamicApi/CreateUpdateUserInput.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/DynamicApi/CreateUpdateUserInput.cs index 86b5366..a582e1e 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/DynamicApi/CreateUpdateUserInput.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/DynamicApi/CreateUpdateUserInput.cs @@ -10,6 +10,7 @@ public class CreateUpdateUserInput public string Email { get; set; } public string PhoneNumber { get; set; } public string Password { get; set; } + public string WorkHour { get; set; } public bool? IsActive { get; set; } public bool? LockoutEnabled { get; set; } public string[] RoleNames { get; set; } diff --git a/api/src/Sozsoft.Platform.Application/Identity/PlatformSignInResultExtensions.cs b/api/src/Sozsoft.Platform.Application/Identity/PlatformSignInResultExtensions.cs index 3362bb2..8d60949 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/PlatformSignInResultExtensions.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/PlatformSignInResultExtensions.cs @@ -58,6 +58,10 @@ public static class PlatformSignInResultExtensions { return PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_WorkHour; } + if (resultP.IsNotAllowed_ConcurrentUserLimit) + { + return PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit; + } } /// Added --> diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs index 2c62db0..7ebc620 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs @@ -7,6 +7,7 @@ using Volo.Abp; using Volo.Abp.Domain.Entities; using Volo.Abp.Identity; using Volo.Abp.TenantManagement; +using Volo.Abp.Data; namespace Sozsoft.Platform.ListForms.DynamicApi; @@ -33,7 +34,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic [Authorize(IdentityPermissions.Users.Create)] public async Task PostUserInsertAsync(DynamicApiBaseInput input) { - await identityUserAppService.CreateAsync(new IdentityUserCreateDto + var user = new IdentityUserCreateDto { UserName = input.Data.Email, Name = input.Data.Name, @@ -43,8 +44,11 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic IsActive = input.Data.IsActive ?? true, LockoutEnabled = true, Password = input.Data.Password, - RoleNames = [] //input.Data.RoleNames, - }); + RoleNames = [], //input.Data.RoleNames, + }; + user.SetProperty(PlatformConsts.AbpIdentity.User.WorkHour, input.Data.WorkHour); + + await identityUserAppService.CreateAsync(user); } [Authorize(IdentityPermissions.Users.Update)] @@ -54,7 +58,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic { throw new UserFriendlyException(L["RecordNotFound"]); } - var id = Guid.Parse(input.Keys[0].ToString()); + var id = Guid.Parse(input.Keys[0]!.ToString()!); var entity = await identityUserAppService.GetAsync(id) ?? throw new EntityNotFoundException(L["RecordNotFound"]); var user = new IdentityUserUpdateDto { @@ -71,6 +75,10 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic { user.Password = input.Data.Password; } + if (input.Data.WorkHour != null) + { + user.SetProperty(PlatformConsts.AbpIdentity.User.WorkHour, input.Data.WorkHour); + } await identityUserAppService.UpdateAsync(id, user); } @@ -94,7 +102,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic { throw new UserFriendlyException(L["RecordNotFound"]); } - var id = Guid.Parse(input.Keys[0].ToString()); + var id = Guid.Parse(input.Keys[0]!.ToString()!); var entity = await identityRoleAppService.GetAsync(id) ?? throw new EntityNotFoundException(L["RecordNotFound"]); var role = new IdentityRoleUpdateDto { @@ -142,7 +150,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic throw new UserFriendlyException(L["RecordNotFound"]); } - var id = Guid.Parse(input.Keys[0].ToString()); + var id = Guid.Parse(input.Keys[0]!.ToString()!); var entity = await tenantRepository.GetAsync(id) ?? throw new EntityNotFoundException(L["RecordNotFound"]); @@ -176,7 +184,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic { throw new UserFriendlyException(L["RecordNotFound"]); } - var id = Guid.Parse(input.Keys[0].ToString()); + var id = Guid.Parse(input.Keys[0]!.ToString()!); await tenantRepository.DeleteAsync(id); } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 40debd4..3fe3c92 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -1,4 +1,4 @@ -{ +{ "Languages": [ { "cultureName": "ar", @@ -3113,6 +3113,12 @@ "key": "Abp.Identity.LoginNotAllowed_WorkHour", "en": "You cannot sign in outside of the allowed work hours.", "tr": "İzin verilen iş saatleri dışında giriş yapamazsınız." + }, + { + "resourceName": "Platform", + "key": "Abp.Identity.ConcurrentUserLimitError", + "en": "The maximum number of simultaneous users has been reached. Please try again later.", + "tr": "Eş zamanlı kullanıcı limiti dolmuştur. Lütfen daha sonra tekrar deneyiniz." }, { "resourceName": "Platform", diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs index 126d5ed..aea37ec 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs @@ -68,6 +68,7 @@ public static class PlatformConsts public const string Email = "Email"; public const string Website = "Website"; public const string MenuGroup = "MenuGroup"; + public const string MaxConcurrentUsers = "MaxConcurrentUsers"; } public static class AbpIdentity @@ -107,6 +108,7 @@ public static class PlatformConsts public const string LoginEndDateError = GroupName + ".LoginEndDateError"; public const string TenantIsPassive = GroupName + ".TenantIsPassive"; public const string LoginNotAllowed_WorkHour = GroupName + ".LoginNotAllowed_WorkHour"; + public const string ConcurrentUserLimitError = GroupName + ".ConcurrentUserLimitError"; public const string CaptchaWrongCode = GroupName + ".CaptchaWrongCode"; public const string TwoFactorWrongCode = GroupName + ".TwoFactorWrongCode"; public const string SignOut = GroupName + ".SignOut"; @@ -450,6 +452,7 @@ public static class PlatformConsts public static string LoginNotAllowed_TenantIsPassive { get; set; } = "UserCannotSignInTenantIsPassive"; public static string LoginNotAllowed_TenantNotFound { get; set; } = "UserCannotSignInTenantNotFound"; public static string LoginNotAllowed_WorkHour { get; set; } = "UserCannotSignInWorkHour"; + public static string LoginNotAllowed_ConcurrentUserLimit { get; set; } = "UserCannotSignInConcurrentUserLimit"; } public static class GridOptions diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs index 2fe858f..fc13db0 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs @@ -93,6 +93,7 @@ public static class PlatformModuleExtensionConfigurator tenantConfig.ConfigureTenant(entity => { entity.AddOrUpdateProperty("IsActive"); + entity.AddOrUpdateProperty("MaxConcurrentUsers"); }); }); diff --git a/api/src/Sozsoft.Platform.Domain/Extensions/AbpTenantExtensions.cs b/api/src/Sozsoft.Platform.Domain/Extensions/AbpTenantExtensions.cs index 7ab3f15..0aa32ab 100644 --- a/api/src/Sozsoft.Platform.Domain/Extensions/AbpTenantExtensions.cs +++ b/api/src/Sozsoft.Platform.Domain/Extensions/AbpTenantExtensions.cs @@ -165,5 +165,14 @@ public static class AbpTenantExtensions { return tenant.GetProperty(PlatformConsts.Tenants.MenuGroup); } + + public static void SetMaxConcurrentUsers(this Tenant tenant, int? maxConcurrentUsers) + { + tenant.SetProperty(PlatformConsts.Tenants.MaxConcurrentUsers, maxConcurrentUsers.HasValue ? (object)maxConcurrentUsers.Value : null); + } + public static int? GetMaxConcurrentUsers(this Tenant tenant) + { + return tenant.GetProperty(PlatformConsts.Tenants.MaxConcurrentUsers); + } } diff --git a/api/src/Sozsoft.Platform.Domain/Identity/PlatformSignInResult.cs b/api/src/Sozsoft.Platform.Domain/Identity/PlatformSignInResult.cs index df13679..0801a36 100644 --- a/api/src/Sozsoft.Platform.Domain/Identity/PlatformSignInResult.cs +++ b/api/src/Sozsoft.Platform.Domain/Identity/PlatformSignInResult.cs @@ -30,6 +30,8 @@ public class PlatformSignInResult : SignInResult public bool IsNotAllowed_WorkHour { get; set; } + public bool IsNotAllowed_ConcurrentUserLimit { get; set; } + public override string ToString() { return @@ -40,6 +42,7 @@ public class PlatformSignInResult : SignInResult ShouldChangePasswordPeriodic ? "ShouldChangePasswordPeriodic" : IsNotAllowed_TenantIsPassive ? "NotAllowed_TenantIsPassive" : IsNotAllowed_WorkHour ? "NotAllowed_WorkHour" : + IsNotAllowed_ConcurrentUserLimit ? "NotAllowed_ConcurrentUserLimit" : base.ToString(); } } diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs index ff41dc5..2babdb7 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs @@ -217,6 +217,15 @@ public static class PlatformEfCoreEntityExtensionMappings } ); + ObjectExtensionManager.Instance + .MapEfCoreProperty( + PlatformConsts.Tenants.MaxConcurrentUsers, + (entityBuilder, propertyBuilder) => + { + propertyBuilder.HasDefaultValue(0); + } + ); + ObjectExtensionManager.Instance .MapEfCoreProperty( PlatformConsts.Permissions.MenuGroup, diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260426180109_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260427070853_Initial.Designer.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260426180109_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260427070853_Initial.Designer.cs index 5d3cc36..69b41f6 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260426180109_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260427070853_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260426180109_Initial")] + [Migration("20260427070853_Initial")] partial class Initial { /// @@ -6164,6 +6164,11 @@ namespace Sozsoft.Platform.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("MaxConcurrentUsers") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + b.Property("MenuGroup") .HasMaxLength(64) .HasColumnType("nvarchar(64)"); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260426180109_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260427070853_Initial.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260426180109_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260427070853_Initial.cs index a34be3f..1b6d4c2 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260426180109_Initial.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260427070853_Initial.cs @@ -350,6 +350,7 @@ namespace Sozsoft.Platform.Migrations FaxNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), Founder = table.Column(type: "nvarchar(max)", nullable: true), IsActive = table.Column(type: "bit", nullable: false, defaultValue: true), + MaxConcurrentUsers = table.Column(type: "int", nullable: true, defaultValue: 0), MenuGroup = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), MobileNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), OrganizationName = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index 9a4635f..26c4932 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -6161,6 +6161,11 @@ namespace Sozsoft.Platform.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("MaxConcurrentUsers") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + b.Property("MenuGroup") .HasMaxLength(64) .HasColumnType("nvarchar(64)"); diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformEventIds.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformEventIds.cs index ef20d89..24784b5 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformEventIds.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformEventIds.cs @@ -24,6 +24,9 @@ public static class PlatformEventIds public static EventId UserCannotSignInWorkHour = new(18, PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_WorkHour); + + public static EventId UserCannotSignInConcurrentUserLimit = + new(19, PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit); } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResult.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResult.cs index aaa1920..d6f8aba 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResult.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResult.cs @@ -64,6 +64,11 @@ public class PlatformLoginResult : AbpLoginResult PResult = PlatformLoginResultType.NotAllowedWorkHour; Description = L[PlatformConsts.AbpIdentity.User.LoginNotAllowed_WorkHour]; } + else if (resultP.IsNotAllowed_ConcurrentUserLimit) + { + PResult = PlatformLoginResultType.ConcurrentUserLimit; + Description = L[PlatformConsts.AbpIdentity.User.ConcurrentUserLimitError]; + } } else { diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResultType.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResultType.cs index 61e5138..08ba3a7 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResultType.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResultType.cs @@ -15,6 +15,7 @@ public enum PlatformLoginResultType : byte LoginEndDateDue, ShowCaptcha, TenantIsPassive, - NotAllowedWorkHour + NotAllowedWorkHour, + ConcurrentUserLimit } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSignInManager.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSignInManager.cs index b42531f..2102539 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSignInManager.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSignInManager.cs @@ -18,6 +18,7 @@ using Volo.Abp.TenantManagement; using Volo.Abp.Timing; using IdentityUser = Volo.Abp.Identity.IdentityUser; using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; +using Microsoft.EntityFrameworkCore; namespace Sozsoft.Platform.Identity; @@ -33,6 +34,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager private readonly IRepository repositoryWorkHour; private readonly ITenantRepository tenantRepository; private readonly IdentityUserManager userManager; + private readonly IIdentitySessionRepository identitySessionRepository; public PlatformSignInManager( IdentityUserManager userManager, @@ -47,7 +49,8 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager IClock clock, IRepository repositoryIp, IRepository repositoryWorkHour, - ITenantRepository tenantRepository + ITenantRepository tenantRepository, + IIdentitySessionRepository identitySessionRepository ) : base( userManager, contextAccessor, @@ -64,6 +67,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager this.repositoryWorkHour = repositoryWorkHour; this.tenantRepository = tenantRepository; this.userManager = userManager; + this.identitySessionRepository = identitySessionRepository; } public async Task PreSignInCheckAsync(IdentityUser user) @@ -98,6 +102,10 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager { return new PlatformSignInResult() { IsNotAllowed_WorkHour = true }; } + if (!await CanSignInConcurrentLimitAsync(user)) + { + return new PlatformSignInResult() { IsNotAllowed_ConcurrentUserLimit = true }; + } } else { @@ -256,5 +264,47 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager return true; } + /// + /// Prevents login when the tenant's concurrent user limit is reached. + /// Counts distinct active users (by UserId) in AbpSessions for the tenant. + /// A user who already has an active session is not counted as a new concurrent user. + /// + private async Task CanSignInConcurrentLimitAsync(IdentityUser user) + { + if (!user.TenantId.HasValue) + { + return true; + } + + var tenant = await tenantRepository.FindAsync(user.TenantId.Value); + if (tenant == null) + { + return true; + } + + var maxConcurrentUsers = tenant.GetMaxConcurrentUsers(); + if (!maxConcurrentUsers.HasValue || maxConcurrentUsers.Value <= 0) + { + return true; + } + + var sessions = await identitySessionRepository.GetListAsync(); + var activeUserCount = sessions + .Where(s => s.UserId != user.Id) + .Select(s => s.UserId) + .Distinct() + .Count(); + + if (activeUserCount >= maxConcurrentUsers.Value) + { + Logger.LogWarning(PlatformEventIds.UserCannotSignInConcurrentUserLimit, + "Tenant {TenantId} concurrent user limit of {Limit} reached. Active users: {ActiveCount}.", + user.TenantId, maxConcurrentUsers.Value, activeUserCount); + return false; + } + + return true; + } + } diff --git a/ui/src/constants/login.result.enum.ts b/ui/src/constants/login.result.enum.ts index 3026fbf..be14719 100644 --- a/ui/src/constants/login.result.enum.ts +++ b/ui/src/constants/login.result.enum.ts @@ -12,6 +12,7 @@ const PlatformLoginResultType = { NotAllowedIp: 19, // IpRestriction tablosu, Mesaj: Identity:IpRestrictionError LoginEndDateDue: 20, // LoginEndDate:>Today, Buton: ShowExtendMyLoginButton, Mesaj: Identity:LoginEndDateError, >LoginEndDate ShowCaptcha: 21, // AccessFailedCount>AccountCaptchaMaxFailedAccessAttempts, Sayfa: Show Captcha + ConcurrentUserLimit: 22, // MaxConcurrentUsers limiti dolduğunda, Mesaj: Identity:ConcurrentUserLimitError } export default PlatformLoginResultType