diff --git a/api/modules/Sozsoft.Languages/common.props b/api/modules/Sozsoft.Languages/common.props index a3623fb..58e8628 100644 --- a/api/modules/Sozsoft.Languages/common.props +++ b/api/modules/Sozsoft.Languages/common.props @@ -7,7 +7,7 @@ - + All runtime; build; native; contentfiles; analyzers diff --git a/api/modules/Sozsoft.Notifications/common.props b/api/modules/Sozsoft.Notifications/common.props index a3623fb..58e8628 100644 --- a/api/modules/Sozsoft.Notifications/common.props +++ b/api/modules/Sozsoft.Notifications/common.props @@ -7,7 +7,7 @@ - + All runtime; build; native; contentfiles; analyzers diff --git a/api/modules/Sozsoft.Settings/common.props b/api/modules/Sozsoft.Settings/common.props index a3623fb..58e8628 100644 --- a/api/modules/Sozsoft.Settings/common.props +++ b/api/modules/Sozsoft.Settings/common.props @@ -7,7 +7,7 @@ - + All runtime; build; native; contentfiles; analyzers diff --git a/api/modules/Sozsoft.SqlQueryManager/common.props b/api/modules/Sozsoft.SqlQueryManager/common.props index a3623fb..58e8628 100644 --- a/api/modules/Sozsoft.SqlQueryManager/common.props +++ b/api/modules/Sozsoft.SqlQueryManager/common.props @@ -7,7 +7,7 @@ - + All runtime; build; native; contentfiles; analyzers diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs index 07b8060..216dd21 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs @@ -3,11 +3,15 @@ using System.Collections.Generic; using System.Threading.Tasks; using Sozsoft.Platform.Extensions; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; using Volo.Abp; using Volo.Abp.Domain.Entities; using Volo.Abp.Identity; using Volo.Abp.TenantManagement; using Volo.Abp.Data; +using IdentityUser = Volo.Abp.Identity.IdentityUser; +using Sozsoft.Platform.Data.Seeds; namespace Sozsoft.Platform.ListForms.DynamicApi; @@ -18,79 +22,128 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic private readonly ITenantManager tenantManager; private readonly IIdentityUserAppService identityUserAppService; private readonly IIdentityRoleAppService identityRoleAppService; + private readonly IdentityUserManager userManager; + private readonly IOptions identityOptions; public ListFormDynamicApiAppService( ITenantRepository tenantRepository, ITenantManager tenantManager, IIdentityUserAppService identityUserAppService, - IIdentityRoleAppService identityRoleAppService) + IIdentityRoleAppService identityRoleAppService, + IdentityUserManager userManager, + IOptions identityOptions) { this.tenantRepository = tenantRepository; this.tenantManager = tenantManager; this.identityUserAppService = identityUserAppService; this.identityRoleAppService = identityRoleAppService; + this.userManager = userManager; + this.identityOptions = identityOptions; } [Authorize(IdentityPermissions.Users.Create)] public async Task PostUserInsertAsync(DynamicApiBaseInput input) { - var user = new IdentityUserCreateDto - { - UserName = input.Data.Email, - Name = input.Data.Name, - Surname = input.Data.Surname, - Email = input.Data.Email, - PhoneNumber = input.Data.PhoneNumber, - IsActive = input.Data.IsActive ?? true, - LockoutEnabled = true, - Password = input.Data.Password, - RoleNames = [], //input.Data.RoleNames, - }; - user.SetProperty(PlatformConsts.AbpIdentity.User.WorkHour, input.Data.WorkHour); - user.SetProperty(PlatformConsts.AbpIdentity.User.DepartmentId, input.Data.DepartmentId); - user.SetProperty(PlatformConsts.AbpIdentity.User.JobPositionId, input.Data.JobPositionId); + await identityOptions.SetAsync(); - await identityUserAppService.CreateAsync(user); + var verifySetting = await SettingProvider.GetOrNullAsync(PlatformConsts.AbpIdentity.Profile.General.RequireVerifiedAccount); + var verify = !string.Equals(verifySetting, "true", StringComparison.OrdinalIgnoreCase); + + var phoneSetting = await SettingProvider.GetOrNullAsync(PlatformConsts.AbpIdentity.SignIn.RequireConfirmedPhoneNumber); + var phoneVerified = !string.Equals(phoneSetting, "true", StringComparison.OrdinalIgnoreCase); + + var user = new IdentityUser( + GuidGenerator.Create(), + input.Data.Email, + input.Data.Email, + CurrentTenant.Id) + { + Name = input.Data.Name, + Surname = input.Data.Surname + }; + + user.SetIsActive(input.Data.IsActive ?? true); + user.SetPhoneNumber(input.Data.PhoneNumber, phoneVerified); + user.SetWorkHour(input.Data.WorkHour); + user.SetDepartmentId(ParseGuid(input.Data.DepartmentId)); + user.SetJobPositionId(ParseGuid(input.Data.JobPositionId)); + user.SetIsVerified(verify); + + (await userManager.CreateAsync(user, input.Data.Password)).CheckErrors(); + await userManager.SetLockoutEnabledAsync(user, true); + } + + private static Guid ParseGuid(string value) + { + return Guid.TryParse(value, out var id) ? id : Guid.Empty; } [Authorize(IdentityPermissions.Users.Update)] public async Task PostUserUpdateAsync(DynamicApiBaseInput input) { + await identityOptions.SetAsync(); + if (input.Keys.IsNullOrEmpty()) { throw new UserFriendlyException(L["RecordNotFound"]); } var id = Guid.Parse(input.Keys[0]!.ToString()!); - var entity = await identityUserAppService.GetAsync(id) ?? throw new EntityNotFoundException(L["RecordNotFound"]); - var user = new IdentityUserUpdateDto + var user = await userManager.GetByIdAsync(id); + if (user == null) { - UserName = input.Data.Email ?? entity.Email, - Email = input.Data.Email ?? entity.Email, - Name = input.Data.Name ?? entity.Name, - Surname = input.Data.Surname ?? entity.Surname, - PhoneNumber = input.Data.PhoneNumber ?? entity.PhoneNumber, - IsActive = input.Data.IsActive ?? entity.IsActive, - LockoutEnabled = input.Data.LockoutEnabled ?? entity.LockoutEnabled, - //RoleNames = input.Data.RoleNames ?? identity.RoleNames, - }; - if (!input.Data.Password.IsNullOrWhiteSpace()) - { - user.Password = input.Data.Password; - } - if (input.Data.WorkHour != null) - { - user.SetProperty(PlatformConsts.AbpIdentity.User.WorkHour, input.Data.WorkHour); - } - if (input.Data.DepartmentId != null) - { - user.SetProperty(PlatformConsts.AbpIdentity.User.DepartmentId, input.Data.DepartmentId); - } - if (input.Data.JobPositionId != null) - { - user.SetProperty(PlatformConsts.AbpIdentity.User.JobPositionId, input.Data.JobPositionId); + throw new EntityNotFoundException(L["RecordNotFound"]); } - await identityUserAppService.UpdateAsync(id, user); + if (!input.Data.Email.IsNullOrWhiteSpace() && input.Data.Email != user.Email) + { + (await userManager.SetUserNameAsync(user, input.Data.Email)).CheckErrors(); + (await userManager.SetEmailAsync(user, input.Data.Email)).CheckErrors(); + } + + user.Name = input.Data.Name ?? user.Name; + user.Surname = input.Data.Surname ?? user.Surname; + + if (input.Data.PhoneNumber != null) + { + user.SetPhoneNumber(input.Data.PhoneNumber, user.PhoneNumberConfirmed); + } + + if (input.Data.IsActive.HasValue) + { + user.SetIsActive(input.Data.IsActive.Value); + } + + if (input.Data.LockoutEnabled.HasValue) + { + (await userManager.SetLockoutEnabledAsync(user, input.Data.LockoutEnabled.Value)).CheckErrors(); + } + + if (!input.Data.Password.IsNullOrWhiteSpace()) + { + if (await userManager.HasPasswordAsync(user)) + { + (await userManager.RemovePasswordAsync(user)).CheckErrors(); + } + + (await userManager.AddPasswordAsync(user, input.Data.Password)).CheckErrors(); + } + + if (input.Data.WorkHour != null) + { + user.SetWorkHour(input.Data.WorkHour); + } + + if (input.Data.DepartmentId != null) + { + user.SetDepartmentId(ParseGuid(input.Data.DepartmentId)); + } + + if (input.Data.JobPositionId != null) + { + user.SetJobPositionId(ParseGuid(input.Data.JobPositionId)); + } + + (await userManager.UpdateAsync(user)).CheckErrors(); } //RoleAppService diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json index 76e10c7..9c0ab1e 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json @@ -12,9 +12,9 @@ "code": "Abp.Localization.DefaultLanguage", "nameKey": "Abp.Localization.DefaultLanguage", "descriptionKey": "Abp.Localization.DefaultLanguage.Description", - "defaultValue": "tr", + "defaultValue": "en", "isVisibleToClients": false, - "providers": "T|G|D", + "providers": "G|D", "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.SiteManagement", @@ -48,7 +48,7 @@ "descriptionKey": "Abp.Timing.TimeZone.Description", "defaultValue": "Turkey Standard Time", "isVisibleToClients": false, - "providers": "T|G|D", + "providers": "G|D", "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.SiteManagement", @@ -642,7 +642,7 @@ "code": "Abp.Account.IsSelfRegistrationEnabled", "nameKey": "Abp.Account.IsSelfRegistrationEnabled", "descriptionKey": "Abp.Account.IsSelfRegistrationEnabled.Description", - "defaultValue": "True", + "defaultValue": "False", "isVisibleToClients": false, "providers": "G|D", "isInherited": false, @@ -754,8 +754,8 @@ "code": "Abp.Identity.Profile.General.RequireVerifiedAccount", "nameKey": "Abp.Identity.Profile.General.RequireVerifiedAccount", "descriptionKey": "Abp.Identity.Profile.General.RequireVerifiedAccount.Description", - "defaultValue": "True", - "isVisibleToClients": false, + "defaultValue": "False", + "isVisibleToClients": true, "providers": "T|G|D", "isInherited": false, "isEncrypted": false, @@ -995,7 +995,7 @@ "nameKey": "Abp.Identity.User.IsUserNameUpdateEnabled", "descriptionKey": "Abp.Identity.User.IsUserNameUpdateEnabled.Description", "defaultValue": "True", - "isVisibleToClients": false, + "isVisibleToClients": true, "providers": "G|D", "isInherited": false, "isEncrypted": false, @@ -1011,7 +1011,7 @@ "nameKey": "Abp.Identity.User.IsEmailUpdateEnabled", "descriptionKey": "Abp.Identity.User.IsEmailUpdateEnabled.Description", "defaultValue": "True", - "isVisibleToClients": false, + "isVisibleToClients": true, "providers": "G|D", "isInherited": false, "isEncrypted": false, diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs index c8732c0..94e783f 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs @@ -797,7 +797,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep PermissionJson = DefaultPermissionJson(PlatformConsts.IdentityPermissions.Users.Create, listFormName, PlatformConsts.IdentityPermissions.Users.Update, PlatformConsts.IdentityPermissions.Users.Delete, PlatformConsts.IdentityPermissions.Users.Export, PlatformConsts.IdentityPermissions.Users.Import, PlatformConsts.IdentityPermissions.Users.Note), DeleteCommand = $"UPDATE \"AbpUsers\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id", DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(), - EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 600, true, true, true, true, false), + EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 730, true, true, true, true, false), EditingFormJson = JsonSerializer.Serialize(new List() { new () { Order=1,ColCount=1,ColSpan=1,ItemType="group",Items=[ new EditingFormItemDto { Order=1, DataField="Email", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox }, @@ -980,6 +980,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep IsActive = true, AllowSearch = true, + ValidationRuleJson = DefaultValidationRuleRequiredJson, EditorOptions = EditorOptionValues.PhoneEditorOptions, ColumnCustomizationJson = DefaultColumnCustomizationJson, PermissionJson = DefaultFieldPermissionJson(PlatformConsts.IdentityPermissions.Users.Create, PlatformConsts.IdentityPermissions.Users.Default, PlatformConsts.IdentityPermissions.Users.Update, true, true, false), diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs index da4282b..9f91613 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs @@ -93,6 +93,13 @@ public static class PlatformConsts { public const string GroupName = $"{Prefix.Abp}.Identity"; + public static class SignIn + { + public const string Default = GroupName + ".SignIn"; + public const string RequireConfirmedEmail = Default + ".RequireConfirmedEmail"; + public const string RequireConfirmedPhoneNumber = Default + ".RequireConfirmedPhoneNumber"; + } + public static class Profile { public const string Default = GroupName + ".Profile"; diff --git a/ui/src/views/admin/profile/components/General.tsx b/ui/src/views/admin/profile/components/General.tsx index a8e276b..32ee524 100644 --- a/ui/src/views/admin/profile/components/General.tsx +++ b/ui/src/views/admin/profile/components/General.tsx @@ -34,6 +34,7 @@ import { ProfileDto, UpdateProfileDto } from '@/proxy/account/models' import { getProfile, updateProfile } from '@/services/account.service' import { CountryDto, getCountry } from '@/services/home.service' import { SelectBoxOption } from '@/types/shared' +import { useSetting } from '@/utils/hooks/useSetting' const schema = Yup.object().shape({ name: Yup.string().min(3).max(50).required(), @@ -50,6 +51,9 @@ const General = () => { const auth = useStoreState((state) => state.auth) const { setUser } = useStoreActions((actions) => actions.auth.user) + const { setting } = useSetting() + const isEmailUpdateEnabled = setting('Abp.Identity.User.IsEmailUpdateEnabled') + const { translate } = useLocalization() const cropperRef = useRef(null) const previewRef = useRef(null) @@ -243,7 +247,7 @@ const General = () => { } value={profileData?.email} > diff --git a/ui/src/views/admin/user-management/Details.tsx b/ui/src/views/admin/user-management/Details.tsx index 9aaa2a8..511902c 100644 --- a/ui/src/views/admin/user-management/Details.tsx +++ b/ui/src/views/admin/user-management/Details.tsx @@ -81,6 +81,7 @@ import { AdaptableCard, ConfirmDialog, Container } from '@/components/shared' import { AssignedClaimViewModel, UserInfoViewModel } from '@/proxy/admin/models' import { APP_NAME, AVATAR_URL } from '@/constants/app.constant' import { useStoreActions, useStoreState } from '@/store' +import { useSetting } from '@/utils/hooks/useSetting' export interface ClaimTypeDto { claimType: string @@ -100,6 +101,8 @@ function UserDetails() { const previewRef = useRef(null) const auth = useStoreState((state) => state.auth) const { setUser } = useStoreActions((actions) => actions.auth.user) + const { setting } = useSetting() + const isEmailUpdateEnabled = setting('Abp.Identity.User.IsEmailUpdateEnabled') const getUser = async (syncAvatar = true) => { const { data } = await getUserDetail(userId || '') @@ -296,7 +299,7 @@ function UserDetails() { } @@ -515,13 +518,7 @@ function UserDetails() { ) : ( )}
diff --git a/ui/src/views/auth/Login.tsx b/ui/src/views/auth/Login.tsx index f1a7ac0..391f2c8 100644 --- a/ui/src/views/auth/Login.tsx +++ b/ui/src/views/auth/Login.tsx @@ -22,6 +22,7 @@ import { useNavigate } from 'react-router-dom' import * as Yup from 'yup' import { Helmet } from 'react-helmet' import { APP_NAME } from '@/constants/app.constant' +import { useSetting } from '@/utils/hooks/useSetting' type SignInFormSchema = { userName: string @@ -62,6 +63,9 @@ const Login = () => { }, 100) } + const { setting } = useSetting() + const isSelfRegistrationEnabled = setting('Abp.Account.IsSelfRegistrationEnabled') + const { signIn } = useAuth() const { translate } = useLocalization() @@ -283,12 +287,15 @@ const Login = () => { - {/*
- {translate('::Abp.Account.SignUp.Message')} - - {translate('::Abp.Account.Register')} - -
*/} + + {isSelfRegistrationEnabled === 'true' && ( +
+ {translate('::Abp.Account.SignUp.Message')} + + {translate('::Abp.Account.Register')} + +
+ )} )}