Browse Source

Merge pull request #134 from colinin/3.3

More reasonable SMS verification code
pull/177/head
cKey 5 years ago
committed by GitHub
parent
commit
37b8a0df88
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 45
      aspnet-core/LINGYUN.MicroService.IdentityServer.sln
  2. 30
      aspnet-core/LINGYUN.MicroService.IdentityServerAdmin.sln
  3. 10
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN.Abp.Account.Application.Contracts.csproj
  4. 22
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/AbpAccountApplicationContractsModule.cs
  5. 16
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/PhoneRegisterDto.cs
  6. 12
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/PhoneResetPasswordDto.cs
  7. 6
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/SendPhoneRegisterCodeDto.cs
  8. 15
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/SendPhoneResetPasswordCodeDto.cs
  9. 3
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/SendPhoneSigninCodeDto.cs
  10. 11
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/WeChatRegisterDto.cs
  11. 44
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/IAccountAppService.cs
  12. 15
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json
  13. 15
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json
  14. 2
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN.Abp.Account.Application.csproj
  15. 5
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AbpAccountApplicationModule.cs
  16. 402
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs
  17. 12
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN.Abp.Account.Domain.Shared.csproj
  18. 63
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/AccountSettingDefinitionProvider.cs
  19. 23
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/AccountSettingNames.cs
  20. 9
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/Localization/AccountResource.cs
  21. 20
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/Localization/Resources/en.json
  22. 20
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json
  23. 2
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain/LINGYUN.Abp.Account.Domain.csproj
  24. 50
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain/Microsoft/AspNetCore/Identity/PhoneNumberUserValidator.cs
  25. 2
      aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN.Abp.Account.HttpApi.csproj
  26. 20
      aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/AbpAccountHttpApiModule.cs
  27. 37
      aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/AccountController.cs
  28. 24
      aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN.Abp.AspNetCore.Mvc.Validation.csproj
  29. 31
      aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN/Abp/AspNetCore/Mvc/Validation/AbpAspNetCoreMvcValidationModule.cs
  30. 563
      aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN/Abp/AspNetCore/Mvc/Validation/AbpDataAnnotationsMetadataProvider.cs
  31. 69
      aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN/Abp/AspNetCore/Mvc/Validation/AbpLocalizerModelMetadataProvider.cs
  32. 69
      aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN/Abp/AspNetCore/Mvc/Validation/DataAnnotationAutoLocalizationMetadataDetailsProvider.cs
  33. 7
      aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN/Abp/AspNetCore/Mvc/Validation/Localization/MissingFields/en.json
  34. 7
      aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN/Abp/AspNetCore/Mvc/Validation/Localization/MissingFields/zh-Hans.json
  35. 7
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/ChangeEmailAddressDto.cs
  36. 7
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/ChangePhoneNumberDto.cs
  37. 2
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/ChangeTwoFactorEnabledDto.cs
  38. 18
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/SendChangeEmailAddressCodeDto.cs
  39. 18
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/SendChangePhoneNumberCodeDto.cs
  40. 2
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/IIdentityUserAppService.cs
  41. 19
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/IMyProfileAppService.cs
  42. 2
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application/LINGYUN/Abp/Identity/IdentityUserAppService.cs
  43. 69
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application/LINGYUN/Abp/Identity/MyProfileAppService.cs
  44. 20
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/IUserSecurityCodeSender.cs
  45. 8
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/IdentityErrorCodes.cs
  46. 20
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/en.json
  47. 19
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/zh-Hans.json
  48. 121
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Security/DefaultTotpService.cs
  49. 12
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Security/ITotpService.cs
  50. 52
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Settings/IdentitySettingDefinitionProvider.cs
  51. 25
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Settings/IdentitySettingNames.cs
  52. 68
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IIdentityUserRepository.cs
  53. 39
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/SmsSecurityTokenCacheItem.cs
  54. 57
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/Microsoft/AspNetCore/Identity/PhoneNumberUserValidator.cs
  55. 38
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs
  56. 8
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.HttpApi/LINGYUN/Abp/Identity/AbpIdentityHttpApiModule.cs
  57. 2
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.HttpApi/LINGYUN/Abp/Identity/IdentityUserController.cs
  58. 16
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.HttpApi/LINGYUN/Abp/Identity/MyProfileController.cs
  59. 6
      aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.HttpApi/LINGYUN/Abp/IdentityServer/AbpIdentityServerHttpApiModule.cs
  60. 2
      aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.SmsValidator/LINGYUN.Abp.IdentityServer.SmsValidator.csproj
  61. 13
      aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.SmsValidator/LINGYUN/Abp/IdentityServer/SmsValidator/SmsTokenGrantValidator.cs
  62. 8
      aspnet-core/services/account/AuthServer.Host/AuthIdentityServerModule.cs
  63. 5
      aspnet-core/services/account/AuthServer.Host/AuthServer.Host.csproj
  64. 52
      aspnet-core/services/account/AuthServer.Host/EntityFrameworkCore/Identity/EfCoreIdentityUserExtensionRepository.cs
  65. 5
      aspnet-core/services/account/AuthServer.Host/Pages/Account/SendCode.cshtml.cs
  66. 12
      aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/AbpIdentityServerAdminHttpApiHostModule.cs
  67. 5
      aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/Emailing/Templates/EmailConfirmed.tpl
  68. 26
      aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/Emailing/Templates/IdentityEmailTemplateDefinitionProvider.cs
  69. 7
      aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/Emailing/Templates/IdentityEmailTemplates.cs
  70. 42
      aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/EntityFrameworkCore/Identity/EfCoreIdentityUserExtensionRepository.cs
  71. 11
      aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/LINGYUN.Abp.IdentityServer4.HttpApi.Host.csproj
  72. 14
      aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/Properties/launchSettings.json
  73. 66
      aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/UserSecurityCodeSender.cs
  74. 35
      vueJs/src/api/users.ts
  75. 7
      vueJs/src/views/login/index.vue
  76. 7
      vueJs/src/views/register/index.vue
  77. 7
      vueJs/src/views/reset-password/index.vue

45
aspnet-core/LINGYUN.MicroService.IdentityServer.sln

@ -23,12 +23,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "wechat", "wechat", "{AF8AEC
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WeChat.Authorization", "modules\wechat\LINGYUN.Abp.WeChat.Authorization\LINGYUN.Abp.WeChat.Authorization.csproj", "{F656C1B2-6122-44C1-97F4-421CB4C03ED8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "account", "account", "{09C7BFF7-BCCB-4E02-8DAE-3B047A68011D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Account.Domain.Shared", "modules\account\LINGYUN.Abp.Account.Domain.Shared\LINGYUN.Abp.Account.Domain.Shared.csproj", "{495A110A-8034-409B-A036-28CDF2CA9386}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Account.Domain", "modules\account\LINGYUN.Abp.Account.Domain\LINGYUN.Abp.Account.Domain.csproj", "{650FE9CC-4A55-4F2C-A82C-EF10DEB32DF0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "permission-management", "permission-management", "{2FEA83BA-2E6D-40AF-8781-12CFD042457F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.PermissionManagement.Domain", "modules\permissions-management\LINGYUN.Abp.PermissionManagement.Domain\LINGYUN.Abp.PermissionManagement.Domain.csproj", "{A0A11373-635B-4343-8EE7-1DFC3611F05A}"
@ -49,6 +43,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Aliyun.Authoriz
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Sms.Aliyun", "modules\common\LINGYUN.Abp.Sms.Aliyun\LINGYUN.Abp.Sms.Aliyun.csproj", "{A51E2EB5-EEC0-4FB0-99B9-A0FBE0D8B0EB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Identity.Domain.Shared", "modules\identity\LINGYUN.Abp.Identity.Domain.Shared\LINGYUN.Abp.Identity.Domain.Shared.csproj", "{E5E51981-864A-4B54-8E50-B5AE8B81245C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Identity.Domain", "modules\identity\LINGYUN.Abp.Identity.Domain\LINGYUN.Abp.Identity.Domain.csproj", "{6514E12E-D97C-4311-8301-F270D4E04A9F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Identity.EntityFrameworkCore", "modules\identity\LINGYUN.Abp.Identity.EntityFrameworkCore\LINGYUN.Abp.Identity.EntityFrameworkCore.csproj", "{77F424CB-75F2-4D7F-8B52-BBE2E09CB423}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.IdentityServer.EntityFrameworkCore", "modules\identityServer\LINGYUN.Abp.IdentityServer.EntityFrameworkCore\LINGYUN.Abp.IdentityServer.EntityFrameworkCore.csproj", "{644FAB8F-ED83-4539-AF43-8F8FE61EF778}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -75,14 +77,6 @@ Global
{F656C1B2-6122-44C1-97F4-421CB4C03ED8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F656C1B2-6122-44C1-97F4-421CB4C03ED8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F656C1B2-6122-44C1-97F4-421CB4C03ED8}.Release|Any CPU.Build.0 = Release|Any CPU
{495A110A-8034-409B-A036-28CDF2CA9386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{495A110A-8034-409B-A036-28CDF2CA9386}.Debug|Any CPU.Build.0 = Debug|Any CPU
{495A110A-8034-409B-A036-28CDF2CA9386}.Release|Any CPU.ActiveCfg = Release|Any CPU
{495A110A-8034-409B-A036-28CDF2CA9386}.Release|Any CPU.Build.0 = Release|Any CPU
{650FE9CC-4A55-4F2C-A82C-EF10DEB32DF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{650FE9CC-4A55-4F2C-A82C-EF10DEB32DF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{650FE9CC-4A55-4F2C-A82C-EF10DEB32DF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{650FE9CC-4A55-4F2C-A82C-EF10DEB32DF0}.Release|Any CPU.Build.0 = Release|Any CPU
{A0A11373-635B-4343-8EE7-1DFC3611F05A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0A11373-635B-4343-8EE7-1DFC3611F05A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0A11373-635B-4343-8EE7-1DFC3611F05A}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -111,6 +105,22 @@ Global
{A51E2EB5-EEC0-4FB0-99B9-A0FBE0D8B0EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A51E2EB5-EEC0-4FB0-99B9-A0FBE0D8B0EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A51E2EB5-EEC0-4FB0-99B9-A0FBE0D8B0EB}.Release|Any CPU.Build.0 = Release|Any CPU
{E5E51981-864A-4B54-8E50-B5AE8B81245C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5E51981-864A-4B54-8E50-B5AE8B81245C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5E51981-864A-4B54-8E50-B5AE8B81245C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5E51981-864A-4B54-8E50-B5AE8B81245C}.Release|Any CPU.Build.0 = Release|Any CPU
{6514E12E-D97C-4311-8301-F270D4E04A9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6514E12E-D97C-4311-8301-F270D4E04A9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6514E12E-D97C-4311-8301-F270D4E04A9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6514E12E-D97C-4311-8301-F270D4E04A9F}.Release|Any CPU.Build.0 = Release|Any CPU
{77F424CB-75F2-4D7F-8B52-BBE2E09CB423}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77F424CB-75F2-4D7F-8B52-BBE2E09CB423}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77F424CB-75F2-4D7F-8B52-BBE2E09CB423}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77F424CB-75F2-4D7F-8B52-BBE2E09CB423}.Release|Any CPU.Build.0 = Release|Any CPU
{644FAB8F-ED83-4539-AF43-8F8FE61EF778}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{644FAB8F-ED83-4539-AF43-8F8FE61EF778}.Debug|Any CPU.Build.0 = Debug|Any CPU
{644FAB8F-ED83-4539-AF43-8F8FE61EF778}.Release|Any CPU.ActiveCfg = Release|Any CPU
{644FAB8F-ED83-4539-AF43-8F8FE61EF778}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -124,9 +134,6 @@ Global
{F87B2BA3-40BC-4CB8-B53E-793C860F285D} = {70B8D735-1E89-4163-8EDB-56A74E374CF4}
{AF8AECC8-0F42-4FC5-B3C0-00987BA8279F} = {7C727110-59CA-495B-8143-C5DDEDE41289}
{F656C1B2-6122-44C1-97F4-421CB4C03ED8} = {AF8AECC8-0F42-4FC5-B3C0-00987BA8279F}
{09C7BFF7-BCCB-4E02-8DAE-3B047A68011D} = {7C727110-59CA-495B-8143-C5DDEDE41289}
{495A110A-8034-409B-A036-28CDF2CA9386} = {09C7BFF7-BCCB-4E02-8DAE-3B047A68011D}
{650FE9CC-4A55-4F2C-A82C-EF10DEB32DF0} = {09C7BFF7-BCCB-4E02-8DAE-3B047A68011D}
{2FEA83BA-2E6D-40AF-8781-12CFD042457F} = {7C727110-59CA-495B-8143-C5DDEDE41289}
{A0A11373-635B-4343-8EE7-1DFC3611F05A} = {2FEA83BA-2E6D-40AF-8781-12CFD042457F}
{6F13C138-1D71-48F5-A404-94047242E98F} = {AF8AECC8-0F42-4FC5-B3C0-00987BA8279F}
@ -137,6 +144,10 @@ Global
{6CB9D0F3-8D50-49B5-98E2-D777717E51E8} = {7C727110-59CA-495B-8143-C5DDEDE41289}
{C675C7DB-5825-4F64-BF76-8A4452A448D2} = {572DBB40-4637-4C01-8491-8686F7E22B45}
{A51E2EB5-EEC0-4FB0-99B9-A0FBE0D8B0EB} = {572DBB40-4637-4C01-8491-8686F7E22B45}
{E5E51981-864A-4B54-8E50-B5AE8B81245C} = {70B8D735-1E89-4163-8EDB-56A74E374CF4}
{6514E12E-D97C-4311-8301-F270D4E04A9F} = {70B8D735-1E89-4163-8EDB-56A74E374CF4}
{77F424CB-75F2-4D7F-8B52-BBE2E09CB423} = {70B8D735-1E89-4163-8EDB-56A74E374CF4}
{644FAB8F-ED83-4539-AF43-8F8FE61EF778} = {98887A8F-7040-4FA1-842F-A4C77A61ED09}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FCB77471-9ECB-4666-A316-1D6A6285A468}

30
aspnet-core/LINGYUN.MicroService.IdentityServerAdmin.sln

@ -25,18 +25,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "identity-server", "identity
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "identity", "identity", "{BD964040-90B2-4179-A5EB-5830F5C7E073}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Account.Domain.Shared", "modules\account\LINGYUN.Abp.Account.Domain.Shared\LINGYUN.Abp.Account.Domain.Shared.csproj", "{82ACBDDA-1C77-49B6-9F33-38DFD3D4691D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Account.Domain", "modules\account\LINGYUN.Abp.Account.Domain\LINGYUN.Abp.Account.Domain.csproj", "{2BA25A1B-D53E-432B-BAC2-BAAC66CA390D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Account.Application.Contracts", "modules\account\LINGYUN.Abp.Account.Application.Contracts\LINGYUN.Abp.Account.Application.Contracts.csproj", "{A4DFDE21-1931-46B5-8381-36B6EB6E51E1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Account.Application", "modules\account\LINGYUN.Abp.Account.Application\LINGYUN.Abp.Account.Application.csproj", "{6EE4D85F-6035-4AB5-B650-9E3D3A2AA8D2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Account.HttpApi", "modules\account\LINGYUN.Abp.Account.HttpApi\LINGYUN.Abp.Account.HttpApi.csproj", "{BB1124C0-79F4-4E72-8854-945B3F0AD76D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Account.Web", "modules\account\LINGYUN.Abp.Account.Web\LINGYUN.Abp.Account.Web.csproj", "{3297ED1B-FB82-4EC5-ADA1-0D0390BEDBB8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Identity.Domain", "modules\identity\LINGYUN.Abp.Identity.Domain\LINGYUN.Abp.Identity.Domain.csproj", "{E22445B9-7039-4DA1-B547-BAB437D6D2B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Identity.Application.Contracts", "modules\identity\LINGYUN.Abp.Identity.Application.Contracts\LINGYUN.Abp.Identity.Application.Contracts.csproj", "{116D9D69-ED2C-4F23-9445-3981D8EA0EB7}"
@ -59,7 +53,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{FD2DDD
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Settings", "modules\common\LINGYUN.Abp.Settings\LINGYUN.Abp.Settings.csproj", "{A8C83B9C-C9BF-48EF-80FF-D4FE2C936EFC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Identity.Domain.Shared", "modules\identity\LINGYUN.Abp.Identity.Domain.Shared\LINGYUN.Abp.Identity.Domain.Shared.csproj", "{23536755-4F00-4929-9C5E-D4CABD1CC513}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Identity.Domain.Shared", "modules\identity\LINGYUN.Abp.Identity.Domain.Shared\LINGYUN.Abp.Identity.Domain.Shared.csproj", "{23536755-4F00-4929-9C5E-D4CABD1CC513}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.ExceptionHandling", "modules\common\LINGYUN.Abp.ExceptionHandling\LINGYUN.Abp.ExceptionHandling.csproj", "{80A418EB-6149-4684-80EF-D8574B91FE2B}"
EndProject
@ -79,6 +73,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.MultiTenancy",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.MultiTenancy.DbFinder", "modules\tenants\LINGYUN.Abp.MultiTenancy.DbFinder\LINGYUN.Abp.MultiTenancy.DbFinder.csproj", "{70C4FA43-0526-48E3-B852-A21395502604}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Mvc.Validation", "modules\common\LINGYUN.Abp.AspNetCore.Mvc.Validation\LINGYUN.Abp.AspNetCore.Mvc.Validation.csproj", "{B3181D37-F379-4E16-8621-5E739C519393}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -109,14 +105,6 @@ Global
{DE9E58E2-268C-4DF0-85AA-FF7F328B62F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE9E58E2-268C-4DF0-85AA-FF7F328B62F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE9E58E2-268C-4DF0-85AA-FF7F328B62F0}.Release|Any CPU.Build.0 = Release|Any CPU
{82ACBDDA-1C77-49B6-9F33-38DFD3D4691D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{82ACBDDA-1C77-49B6-9F33-38DFD3D4691D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{82ACBDDA-1C77-49B6-9F33-38DFD3D4691D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{82ACBDDA-1C77-49B6-9F33-38DFD3D4691D}.Release|Any CPU.Build.0 = Release|Any CPU
{2BA25A1B-D53E-432B-BAC2-BAAC66CA390D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BA25A1B-D53E-432B-BAC2-BAAC66CA390D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BA25A1B-D53E-432B-BAC2-BAAC66CA390D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BA25A1B-D53E-432B-BAC2-BAAC66CA390D}.Release|Any CPU.Build.0 = Release|Any CPU
{A4DFDE21-1931-46B5-8381-36B6EB6E51E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A4DFDE21-1931-46B5-8381-36B6EB6E51E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A4DFDE21-1931-46B5-8381-36B6EB6E51E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -129,10 +117,6 @@ Global
{BB1124C0-79F4-4E72-8854-945B3F0AD76D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB1124C0-79F4-4E72-8854-945B3F0AD76D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB1124C0-79F4-4E72-8854-945B3F0AD76D}.Release|Any CPU.Build.0 = Release|Any CPU
{3297ED1B-FB82-4EC5-ADA1-0D0390BEDBB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3297ED1B-FB82-4EC5-ADA1-0D0390BEDBB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3297ED1B-FB82-4EC5-ADA1-0D0390BEDBB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3297ED1B-FB82-4EC5-ADA1-0D0390BEDBB8}.Release|Any CPU.Build.0 = Release|Any CPU
{E22445B9-7039-4DA1-B547-BAB437D6D2B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E22445B9-7039-4DA1-B547-BAB437D6D2B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E22445B9-7039-4DA1-B547-BAB437D6D2B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -205,6 +189,10 @@ Global
{70C4FA43-0526-48E3-B852-A21395502604}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70C4FA43-0526-48E3-B852-A21395502604}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70C4FA43-0526-48E3-B852-A21395502604}.Release|Any CPU.Build.0 = Release|Any CPU
{B3181D37-F379-4E16-8621-5E739C519393}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B3181D37-F379-4E16-8621-5E739C519393}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3181D37-F379-4E16-8621-5E739C519393}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3181D37-F379-4E16-8621-5E739C519393}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -219,12 +207,9 @@ Global
{4F06C65B-22CC-466E-B3FD-3F695DD38191} = {14B8F528-C649-4FAD-9BBB-6C979ED403E1}
{7AB942D5-D139-4F9F-9342-71534AA3A5AC} = {14B8F528-C649-4FAD-9BBB-6C979ED403E1}
{BD964040-90B2-4179-A5EB-5830F5C7E073} = {14B8F528-C649-4FAD-9BBB-6C979ED403E1}
{82ACBDDA-1C77-49B6-9F33-38DFD3D4691D} = {4F06C65B-22CC-466E-B3FD-3F695DD38191}
{2BA25A1B-D53E-432B-BAC2-BAAC66CA390D} = {4F06C65B-22CC-466E-B3FD-3F695DD38191}
{A4DFDE21-1931-46B5-8381-36B6EB6E51E1} = {4F06C65B-22CC-466E-B3FD-3F695DD38191}
{6EE4D85F-6035-4AB5-B650-9E3D3A2AA8D2} = {4F06C65B-22CC-466E-B3FD-3F695DD38191}
{BB1124C0-79F4-4E72-8854-945B3F0AD76D} = {4F06C65B-22CC-466E-B3FD-3F695DD38191}
{3297ED1B-FB82-4EC5-ADA1-0D0390BEDBB8} = {4F06C65B-22CC-466E-B3FD-3F695DD38191}
{E22445B9-7039-4DA1-B547-BAB437D6D2B3} = {BD964040-90B2-4179-A5EB-5830F5C7E073}
{116D9D69-ED2C-4F23-9445-3981D8EA0EB7} = {BD964040-90B2-4179-A5EB-5830F5C7E073}
{B07A1763-1B37-4416-8F0C-B938C175A56D} = {BD964040-90B2-4179-A5EB-5830F5C7E073}
@ -246,6 +231,7 @@ Global
{89BA9708-62E5-4FD6-A28A-CF9E1C26DCAE} = {14B8F528-C649-4FAD-9BBB-6C979ED403E1}
{99B60A42-29DA-4D91-9255-B90F53BC6D6E} = {89BA9708-62E5-4FD6-A28A-CF9E1C26DCAE}
{70C4FA43-0526-48E3-B852-A21395502604} = {89BA9708-62E5-4FD6-A28A-CF9E1C26DCAE}
{B3181D37-F379-4E16-8621-5E739C519393} = {FD2DDD48-8F84-4924-BBAF-52080AB32267}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {785FFF4D-BC59-499E-88A3-7CB7A7667228}

10
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN.Abp.Account.Application.Contracts.csproj

@ -8,11 +8,17 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Account.Application.Contracts" Version="3.3.0" />
<None Remove="LINGYUN\Abp\Account\Localization\Resources\en.json" />
<None Remove="LINGYUN\Abp\Account\Localization\Resources\zh-Hans.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.Account.Domain.Shared\LINGYUN.Abp.Account.Domain.Shared.csproj" />
<EmbeddedResource Include="LINGYUN\Abp\Account\Localization\Resources\en.json" />
<EmbeddedResource Include="LINGYUN\Abp\Account\Localization\Resources\zh-Hans.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Account.Application.Contracts" Version="3.3.0" />
</ItemGroup>
</Project>

22
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/AbpAccountApplicationContractsModule.cs

@ -1,9 +1,27 @@
using Volo.Abp.Modularity;
using Volo.Abp.Account.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.Account
{
[DependsOn(typeof(AbpAccountDomainSharedModule))]
[DependsOn(
typeof(Volo.Abp.Account.AbpAccountApplicationContractsModule))]
public class AbpAccountApplicationContractsModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpAccountApplicationContractsModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<AccountResource>()
.AddVirtualJson("/LINGYUN/Abp/Account/Localization/Resources");
});
}
}
}

16
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/PhoneNumberRegisterDto.cs → aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/PhoneRegisterDto.cs

@ -1,35 +1,43 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Auditing;
using Volo.Abp.Identity;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.Account
{
public class PhoneNumberRegisterDto
public class PhoneRegisterDto
{
[Required]
[Phone]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))]
[Display(Name = "PhoneNumber")]
public string PhoneNumber { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))]
[DisplayName("Name")]
public string Name { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))]
[DisplayName("UserName")]
public string UserName { get; set; }
[EmailAddress]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))]
[DisplayName("EmailAddress")]
public string EmailAddress { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[DataType(DataType.Password)]
[DisplayName("Password")]
[DisableAuditing]
public string Password { get; set; }
[Required]
[StringLength(6)]
public string VerifyCode { get; set; }
[StringLength(6,MinimumLength = 6)]
[DisableAuditing]
[DisplayName("DisplayName:SmsVerifyCode")]
public string Code { get; set; }
}
}

12
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/PasswordResetDto.cs → aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/PhoneResetPasswordDto.cs

@ -5,11 +5,17 @@ using Volo.Abp.Validation;
namespace LINGYUN.Abp.Account
{
public class PasswordResetDto
public class PhoneResetPasswordDto
{
[Required]
[Phone]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))]
// 如果Dto属性和本地化内容不一致,需要指定本地化名称如下
// [Display(Name = "DisplayName:RequiredPhoneNumber")] //json本地化文件中必须有相同的格式: DisplayName:RequiredPhoneNumber
//[DisplayName("DisplayName:RequiredPhoneNumber")] //两种方法都可以
// 如果Dto属性与本地化内容一致,不需要显示指定名称,但是本地化文件必须存在对应格式的文本: DisplayName:PhoneNumber
public string PhoneNumber { get; set; }
[Required]
@ -20,6 +26,8 @@ namespace LINGYUN.Abp.Account
[Required]
[StringLength(6)]
public string VerifyCode { get; set; }
[DisableAuditing]
[Display(Name = "DisplayName:SmsVerifyCode")]
public string Code { get; set; }
}
}

6
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyDto.cs → aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/SendPhoneRegisterCodeDto.cs

@ -4,14 +4,12 @@ using Volo.Abp.Validation;
namespace LINGYUN.Abp.Account
{
public class VerifyDto
public class SendPhoneRegisterCodeDto
{
[Required]
[Phone]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))]
[Display(Name = "PhoneNumber")]
public string PhoneNumber { get; set; }
[Required]
public PhoneNumberVerifyType VerifyType { get; set; }
}
}

15
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/SendPhoneResetPasswordCodeDto.cs

@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Identity;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.Account
{
public class SendPhoneResetPasswordCodeDto
{
[Required]
[Phone]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))]
[Display(Name = "PhoneNumber")]
public string PhoneNumber { get; set; }
}
}

3
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/RegisterDto.cs → aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/SendPhoneSigninCodeDto.cs

@ -4,11 +4,12 @@ using Volo.Abp.Validation;
namespace LINGYUN.Abp.Account
{
public class RegisterDto
public class SendPhoneSigninCodeDto
{
[Required]
[Phone]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))]
[Display(Name = "PhoneNumber")]
public string PhoneNumber { get; set; }
}
}

11
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/WeChatRegisterDto.cs

@ -8,22 +8,23 @@ namespace LINGYUN.Abp.Account
public class WeChatRegisterDto
{
[Required]
[DisableAuditing]
[Display(Name = "DisplayName:WeChatCode")]
public string Code { get; set; }
[DisableAuditing]
[DataType(DataType.Password)]
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[DisableAuditing]
[Display(Name = "Password")]
public string Password { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))]
public string Name { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))]
[Display(Name = "UserName")]
public string UserName { get; set; }
[EmailAddress]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))]
[Display(Name = "EmailAddress")]
public string EmailAddress { get; set; }
}
}

44
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/IAccountAppService.cs

@ -1,17 +1,45 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.Identity;
namespace LINGYUN.Abp.Account
{
public interface IAccountAppService : IApplicationService
{
Task<IdentityUserDto> RegisterAsync(PhoneNumberRegisterDto input);
Task<IdentityUserDto> RegisterAsync(WeChatRegisterDto input);
Task ResetPasswordAsync(PasswordResetDto input);
Task VerifyPhoneNumberAsync(VerifyDto input);
/// <summary>
/// 通过手机号注册用户账户
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task RegisterAsync(PhoneRegisterDto input);
/// <summary>
/// 通过微信注册用户账户
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task RegisterAsync(WeChatRegisterDto input);
/// <summary>
/// 通过手机号重置用户密码
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task ResetPasswordAsync(PhoneResetPasswordDto input);
/// <summary>
/// 发送手机注册验证码短信
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task SendPhoneRegisterCodeAsync(SendPhoneRegisterCodeDto input);
/// <summary>
/// 发送手机登录验证码短信
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task SendPhoneSigninCodeAsync(SendPhoneSigninCodeDto input);
/// <summary>
/// 发送手机重置密码验证码短信
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task SendPhoneResetPasswordCodeAsync(SendPhoneResetPasswordCodeDto input);
}
}

15
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json

@ -0,0 +1,15 @@
{
"culture": "en",
"texts": {
"SendRepeatSmsVerifyCode": "Phone verification code cannot be sent repeatedly within {0} minutes!",
"DuplicatePhoneNumber": "The phone number already exists!",
"PhoneNumberNotRegisterd": "The registered mobile phone number is not registered!",
"InvalidSmsVerifyCode": "The phone verification code is invalid or expired!",
"RequiredEmailAddress": "Email address required",
"InvalidPhoneNumber": "Invalid phone number",
"DuplicateWeChat": "The wechat has been registered!",
"DisplayName:SmsVerifyCode": "SMS verification code",
"DisplayName:EmailVerifyCode": "Mail verification code",
"DisplayName:WeChatCode": "Wechat login code"
}
}

15
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json

@ -0,0 +1,15 @@
{
"culture": "zh-Hans",
"texts": {
"SendRepeatSmsVerifyCode": "手机验证码不能在 {0} 分钟内重复发送!",
"DuplicatePhoneNumber": "手机号已经存在!",
"PhoneNumberNotRegisterd": "手机号码未注册!",
"InvalidSmsVerifyCode": "手机验证码无效或已经过期!",
"RequiredEmailAddress": "邮件地址必须输入",
"InvalidPhoneNumber": "手机号无效",
"DuplicateWeChat": "微信号已经注册过!",
"DisplayName:SmsVerifyCode": "短信验证码",
"DisplayName:EmailVerifyCode": "邮件验证码",
"DisplayName:WeChatCode": "微信登录凭证"
}
}

2
aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN.Abp.Account.Application.csproj

@ -14,9 +14,9 @@
<ItemGroup>
<ProjectReference Include="..\..\common\LINGYUN.Abp.Settings\LINGYUN.Abp.Settings.csproj" />
<ProjectReference Include="..\..\identity\LINGYUN.Abp.Identity.Domain\LINGYUN.Abp.Identity.Domain.csproj" />
<ProjectReference Include="..\..\wechat\LINGYUN.Abp.WeChat.Authorization\LINGYUN.Abp.WeChat.Authorization.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.Account.Application.Contracts\LINGYUN.Abp.Account.Application.Contracts.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.Account.Domain\LINGYUN.Abp.Account.Domain.csproj" />
</ItemGroup>
</Project>

5
aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AbpAccountApplicationModule.cs

@ -1,12 +1,13 @@
using LINGYUN.Abp.WeChat.Authorization;
using LINGYUN.Abp.Identity;
using LINGYUN.Abp.WeChat.Authorization;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Account
{
[DependsOn(
typeof(AbpAccountDomainModule),
typeof(Volo.Abp.Account.AbpAccountApplicationModule),
typeof(AbpAccountApplicationContractsModule),
typeof(AbpIdentityDomainModule),
typeof(AbpWeChatAuthorizationModule))]
public class AbpAccountApplicationModule : AbpModule
{

402
aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs

@ -1,49 +1,58 @@
using LINGYUN.Abp.WeChat.Authorization;
using LINGYUN.Abp.Identity;
using LINGYUN.Abp.Identity.Security;
using LINGYUN.Abp.Identity.Settings;
using LINGYUN.Abp.WeChat.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.ComponentModel.DataAnnotations;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Account.Localization;
using Volo.Abp.Application.Services;
using Volo.Abp.Caching;
using Volo.Abp.Identity;
using Volo.Abp.Settings;
using Volo.Abp.Sms;
using Volo.Abp.Validation;
using IIdentityUserRepository = LINGYUN.Abp.Identity.IIdentityUserRepository;
namespace LINGYUN.Abp.Account
{
/// <summary>
/// 用户注册服务
/// </summary>
public class AccountAppService : ApplicationService, IAccountAppService
{
private IWeChatOpenIdFinder _weChatOpenIdFinder;
protected IWeChatOpenIdFinder WeChatOpenIdFinder => LazyGetRequiredService(ref _weChatOpenIdFinder);
protected ISmsSender SmsSender { get; }
protected IdentityUserManager UserManager { get; }
protected ITotpService TotpService { get; }
protected IdentityUserStore UserStore { get; }
protected IdentityUserManager UserManager { get; }
protected IIdentityUserRepository UserRepository { get; }
protected IDistributedCache<AccountRegisterVerifyCacheItem> Cache { get; }
protected PhoneNumberTokenProvider<IdentityUser> PhoneNumberTokenProvider { get; }
protected IUserSecurityCodeSender SecurityCodeSender { get; }
protected IWeChatOpenIdFinder WeChatOpenIdFinder { get; }
protected IDistributedCache<SmsSecurityTokenCacheItem> SecurityTokenCache { get; }
public AccountAppService(
ISmsSender smsSender,
IdentityUserManager userManager,
ITotpService totpService,
IdentityUserStore userStore,
IdentityUserManager userManager,
IWeChatOpenIdFinder weChatOpenIdFinder,
IIdentityUserRepository userRepository,
IDistributedCache<AccountRegisterVerifyCacheItem> cache,
PhoneNumberTokenProvider<IdentityUser> phoneNumberTokenProvider)
IUserSecurityCodeSender securityCodeSender,
IDistributedCache<SmsSecurityTokenCacheItem> securityTokenCache)
{
Cache = cache;
SmsSender = smsSender;
TotpService = totpService;
UserStore = userStore;
UserManager = userManager;
UserRepository = userRepository;
PhoneNumberTokenProvider = phoneNumberTokenProvider;
LocalizationResource = typeof(Localization.AccountResource);
WeChatOpenIdFinder = weChatOpenIdFinder;
SecurityCodeSender = securityCodeSender;
SecurityTokenCache = securityTokenCache;
LocalizationResource = typeof(AccountResource);
}
public virtual async Task<IdentityUserDto> RegisterAsync(WeChatRegisterDto input)
public virtual async Task RegisterAsync(WeChatRegisterDto input)
{
ThowIfInvalidEmailAddress(input.EmailAddress);
await CheckSelfRegistrationAsync();
var wehchatOpenId = await WeChatOpenIdFinder.FindAsync(input.Code);
@ -54,13 +63,19 @@ namespace LINGYUN.Abp.Account
// 应该要抛出微信号已注册异常,而不是直接返回注册用户数据,否则造成用户信息泄露
throw new UserFriendlyException(L["DuplicateWeChat"]);
}
var userName = input.UserName ?? "wx-" + wehchatOpenId.OpenId;
var userEmail = input.EmailAddress ?? $"{userName}@{CurrentTenant.Name ?? "default"}.io";//如果邮件地址不验证,随意写入一个
user = new IdentityUser(GuidGenerator.Create(), userName, userEmail, CurrentTenant.Id)
var userName = input.UserName;
if (userName.IsNullOrWhiteSpace())
{
userName = "wxid-" + wehchatOpenId.OpenId.ToMd5();
}
var userEmail = input.EmailAddress;//如果邮件地址不验证,随意写入一个
if (userEmail.IsNullOrWhiteSpace())
{
Name = input.Name ?? userName
};
userEmail = $"{userName}@{CurrentTenant.Name ?? "default"}.io";
}
user = new IdentityUser(GuidGenerator.Create(), userName, userEmail, CurrentTenant.Id);
(await UserManager.CreateAsync(user, input.Password)).CheckErrors();
(await UserManager.AddDefaultRolesAsync(user)).CheckErrors();
@ -68,205 +83,192 @@ namespace LINGYUN.Abp.Account
var userLogin = new UserLoginInfo(AbpWeChatAuthorizationConsts.ProviderKey, wehchatOpenId.OpenId, AbpWeChatAuthorizationConsts.DisplayName);
(await UserManager.AddLoginAsync(user, userLogin)).CheckErrors();
return ObjectMapper.Map<IdentityUser, IdentityUserDto>(user);
await CurrentUnitOfWork.SaveChangesAsync();
}
/// <summary>
/// 用户注册
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <remarks>
/// 用户通过VerifyPhoneNumber接口发送到手机的验证码,传递注册信息注册用户
/// 如果没有此手机号的缓存记录或验证码不匹配,抛出验证码无效的异常
/// 用户注册成功,清除缓存的验证码记录
/// </remarks>
public virtual async Task<IdentityUserDto> RegisterAsync(PhoneNumberRegisterDto input)
public virtual async Task SendPhoneRegisterCodeAsync(SendPhoneRegisterCodeDto input)
{
var phoneVerifyCacheKey = NormalizeCacheKey(input.PhoneNumber);
await CheckSelfRegistrationAsync();
await CheckNewUserPhoneNumberNotBeUsedAsync(input.PhoneNumber);
var phoneVerifyCacheItem = await Cache.GetAsync(phoneVerifyCacheKey);
if(phoneVerifyCacheItem == null || !phoneVerifyCacheItem.VerifyCode.Equals(input.VerifyCode))
var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode");
var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey);
var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1);
if (securityTokenCacheItem != null)
{
throw new UserFriendlyException(L["PhoneVerifyCodeInvalid"]);
throw new UserFriendlyException(L["SendRepeatSmsVerifyCode", interval]);
}
await CheckSelfRegistrationAsync();
var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsNewUserRegister);
// 需要用户输入邮箱?
//if (UserManager.Options.User.RequireUniqueEmail)
//{
// if (input.EmailAddress.IsNullOrWhiteSpace())
// {
// throw new UserFriendlyException(L["RequiredEmailAddress"]);
// }
//}
var userEmail = input.EmailAddress ?? $"{input.PhoneNumber}@{CurrentTenant.Name ?? "default"}.io";//如果邮件地址不验证,随意写入一个
var userName = input.UserName ?? input.PhoneNumber;
var user = new IdentityUser(GuidGenerator.Create(), userName, userEmail, CurrentTenant.Id)
{
Name = input.Name ?? input.PhoneNumber
};
// 写入手机号要在创建用户之前,因为有一个自定义的手机号验证
await UserStore.SetPhoneNumberAsync(user, input.PhoneNumber);
await UserStore.SetPhoneNumberConfirmedAsync(user, true);
// 安全令牌
var securityToken = GuidGenerator.Create().ToString("N");
(await UserManager.CreateAsync(user, input.Password)).CheckErrors();
var code = TotpService.GenerateCode(Encoding.Unicode.GetBytes(securityToken), securityTokenCacheKey);
securityTokenCacheItem = new SmsSecurityTokenCacheItem(code.ToString(), securityToken);
(await UserManager.AddDefaultRolesAsync(user)).CheckErrors();
await SecurityCodeSender.SendPhoneConfirmedCodeAsync(
input.PhoneNumber, securityTokenCacheItem.Token, template);
await Cache.RemoveAsync(phoneVerifyCacheKey);
return ObjectMapper.Map<IdentityUser, IdentityUserDto>(user);
await SecurityTokenCache
.SetAsync(securityTokenCacheKey, securityTokenCacheItem,
new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval)
});
}
// TODO: 是否有必要移动到ProfileService
/// <summary>
/// 重置用户密码
/// </summary>
/// <param name="passwordReset"></param>
/// <returns></returns>
public virtual async Task ResetPasswordAsync(PasswordResetDto passwordReset)
public virtual async Task RegisterAsync(PhoneRegisterDto input)
{
// 本来可以不需要的,令牌算法有一个有效期
// 不过这里采用令牌强制过期策略,避免一个令牌多次使用
var phoneVerifyCacheKey = NormalizeCacheKey(passwordReset.PhoneNumber);
await CheckSelfRegistrationAsync();
await CheckNewUserPhoneNumberNotBeUsedAsync(input.PhoneNumber);
var phoneVerifyCacheItem = await Cache.GetAsync(phoneVerifyCacheKey);
if (phoneVerifyCacheItem == null || !phoneVerifyCacheItem.VerifyCode.Equals(passwordReset.VerifyCode))
var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode");
var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey);
if (securityTokenCacheItem == null)
{
throw new UserFriendlyException(L["PhoneVerifyCodeInvalid"]);
// 验证码过期
throw new UserFriendlyException(L["InvalidSmsVerifyCode"]);
}
var userId = await GetUserIdByPhoneNumberAsync(passwordReset.PhoneNumber);
// 验证码是否有效
if (input.Code.Equals(securityTokenCacheItem.Token) && int.TryParse(input.Code, out int token))
{
var securityToken = Encoding.Unicode.GetBytes(securityTokenCacheItem.SecurityToken);
// 校验totp验证码
if (TotpService.ValidateCode(securityToken, token, securityTokenCacheKey))
{
var userEmail = input.EmailAddress ?? $"{input.PhoneNumber}@{CurrentTenant.Name ?? "default"}.io";//如果邮件地址不验证,随意写入一个
var userName = input.UserName ?? input.PhoneNumber;
var user = new IdentityUser(GuidGenerator.Create(), userName, userEmail, CurrentTenant.Id)
{
Name = input.Name ?? input.PhoneNumber
};
await UserStore.SetPhoneNumberAsync(user, input.PhoneNumber);
await UserStore.SetPhoneNumberConfirmedAsync(user, true);
var user = await UserManager.GetByIdAsync(userId);
(await UserManager.CreateAsync(user, input.Password)).CheckErrors();
(await UserManager.ResetPasswordAsync(user, phoneVerifyCacheItem.VerifyToken, passwordReset.NewPassword)).CheckErrors();
(await UserManager.AddDefaultRolesAsync(user)).CheckErrors();
await SecurityTokenCache.RemoveAsync(securityTokenCacheKey);
await Cache.RemoveAsync(phoneVerifyCacheKey);
await CurrentUnitOfWork.SaveChangesAsync();
return;
}
}
// 验证码无效
throw new UserFriendlyException(L["InvalidSmsVerifyCode"]);
}
/// <summary>
/// 验证手机号码
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <remarks>
/// 用户传递手机号码及认证类型
/// 1、如果认证类型为注册:
/// 先查询是否存在此手机号的缓存验证码信息,如果存在,抛出不能重复发送验证码异常
/// 随机生成6位纯数字验证码,通过短信接口服务发送到用户手机,并缓存验证码,设定一个有效时长
///
/// 2、如果认证类型为登录:
/// 先查询是否存在此手机号的缓存验证码信息,如果存在,抛出不能重复发送验证码异常
/// 通过手机号查询用户信息,如果用户不存在,抛出手机号未注册异常
/// 调用PhoneNumberTokenProvider接口生成6位手机验证码,用途为 phone_verify
/// 发送手机验证码到用户手机,并缓存验证码,设定一个有效时长
///
/// 用户调用 IdentityServer4/connect/token 登录系统(需要引用LINGYUN.Abp.IdentityServer.SmsValidator模块)
/// 参数1:grant_type=phone_verify
/// 参数2:phone_number=手机号码
/// 参数3:phone_verify_code=手机验证码
/// 参数4:client_id=客户端标识
/// 参数5:client_secret=客户端密钥
/// </remarks>
public virtual async Task VerifyPhoneNumberAsync(VerifyDto input)
public virtual async Task SendPhoneResetPasswordCodeAsync(SendPhoneResetPasswordCodeDto input)
{
// TODO: 借用TOTP算法生成6位动态验证码
/*
* : UserManager.GeneratePasswordResetTokenAsync Token
* Token设计的意义就是用户通过链接来重置密码,
* ,,便
*
* :
*
* step1: ,
* step2: ,,,
* step3(): UserManager.GenerateTwoFactorTokenAsync ,,
* ,TOTP算法,
* step4(): , UserManager.VerifyTwoFactorTokenAsync
* , UserManager.GeneratePasswordResetTokenAsync Token
*/
var verifyCodeExpiration = await SettingProvider.GetAsync<int>(AccountSettingNames.PhoneVerifyCodeExpiration);
var phoneVerifyCacheKey = NormalizeCacheKey(input.PhoneNumber);
var verifyCacheItem = await Cache.GetAsync(phoneVerifyCacheKey);
if (verifyCacheItem != null)
var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode");
var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey);
var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1);
// 传递 isConfirmed 用户必须是已确认过手机号的
var user = await GetUserByPhoneNumberAsync(input.PhoneNumber, isConfirmed: true);
// 能查询到缓存就是重复发送
if (securityTokenCacheItem != null)
{
throw new UserFriendlyException(L["PhoneVerifyCodeNotRepeatSend", verifyCodeExpiration]);
throw new UserFriendlyException(L["SendRepeatSmsVerifyCode", interval]);
}
verifyCacheItem = new AccountRegisterVerifyCacheItem
{
PhoneNumber = input.PhoneNumber,
};
switch (input.VerifyType)
{
case PhoneNumberVerifyType.Register:
var phoneVerifyCode = new Random().Next(100000, 999999);
verifyCacheItem.VerifyCode = phoneVerifyCode.ToString();
var templateCode = await SettingProvider.GetOrDefaultAsync(AccountSettingNames.SmsRegisterTemplateCode, ServiceProvider);
await SendPhoneVerifyMessageAsync(templateCode, input.PhoneNumber, phoneVerifyCode.ToString());
break;
case PhoneNumberVerifyType.Signin:
var phoneSigninCode = await SendSigninVerifyCodeAsync(input.PhoneNumber);
verifyCacheItem.VerifyCode = phoneSigninCode;
break;
case PhoneNumberVerifyType.ResetPassword:
var resetPasswordCode = new Random().Next(100000, 999999);
verifyCacheItem.VerifyCode = resetPasswordCode.ToString();
var resetPasswordToken = await SendResetPasswordVerifyCodeAsync(input.PhoneNumber, verifyCacheItem.VerifyCode);
verifyCacheItem.VerifyToken = resetPasswordToken;
break;
}
var cacheOptions = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(verifyCodeExpiration)
};
await Cache.SetAsync(phoneVerifyCacheKey, verifyCacheItem, cacheOptions);
}
/// <summary>
/// 发送登录验证码
/// </summary>
/// <param name="phoneNumber">手机号</param>
/// <returns>返回登录验证码</returns>
protected virtual async Task<string> SendSigninVerifyCodeAsync(string phoneNumber)
{
// 查找用户信息
var user = await GetUserByPhoneNumberAsync(phoneNumber);
// 获取登录验证码模板号
var templateCode = await SettingProvider.GetOrDefaultAsync(AccountSettingNames.SmsSigninTemplateCode, ServiceProvider);
// 生成手机验证码
var phoneVerifyCode = await PhoneNumberTokenProvider.GenerateAsync("phone_verify", UserManager, user);
var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsResetPassword);
// 生成二次认证码
var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider);
// 发送短信验证码
await SendPhoneVerifyMessageAsync(templateCode, user.PhoneNumber, phoneVerifyCode);
return phoneVerifyCode;
await SecurityCodeSender.SendPhoneConfirmedCodeAsync(input.PhoneNumber, code, template);
// 缓存这个手机号的记录,防重复
securityTokenCacheItem = new SmsSecurityTokenCacheItem(code, user.SecurityStamp);
await SecurityTokenCache
.SetAsync(securityTokenCacheKey, securityTokenCacheItem,
new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval)
});
}
protected virtual async Task<string> SendResetPasswordVerifyCodeAsync(string phoneNumber, string phoneVerifyCode)
public virtual async Task ResetPasswordAsync(PhoneResetPasswordDto input)
{
// 查找用户信息
var user = await GetUserByPhoneNumberAsync(phoneNumber);
// 获取登录验证码模板号
var templateCode = await SettingProvider.GetOrDefaultAsync(AccountSettingNames.SmsResetPasswordTemplateCode, ServiceProvider);
// 生成重置密码验证码
var phoneVerifyToken = await UserManager.GeneratePasswordResetTokenAsync(user);
// 发送短信验证码
await SendPhoneVerifyMessageAsync(templateCode, user.PhoneNumber, phoneVerifyCode);
var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode");
var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey);
if (securityTokenCacheItem == null)
{
throw new UserFriendlyException(L["InvalidSmsVerifyCode"]);
}
// 传递 isConfirmed 用户必须是已确认过手机号的
var user = await GetUserByPhoneNumberAsync(input.PhoneNumber, isConfirmed: true);
// 验证二次认证码
if (!await UserManager.VerifyTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider, input.Code))
{
// 验证码无效
throw new UserFriendlyException(L["InvalidSmsVerifyCode"]);
}
// 生成真正的重置密码Token
var resetPwdToken = await UserManager.GeneratePasswordResetTokenAsync(user);
// 重置密码
(await UserManager.ResetPasswordAsync(user, resetPwdToken, input.NewPassword)).CheckErrors();
// 移除缓存项
await SecurityTokenCache.RemoveAsync(securityTokenCacheKey);
return phoneVerifyToken;
await CurrentUnitOfWork.SaveChangesAsync();
}
protected virtual async Task<IdentityUser> GetUserByPhoneNumberAsync(string phoneNumber)
public virtual async Task SendPhoneSigninCodeAsync(SendPhoneSigninCodeDto input)
{
// 查找用户信息
var user = await UserRepository.FindByPhoneNumberAsync(phoneNumber);
if (user == null)
var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode");
var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey);
var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1);
if (securityTokenCacheItem != null)
{
throw new UserFriendlyException(L["PhoneNumberNotRegisterd"]);
throw new UserFriendlyException(L["SendRepeatSmsVerifyCode", interval]);
}
return user;
// 传递 isConfirmed 验证过的用户才允许通过手机登录
var user = await GetUserByPhoneNumberAsync(input.PhoneNumber, isConfirmed: true);
var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider);
var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsUserSignin);
// 发送登录验证码短信
await SecurityCodeSender.SendPhoneConfirmedCodeAsync(input.PhoneNumber, code, template);
// 缓存登录验证码状态,防止同一手机号重复发送
securityTokenCacheItem = new SmsSecurityTokenCacheItem(code, user.SecurityStamp);
await SecurityTokenCache
.SetAsync(securityTokenCacheKey, securityTokenCacheItem,
new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval)
});
}
protected virtual async Task<Guid> GetUserIdByPhoneNumberAsync(string phoneNumber)
protected virtual async Task<IdentityUser> GetUserByPhoneNumberAsync(string phoneNumber, bool isConfirmed = true)
{
// 查找用户信息
var userId = await UserRepository.GetIdByPhoneNumberAsync(phoneNumber);
if (!userId.HasValue)
var user = await UserRepository.FindByPhoneNumberAsync(phoneNumber, isConfirmed, true);
if (user == null)
{
throw new UserFriendlyException(L["PhoneNumberNotRegisterd"]);
}
return userId.Value;
return user;
}
/// <summary>
/// 检查是否允许用户注册
/// </summary>
@ -278,28 +280,26 @@ namespace LINGYUN.Abp.Account
throw new UserFriendlyException(L["SelfRegistrationDisabledMessage"]);
}
}
/// <summary>
/// 发送短信验证码
/// </summary>
/// <param name="templateCode"></param>
/// <param name="phoneNumber"></param>
/// <param name="verifyCode"></param>
/// <returns></returns>
protected virtual async Task SendPhoneVerifyMessageAsync(string templateCode, string phoneNumber, string verifyCode)
protected virtual async Task CheckNewUserPhoneNumberNotBeUsedAsync(string phoneNumber)
{
var sendMessage = new SmsMessage(phoneNumber, "SendSmsMessage");
sendMessage.Properties.Add("code", verifyCode);
sendMessage.Properties.Add("TemplateCode", templateCode);
await SmsSender.SendAsync(sendMessage);
if (await UserRepository.IsPhoneNumberUedAsync(phoneNumber))
{
throw new UserFriendlyException(L["DuplicatePhoneNumber"]);
}
}
/// <summary>
/// 格式化缓存主键
/// </summary>
/// <param name="phoneNumber">手机号码</param>
/// <returns></returns>
protected string NormalizeCacheKey(string phoneNumber)
private void ThowIfInvalidEmailAddress(string inputEmail)
{
return $"ACCOUNT-PHONE:{phoneNumber}";
if (!inputEmail.IsNullOrWhiteSpace() &&
!ValidationHelper.IsValidEmailAddress(inputEmail))
{
throw new AbpValidationException(
new ValidationResult[]
{
new ValidationResult(L["The {0} field is not a valid e-mail address.", L["DisplayName:EmailAddress"]], new string[]{ "EmailAddress" })
});
}
}
}
}

12
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN.Abp.Account.Domain.Shared.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
@ -8,17 +8,11 @@
</PropertyGroup>
<ItemGroup>
<None Remove="LINGYUN\Abp\Account\Localization\Resources\en.json" />
<None Remove="LINGYUN\Abp\Account\Localization\Resources\zh-Hans.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="LINGYUN\Abp\Account\Localization\Resources\en.json" />
<EmbeddedResource Include="LINGYUN\Abp\Account\Localization\Resources\zh-Hans.json" />
<PackageReference Include="Volo.Abp.Localization" Version="3.3.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Localization" Version="3.3.0" />
<ProjectReference Include="..\..\identity\LINGYUN.Abp.Identity.Domain.Shared\LINGYUN.Abp.Identity.Domain.Shared.csproj" />
</ItemGroup>
</Project>

63
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/AccountSettingDefinitionProvider.cs

@ -1,63 +0,0 @@
using LINGYUN.Abp.Account.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.Account
{
public class AccountSettingDefinitionProvider : SettingDefinitionProvider
{
public override void Define(ISettingDefinitionContext context)
{
context.Add(GetAccountSettings());
}
protected SettingDefinition[] GetAccountSettings()
{
return new SettingDefinition[]
{
new SettingDefinition(
name: AccountSettingNames.SmsRegisterTemplateCode,
defaultValue: "SMS_190728520",
displayName: L("DisplayName:SmsRegisterTemplateCode"),
description: L("Description:SmsRegisterTemplateCode"),
isVisibleToClients: true)
.WithProviders(
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
name: AccountSettingNames.SmsSigninTemplateCode,
defaultValue: "SMS_190728516",
displayName: L("DisplayName:SmsSigninTemplateCode"),
description: L("Description:SmsSigninTemplateCode"),
isVisibleToClients: true)
.WithProviders(
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
name: AccountSettingNames.SmsResetPasswordTemplateCode,
defaultValue: "SMS_192530831",
displayName: L("DisplayName:SmsResetPasswordTemplateCode"),
description: L("Description:SmsResetPasswordTemplateCode"),
isVisibleToClients: true)
.WithProviders(
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
name: AccountSettingNames.PhoneVerifyCodeExpiration,
defaultValue: "3",
displayName: L("DisplayName:PhoneVerifyCodeExpiration"),
description: L("Description:PhoneVerifyCodeExpiration"),
isVisibleToClients: true)
.WithProviders(
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
};
}
protected LocalizableString L(string name)
{
return LocalizableString.Create<AccountResource>(name);
}
}
}

23
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/AccountSettingNames.cs

@ -1,23 +0,0 @@
namespace LINGYUN.Abp.Account
{
public class AccountSettingNames
{
public const string GroupName = "Abp.Account";
/// <summary>
/// 短信验证码过期时间
/// </summary>
public const string PhoneVerifyCodeExpiration = GroupName + ".PhoneVerifyCodeExpiration";
/// <summary>
/// 用户注册短信验证码模板号
/// </summary>
public const string SmsRegisterTemplateCode = GroupName + ".SmsRegisterTemplateCode";
/// <summary>
/// 用户登录短信验证码模板号
/// </summary>
public const string SmsSigninTemplateCode = GroupName + ".SmsSigninTemplateCode";
/// <summary>
/// 用户重置密码短信验证码模板号
/// </summary>
public const string SmsResetPasswordTemplateCode = GroupName + ".SmsResetPasswordTemplateCode";
}
}

9
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/Localization/AccountResource.cs

@ -1,9 +0,0 @@
using Volo.Abp.Localization;
namespace LINGYUN.Abp.Account.Localization
{
[LocalizationResourceName("LINYUNAbpAccount")]
public class AccountResource
{
}
}

20
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/Localization/Resources/en.json

@ -1,20 +0,0 @@
{
"culture": "en",
"texts": {
"PhoneNumberNotRegisterd": "The registered mobile phone number is not registered!",
"PhoneVerifyCodeInvalid": "The phone verification code is invalid or expired!",
"PhoneVerifyCodeNotRepeatSend": "Phone verification code cannot be sent repeatedly within {0} minutes!",
"DisplayName:SmsRegisterTemplateCode": "Register sms template",
"Description:SmsRegisterTemplateCode": "When the user registers, he/she should send the template number of the SMS verification code and fill in the template number of the corresponding cloud platform registration",
"DisplayName:SmsSigninTemplateCode": "Signin sms template",
"Description:SmsSigninTemplateCode": "When the user logs in, he/she should send the template number of the SMS verification code and fill in the template number of the corresponding cloud platform registration",
"DisplayName:SmsResetPasswordTemplateCode": "Reset password sms template",
"Description:SmsResetPasswordTemplateCode": "When the user resets the password, he/she sends the template number of SMS verification code and fills in the template number registered on the cloud platform",
"DisplayName:PhoneVerifyCodeExpiration": "SMS verification code validity",
"Description:PhoneVerifyCodeExpiration": "The valid time for the user to send SMS verification code, unit m, default 3m",
"RequiredEmailAddress": "Email address required",
"InvalidPhoneNumber": "Invalid phone number",
"DuplicatePhoneNumber": "The phone number {0} has been registered!",
"DuplicateWeChat": "The wechat has been registered!"
}
}

20
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json

@ -1,20 +0,0 @@
{
"culture": "zh-Hans",
"texts": {
"PhoneNumberNotRegisterd": "手机号码未注册!",
"PhoneVerifyCodeInvalid": "手机验证码无效或已经过期!",
"PhoneVerifyCodeNotRepeatSend": "手机验证码不能在 {0} 分钟内重复发送!",
"DisplayName:SmsRegisterTemplateCode": "用户注册短信模板",
"Description:SmsRegisterTemplateCode": "用户注册时发送短信验证码的模板号,填写对应云平台注册的模板号",
"DisplayName:SmsSigninTemplateCode": "用户登录短信模板",
"Description:SmsSigninTemplateCode": "用户登录时发送短信验证码的模板号,填写对应云平台注册的模板号",
"DisplayName:SmsResetPasswordTemplateCode": "用户重置密码短信模板",
"Description:SmsResetPasswordTemplateCode": "用户重置密码时发送短信验证码的模板号,填写对应云平台注册的模板号",
"DisplayName:PhoneVerifyCodeExpiration": "短信验证码有效期",
"Description:PhoneVerifyCodeExpiration": "用户发送短信验证码的有效时长,单位m,默认3m",
"RequiredEmailAddress": "邮件地址必须输入",
"InvalidPhoneNumber": "手机号无效",
"DuplicatePhoneNumber": "手机号已经注册过!",
"DuplicateWeChat": "微信号已经注册过!"
}
}

2
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain/LINGYUN.Abp.Account.Domain.csproj

@ -12,7 +12,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.Account.Domain.Shared\LINGYUN.Abp.Account.Domain.Shared.csproj" />
<Folder Include="Microsoft\AspNetCore\Identity\" />
</ItemGroup>
</Project>

50
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain/Microsoft/AspNetCore/Identity/PhoneNumberUserValidator.cs

@ -1,50 +0,0 @@
using LINGYUN.Abp.Account.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using System;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using IIdentityUserRepository = LINGYUN.Abp.Account.IIdentityUserRepository;
namespace Microsoft.AspNetCore.Identity
{
[Dependency(ServiceLifetime.Scoped, ReplaceServices = true)]
[ExposeServices(typeof(IUserValidator<IdentityUser>))]
public class PhoneNumberUserValidator : UserValidator<IdentityUser>
{
private readonly IStringLocalizer<AccountResource> _stringLocalizer;
private readonly IIdentityUserRepository _identityUserRepository;
public PhoneNumberUserValidator(
IStringLocalizer<AccountResource> stringLocalizer,
IIdentityUserRepository identityUserRepository)
{
_stringLocalizer = stringLocalizer;
_identityUserRepository = identityUserRepository;
}
public override async Task<IdentityResult> ValidateAsync(UserManager<IdentityUser> manager, IdentityUser user)
{
await ValidatePhoneNumberAsync(manager, user);
return await base.ValidateAsync(manager, user);
}
protected virtual async Task ValidatePhoneNumberAsync(UserManager<IdentityUser> manager, IdentityUser user)
{
var phoneNumber = await manager.GetPhoneNumberAsync(user);
if (phoneNumber.IsNullOrWhiteSpace())
{
return;
// 如果用户没有手机号,不验证
//throw new UserFriendlyException(_stringLocalizer["InvalidPhoneNumber"].Value, "InvalidPhoneNumber");
}
var phoneNumberHasRegisted = await _identityUserRepository.PhoneNumberHasRegistedAsync(phoneNumber);
if (phoneNumberHasRegisted)
{
throw new UserFriendlyException(_stringLocalizer["DuplicatePhoneNumber", phoneNumber].Value, "DuplicatePhoneNumber");
}
}
}
}

2
aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN.Abp.Account.HttpApi.csproj

@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="3.3.0" />
<PackageReference Include="Volo.Abp.Account.HttpApi" Version="3.3.0" />
</ItemGroup>
<ItemGroup>

20
aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/AbpAccountHttpApiModule.cs

@ -1,31 +1,27 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Account.Localization;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Localization;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Account
{
[DependsOn(
typeof(AbpAccountApplicationContractsModule),
typeof(AbpAspNetCoreMvcModule))]
typeof(Volo.Abp.Account.AbpAccountHttpApiModule))]
public class AbpAccountHttpApiModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<IMvcBuilder>(mvcBuilder =>
PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
{
mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpAccountHttpApiModule).Assembly);
options.AddAssemblyResource(typeof(AccountResource), typeof(AbpAccountApplicationContractsModule).Assembly);
// 原生的在Web项目指定,不没有引用Web项目的情况下需要它
options.AddAssemblyResource(typeof(AccountResource), typeof(Volo.Abp.Account.AbpAccountApplicationContractsModule).Assembly);
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpLocalizationOptions>(options =>
PreConfigure<IMvcBuilder>(mvcBuilder =>
{
options.Resources
.Get<Localization.AccountResource>()
.AddBaseTypes(typeof(AccountResource));
mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpAccountHttpApiModule).Assembly);
});
}
}

37
aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/AccountController.cs

@ -3,7 +3,6 @@ using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Identity;
namespace LINGYUN.Abp.Account
{
@ -21,30 +20,44 @@ namespace LINGYUN.Abp.Account
[HttpPost]
[Route("wechat/register")]
public virtual async Task<IdentityUserDto> RegisterAsync(WeChatRegisterDto input)
public virtual async Task RegisterAsync(WeChatRegisterDto input)
{
return await AccountAppService.RegisterAsync(input);
await AccountAppService.RegisterAsync(input);
}
[HttpPost]
[Route("phone/register")]
public virtual async Task<IdentityUserDto> RegisterAsync(PhoneNumberRegisterDto input)
public virtual async Task RegisterAsync(PhoneRegisterDto input)
{
return await AccountAppService.RegisterAsync(input);
await AccountAppService.RegisterAsync(input);
}
[HttpPut]
[Route("phone/reset-password")]
public virtual async Task ResetPasswordAsync(PhoneResetPasswordDto input)
{
await AccountAppService.ResetPasswordAsync(input);
}
[HttpPost]
[Route("phone/verify")]
public virtual async Task VerifyPhoneNumberAsync(VerifyDto input)
[Route("phone/send-signin-code")]
public virtual async Task SendPhoneSigninCodeAsync(SendPhoneSigninCodeDto input)
{
await AccountAppService.VerifyPhoneNumberAsync(input);
await AccountAppService.SendPhoneSigninCodeAsync(input);
}
[HttpPut]
[Route("phone/reset-password")]
public virtual async Task ResetPasswordAsync(PasswordResetDto passwordReset)
[HttpPost]
[Route("phone/send-register-code")]
public virtual async Task SendPhoneRegisterCodeAsync(SendPhoneRegisterCodeDto input)
{
await AccountAppService.SendPhoneRegisterCodeAsync(input);
}
[HttpPost]
[Route("phone/send-password-reset-code")]
public virtual async Task SendPhoneResetPasswordCodeAsync(SendPhoneResetPasswordCodeDto input)
{
await AccountAppService.ResetPasswordAsync(passwordReset);
await AccountAppService.SendPhoneResetPasswordCodeAsync(input);
}
}
}

24
aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN.Abp.AspNetCore.Mvc.Validation.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<None Remove="LINGYUN\Abp\AspNetCore\Mvc\Validation\Localization\MissingFields\en.json" />
<None Remove="LINGYUN\Abp\AspNetCore\Mvc\Validation\Localization\MissingFields\zh-Hans.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="LINGYUN\Abp\AspNetCore\Mvc\Validation\Localization\MissingFields\en.json" />
<EmbeddedResource Include="LINGYUN\Abp\AspNetCore\Mvc\Validation\Localization\MissingFields\zh-Hans.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="3.3.0" />
</ItemGroup>
</Project>

31
aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN/Abp/AspNetCore/Mvc/Validation/AbpAspNetCoreMvcValidationModule.cs

@ -0,0 +1,31 @@
using System;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.Validation.Localization;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.AspNetCore.Mvc.Validation
{
[Obsolete("用于测试模型绑定与验证相关的类,无需引用")]
[DependsOn(
typeof(AbpAspNetCoreMvcModule))]
public class AbpAspNetCoreMvcValidationModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpAspNetCoreMvcValidationModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<AbpValidationResource>()
.AddVirtualJson("/LINGYUN/Abp/AspNetCore/Mvc/Validation/Localization/MissingFields");
});
}
}
}

563
aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN/Abp/AspNetCore/Mvc/Validation/AbpDataAnnotationsMetadataProvider.cs

@ -0,0 +1,563 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
namespace LINGYUN.Abp.AspNetCore.Mvc.Validation
{
public class AbpDataAnnotationsMetadataProvider :
IBindingMetadataProvider,
IDisplayMetadataProvider,
IValidationMetadataProvider
{
// The [Nullable] attribute is synthesized by the compiler. It's best to just compare the type name.
private const string NullableAttributeFullTypeName = "System.Runtime.CompilerServices.NullableAttribute";
private const string NullableFlagsFieldName = "NullableFlags";
private const string NullableContextAttributeFullName = "System.Runtime.CompilerServices.NullableContextAttribute";
private const string NullableContextFlagsFieldName = "Flag";
private readonly IStringLocalizerFactory _stringLocalizerFactory;
private readonly MvcOptions _options;
private readonly MvcDataAnnotationsLocalizationOptions _localizationOptions;
public AbpDataAnnotationsMetadataProvider(
MvcOptions options,
IOptions<MvcDataAnnotationsLocalizationOptions> localizationOptions,
IStringLocalizerFactory stringLocalizerFactory)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (localizationOptions == null)
{
throw new ArgumentNullException(nameof(localizationOptions));
}
_options = options;
_localizationOptions = localizationOptions.Value;
_stringLocalizerFactory = stringLocalizerFactory;
}
/// <inheritdoc />
public void CreateBindingMetadata(BindingMetadataProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var editableAttribute = context.Attributes.OfType<EditableAttribute>().FirstOrDefault();
if (editableAttribute != null)
{
context.BindingMetadata.IsReadOnly = !editableAttribute.AllowEdit;
}
}
/// <inheritdoc />
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var attributes = context.Attributes;
var dataTypeAttribute = attributes.OfType<DataTypeAttribute>().FirstOrDefault();
var displayAttribute = attributes.OfType<DisplayAttribute>().FirstOrDefault();
var displayColumnAttribute = attributes.OfType<DisplayColumnAttribute>().FirstOrDefault();
var displayFormatAttribute = attributes.OfType<DisplayFormatAttribute>().FirstOrDefault();
var displayNameAttribute = attributes.OfType<DisplayNameAttribute>().FirstOrDefault();
var hiddenInputAttribute = attributes.OfType<HiddenInputAttribute>().FirstOrDefault();
var scaffoldColumnAttribute = attributes.OfType<ScaffoldColumnAttribute>().FirstOrDefault();
var uiHintAttribute = attributes.OfType<UIHintAttribute>().FirstOrDefault();
// Special case the [DisplayFormat] attribute hanging off an applied [DataType] attribute. This property is
// non-null for DataType.Currency, DataType.Date, DataType.Time, and potentially custom [DataType]
// subclasses. The DataType.Currency, DataType.Date, and DataType.Time [DisplayFormat] attributes have a
// non-null DataFormatString and the DataType.Date and DataType.Time [DisplayFormat] attributes have
// ApplyFormatInEditMode==true.
if (displayFormatAttribute == null && dataTypeAttribute != null)
{
displayFormatAttribute = dataTypeAttribute.DisplayFormat;
}
var displayMetadata = context.DisplayMetadata;
// ConvertEmptyStringToNull
if (displayFormatAttribute != null)
{
displayMetadata.ConvertEmptyStringToNull = displayFormatAttribute.ConvertEmptyStringToNull;
}
// DataTypeName
if (dataTypeAttribute != null)
{
displayMetadata.DataTypeName = dataTypeAttribute.GetDataTypeName();
}
else if (displayFormatAttribute != null && !displayFormatAttribute.HtmlEncode)
{
displayMetadata.DataTypeName = nameof(DataType.Html);
}
var containerType = context.Key.ContainerType ?? context.Key.ModelType;
IStringLocalizer localizer = null;
if (_stringLocalizerFactory != null && _localizationOptions.DataAnnotationLocalizerProvider != null)
{
localizer = _localizationOptions.DataAnnotationLocalizerProvider(containerType, _stringLocalizerFactory);
}
// Description
if (displayAttribute != null)
{
if (localizer != null &&
!string.IsNullOrEmpty(displayAttribute.Description) &&
displayAttribute.ResourceType == null)
{
displayMetadata.Description = () => localizer[displayAttribute.Description];
}
else
{
displayMetadata.Description = () => displayAttribute.GetDescription();
}
}
// DisplayFormatString
if (displayFormatAttribute != null)
{
displayMetadata.DisplayFormatString = displayFormatAttribute.DataFormatString;
}
// DisplayName
// DisplayAttribute has precedence over DisplayNameAttribute.
if (displayAttribute?.GetName() != null)
{
if (localizer != null &&
!string.IsNullOrEmpty(displayAttribute.Name) &&
displayAttribute.ResourceType == null)
{
var displayName = localizer[displayAttribute.Name];
displayMetadata.DisplayName = () => displayName;
}
else
{
displayMetadata.DisplayName = () => displayAttribute.GetName();
}
}
else if (displayNameAttribute != null)
{
if (localizer != null &&
!string.IsNullOrEmpty(displayNameAttribute.DisplayName))
{
displayMetadata.DisplayName = () => localizer[displayNameAttribute.DisplayName];
}
else
{
displayMetadata.DisplayName = () => displayNameAttribute.DisplayName;
}
}
// EditFormatString
if (displayFormatAttribute != null && displayFormatAttribute.ApplyFormatInEditMode)
{
displayMetadata.EditFormatString = displayFormatAttribute.DataFormatString;
}
// IsEnum et cetera
var underlyingType = Nullable.GetUnderlyingType(context.Key.ModelType) ?? context.Key.ModelType;
var underlyingTypeInfo = underlyingType.GetTypeInfo();
if (underlyingTypeInfo.IsEnum)
{
// IsEnum
displayMetadata.IsEnum = true;
// IsFlagsEnum
displayMetadata.IsFlagsEnum = underlyingTypeInfo.IsDefined(typeof(FlagsAttribute), inherit: false);
// EnumDisplayNamesAndValues and EnumNamesAndValues
//
// Order EnumDisplayNamesAndValues by DisplayAttribute.Order, then by the order of Enum.GetNames().
// That method orders by absolute value, then its behavior is undefined (but hopefully stable).
// Add to EnumNamesAndValues in same order but Dictionary does not guarantee order will be preserved.
var groupedDisplayNamesAndValues = new List<KeyValuePair<EnumGroupAndName, string>>();
var namesAndValues = new Dictionary<string, string>();
IStringLocalizer enumLocalizer = null;
if (_stringLocalizerFactory != null && _localizationOptions.DataAnnotationLocalizerProvider != null)
{
enumLocalizer = _localizationOptions.DataAnnotationLocalizerProvider(underlyingType, _stringLocalizerFactory);
}
var enumFields = Enum.GetNames(underlyingType)
.Select(name => underlyingType.GetField(name))
.OrderBy(field => field.GetCustomAttribute<DisplayAttribute>(inherit: false)?.GetOrder() ?? 1000);
foreach (var field in enumFields)
{
var groupName = GetDisplayGroup(field);
var value = ((Enum)field.GetValue(obj: null)).ToString("d");
groupedDisplayNamesAndValues.Add(new KeyValuePair<EnumGroupAndName, string>(
new EnumGroupAndName(
groupName,
() => GetDisplayName(field, enumLocalizer)),
value));
namesAndValues.Add(field.Name, value);
}
displayMetadata.EnumGroupedDisplayNamesAndValues = groupedDisplayNamesAndValues;
displayMetadata.EnumNamesAndValues = namesAndValues;
}
// HasNonDefaultEditFormat
if (!string.IsNullOrEmpty(displayFormatAttribute?.DataFormatString) &&
displayFormatAttribute?.ApplyFormatInEditMode == true)
{
// Have a non-empty EditFormatString based on [DisplayFormat] from our cache.
if (dataTypeAttribute == null)
{
// Attributes include no [DataType]; [DisplayFormat] was applied directly.
displayMetadata.HasNonDefaultEditFormat = true;
}
else if (dataTypeAttribute.DisplayFormat != displayFormatAttribute)
{
// Attributes include separate [DataType] and [DisplayFormat]; [DisplayFormat] provided override.
displayMetadata.HasNonDefaultEditFormat = true;
}
else if (dataTypeAttribute.GetType() != typeof(DataTypeAttribute))
{
// Attributes include [DisplayFormat] copied from [DataType] and [DataType] was of a subclass.
// Assume the [DataType] constructor used the protected DisplayFormat setter to override its
// default. That is derived [DataType] provided override.
displayMetadata.HasNonDefaultEditFormat = true;
}
}
// HideSurroundingHtml
if (hiddenInputAttribute != null)
{
displayMetadata.HideSurroundingHtml = !hiddenInputAttribute.DisplayValue;
}
// HtmlEncode
if (displayFormatAttribute != null)
{
displayMetadata.HtmlEncode = displayFormatAttribute.HtmlEncode;
}
// NullDisplayText
if (displayFormatAttribute != null)
{
displayMetadata.NullDisplayText = displayFormatAttribute.NullDisplayText;
}
// Order
if (displayAttribute?.GetOrder() != null)
{
displayMetadata.Order = displayAttribute.GetOrder().Value;
}
// Placeholder
if (displayAttribute != null)
{
if (localizer != null &&
!string.IsNullOrEmpty(displayAttribute.Prompt) &&
displayAttribute.ResourceType == null)
{
displayMetadata.Placeholder = () => localizer[displayAttribute.Prompt];
}
else
{
displayMetadata.Placeholder = () => displayAttribute.GetPrompt();
}
}
// ShowForDisplay
if (scaffoldColumnAttribute != null)
{
displayMetadata.ShowForDisplay = scaffoldColumnAttribute.Scaffold;
}
// ShowForEdit
if (scaffoldColumnAttribute != null)
{
displayMetadata.ShowForEdit = scaffoldColumnAttribute.Scaffold;
}
// SimpleDisplayProperty
if (displayColumnAttribute != null)
{
displayMetadata.SimpleDisplayProperty = displayColumnAttribute.DisplayColumn;
}
// TemplateHint
if (uiHintAttribute != null)
{
displayMetadata.TemplateHint = uiHintAttribute.UIHint;
}
else if (hiddenInputAttribute != null)
{
displayMetadata.TemplateHint = "HiddenInput";
}
}
/// <inheritdoc />
public void CreateValidationMetadata(ValidationMetadataProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// Read interface .Count once rather than per iteration
var contextAttributes = context.Attributes;
var contextAttributesCount = contextAttributes.Count;
var attributes = new List<object>(contextAttributesCount);
for (var i = 0; i < contextAttributesCount; i++)
{
var attribute = contextAttributes[i];
if (attribute is ValidationProviderAttribute validationProviderAttribute)
{
attributes.AddRange(validationProviderAttribute.GetValidationAttributes());
}
else
{
attributes.Add(attribute);
}
}
// RequiredAttribute marks a property as required by validation - this means that it
// must have a non-null value on the model during validation.
var requiredAttribute = attributes.OfType<RequiredAttribute>().FirstOrDefault();
// For non-nullable reference types, treat them as-if they had an implicit [Required].
// This allows the developer to specify [Required] to customize the error message, so
// if they already have [Required] then there's no need for us to do this check.
if (!_options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes &&
requiredAttribute == null &&
!context.Key.ModelType.IsValueType &&
context.Key.MetadataKind != ModelMetadataKind.Type)
{
var addInferredRequiredAttribute = false;
if (context.Key.MetadataKind == ModelMetadataKind.Type)
{
// Do nothing.
}
else if (context.Key.MetadataKind == ModelMetadataKind.Property)
{
var property = context.Key.PropertyInfo;
if (property is null)
{
// PropertyInfo was unavailable on ModelIdentity prior to 3.1.
// Making a cogent argument about the nullability of the property requires inspecting the declared type,
// since looking at the runtime type may result in false positives: https://github.com/dotnet/aspnetcore/issues/14812
// The only way we could arrive here is if the ModelMetadata was constructed using the non-default provider.
// We'll cursorily examine the attributes on the property, but not the ContainerType to make a decision about it's nullability.
if (HasNullableAttribute(context.PropertyAttributes, out var propertyHasNullableAttribute))
{
addInferredRequiredAttribute = propertyHasNullableAttribute;
}
}
else
{
addInferredRequiredAttribute = IsNullableReferenceType(
property.DeclaringType,
member: null,
context.PropertyAttributes);
}
}
else if (context.Key.MetadataKind == ModelMetadataKind.Parameter)
{
addInferredRequiredAttribute = IsNullableReferenceType(
context.Key.ParameterInfo?.Member.ReflectedType,
context.Key.ParameterInfo.Member,
context.ParameterAttributes);
}
else
{
throw new InvalidOperationException("Unsupported ModelMetadataKind: " + context.Key.MetadataKind);
}
if (addInferredRequiredAttribute)
{
// Since this behavior specifically relates to non-null-ness, we will use the non-default
// option to tolerate empty/whitespace strings. empty/whitespace INPUT will still result in
// a validation error by default because we convert empty/whitespace strings to null
// unless you say otherwise.
requiredAttribute = new RequiredAttribute()
{
AllowEmptyStrings = true,
};
attributes.Add(requiredAttribute);
}
}
if (requiredAttribute != null)
{
context.ValidationMetadata.IsRequired = true;
}
foreach (var attribute in attributes.OfType<ValidationAttribute>())
{
// If another provider has already added this attribute, do not repeat it.
// This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and
// IClientModelValidator) to be added to the ValidationMetadata twice.
// This is to ensure we do not end up with duplication validation rules on the client side.
if (!context.ValidationMetadata.ValidatorMetadata.Contains(attribute))
{
context.ValidationMetadata.ValidatorMetadata.Add(attribute);
}
}
}
private static string GetDisplayName(FieldInfo field, IStringLocalizer stringLocalizer)
{
var display = field.GetCustomAttribute<DisplayAttribute>(inherit: false);
if (display != null)
{
// Note [Display(Name = "")] is allowed but we will not attempt to localize the empty name.
var name = display.GetName();
if (stringLocalizer != null && !string.IsNullOrEmpty(name) && display.ResourceType == null)
{
name = stringLocalizer[name];
}
return name ?? field.Name;
}
return field.Name;
}
// Return non-empty group specified in a [Display] attribute for a field, if any; string.Empty otherwise.
private static string GetDisplayGroup(FieldInfo field)
{
var display = field.GetCustomAttribute<DisplayAttribute>(inherit: false);
if (display != null)
{
// Note [Display(Group = "")] is allowed.
var group = display.GetGroupName();
if (group != null)
{
return group;
}
}
return string.Empty;
}
internal static bool IsNullableReferenceType(Type containingType, MemberInfo member, IEnumerable<object> attributes)
{
if (HasNullableAttribute(attributes, out var result))
{
return result;
}
return IsNullableBasedOnContext(containingType, member);
}
// Internal for testing
internal static bool HasNullableAttribute(IEnumerable<object> attributes, out bool isNullable)
{
// [Nullable] is compiler synthesized, comparing by name.
var nullableAttribute = attributes
.FirstOrDefault(a => string.Equals(a.GetType().FullName, NullableAttributeFullTypeName, StringComparison.Ordinal));
if (nullableAttribute == null)
{
isNullable = false;
return false; // [Nullable] not found
}
// We don't handle cases where generics and NNRT are used. This runs into a
// fundamental limitation of ModelMetadata - we use a single Type and Property/Parameter
// to look up the metadata. However when generics are involved and NNRT is in use
// the distance between the [Nullable] and member we're looking at is potentially
// unbounded.
//
// See: https://github.com/dotnet/roslyn/blob/master/docs/features/nullable-reference-types.md#annotations
if (nullableAttribute.GetType().GetField(NullableFlagsFieldName) is FieldInfo field &&
field.GetValue(nullableAttribute) is byte[] flags &&
flags.Length > 0 &&
flags[0] == 1) // First element is the property/parameter type.
{
isNullable = true;
return true; // [Nullable] found and type is an NNRT
}
isNullable = false;
return true; // [Nullable] found but type is not an NNRT
}
internal static bool IsNullableBasedOnContext(Type containingType, MemberInfo member)
{
// For generic types, inspecting the nullability requirement additionally requires
// inspecting the nullability constraint on generic type parameters. This is fairly non-triviial
// so we'll just avoid calculating it. Users should still be able to apply an explicit [Required]
// attribute on these members.
if (containingType.IsGenericType)
{
return false;
}
// The [Nullable] and [NullableContext] attributes are not inherited.
//
// The [NullableContext] attribute can appear on a method or on the module.
var attributes = member?.GetCustomAttributes(inherit: false) ?? Array.Empty<object>();
var isNullable = AttributesHasNullableContext(attributes);
if (isNullable != null)
{
return isNullable.Value;
}
// Check on the containing type
var type = containingType;
do
{
attributes = type.GetCustomAttributes(inherit: false);
isNullable = AttributesHasNullableContext(attributes);
if (isNullable != null)
{
return isNullable.Value;
}
type = type.DeclaringType;
}
while (type != null);
// If we don't find the attribute on the declaring type then repeat at the module level
attributes = containingType.Module.GetCustomAttributes(inherit: false);
isNullable = AttributesHasNullableContext(attributes);
return isNullable ?? false;
bool? AttributesHasNullableContext(object[] attributes)
{
var nullableContextAttribute = attributes
.FirstOrDefault(a => string.Equals(a.GetType().FullName, NullableContextAttributeFullName, StringComparison.Ordinal));
if (nullableContextAttribute != null)
{
if (nullableContextAttribute.GetType().GetField(NullableContextFlagsFieldName) is FieldInfo field &&
field.GetValue(nullableContextAttribute) is byte @byte)
{
return @byte == 1; // [NullableContext] found
}
}
return null;
}
}
}
}

69
aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN/Abp/AspNetCore/Mvc/Validation/AbpLocalizerModelMetadataProvider.cs

@ -0,0 +1,69 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using Volo.Abp.AspNetCore.Mvc.Validation;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Validation.Localization;
namespace LINGYUN.Abp.AspNetCore.Mvc.Validation
{
//[Dependency(ServiceLifetime.Singleton, ReplaceServices = true)]
//[ExposeServices(typeof(IModelMetadataProvider))]
public class AbpLocalizerModelMetadataProvider : DefaultModelMetadataProvider
{
protected IStringLocalizer StringLocalizer { get; }
public AbpLocalizerModelMetadataProvider(
ICompositeMetadataDetailsProvider detailsProvider,
IStringLocalizer<AbpValidationResource> stringLocalizer)
: base(detailsProvider)
{
StringLocalizer = stringLocalizer;
}
public AbpLocalizerModelMetadataProvider(
IStringLocalizer<AbpValidationResource> stringLocalizer,
ICompositeMetadataDetailsProvider detailsProvider,
IOptions<MvcOptions> optionsAccessor)
: base(detailsProvider, optionsAccessor)
{
StringLocalizer = stringLocalizer;
}
protected override DefaultMetadataDetails[] CreatePropertyDetails(ModelMetadataIdentity key)
{
var details = base.CreatePropertyDetails(key);
foreach (var detail in details)
{
NormalizeMetadataDetail(detail);
}
return details;
}
private void NormalizeMetadataDetail(DefaultMetadataDetails detail)
{
foreach (var validationAttribute in detail.ModelAttributes.Attributes.OfType<ValidationAttribute>())
{
NormalizeValidationAttrbute(validationAttribute, detail.DisplayMetadata?.DisplayFormatString ?? detail.Key.Name);
}
}
protected virtual void NormalizeValidationAttrbute(ValidationAttribute validationAttribute, string displayName)
{
if (validationAttribute.ErrorMessage == null)
{
if (validationAttribute is RequiredAttribute required)
{
validationAttribute.ErrorMessage = StringLocalizer["The field {0} is invalid.", displayName];
}
}
}
}
}

69
aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN/Abp/AspNetCore/Mvc/Validation/DataAnnotationAutoLocalizationMetadataDetailsProvider.cs

@ -0,0 +1,69 @@
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace LINGYUN.Abp.AspNetCore.Mvc.Validation
{
public class DataAnnotationAutoLocalizationMetadataDetailsProvider : IDisplayMetadataProvider
{
private const string PropertyLocalizationKeyPrefix = "DisplayName:";
private readonly Lazy<IStringLocalizerFactory> _stringLocalizerFactory;
private readonly Lazy<IOptions<MvcDataAnnotationsLocalizationOptions>> _localizationOptions;
public DataAnnotationAutoLocalizationMetadataDetailsProvider(IServiceCollection services)
{
_stringLocalizerFactory = services.GetRequiredServiceLazy<IStringLocalizerFactory>();
_localizationOptions = services.GetRequiredServiceLazy<IOptions<MvcDataAnnotationsLocalizationOptions>>();
}
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
{
var displayMetadata = context.DisplayMetadata;
if (displayMetadata.DisplayName != null)
{
var displayName = displayMetadata.DisplayName();
return;
}
var attributes = context.Attributes;
if (attributes.OfType<DisplayAttribute>().Any() ||
attributes.OfType<DisplayNameAttribute>().Any())
{
return;
}
if (context.Key.Name.IsNullOrWhiteSpace())
{
return;
}
if (_localizationOptions.Value.Value.DataAnnotationLocalizerProvider == null)
{
return;
}
var containerType = context.Key.ContainerType ?? context.Key.ModelType;
var localizer = _localizationOptions.Value.Value.DataAnnotationLocalizerProvider(containerType, _stringLocalizerFactory.Value);
displayMetadata.DisplayName = () =>
{
var localizedString = localizer[PropertyLocalizationKeyPrefix + context.Key.Name];
if (localizedString.ResourceNotFound)
{
localizedString = localizer[context.Key.Name];
}
return localizedString;
};
}
}
}

7
aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN/Abp/AspNetCore/Mvc/Validation/Localization/MissingFields/en.json

@ -0,0 +1,7 @@
{
"culture": "en",
"texts": {
"ThisFieldMustBeANumber": "The field must be a number!",
"The field {0} must be a number.": "The supplied value {0} must be a number!"
}
}

7
aspnet-core/modules/common/LINGYUN.Abp.AspNetCore.Mvc.Validation/LINGYUN/Abp/AspNetCore/Mvc/Validation/Localization/MissingFields/zh-Hans.json

@ -0,0 +1,7 @@
{
"culture": "zh-Hans",
"texts": {
"ThisFieldMustBeANumber": "字段必须是数字!",
"The field {0} must be a number.": "提供的值 {0} 必须是数字!"
}
}

7
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/ChangeEmailAddressInput.cs → aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/ChangeEmailAddressDto.cs

@ -5,20 +5,23 @@ using Volo.Abp.Validation;
namespace LINGYUN.Abp.Identity
{
public class ChangeEmailAddressInput
public class ChangeEmailAddressDto
{
/// <summary>
/// 新邮件地址
/// </summary>
[Required]
[EmailAddress]
[Display(Name = "EmailAddress")]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))]
public string NewEmailAddress { get; set; }
/// <summary>
/// 安全验证码
/// </summary>
[Required]
[DisableAuditing]
[StringLength(6)]
[StringLength(6, MinimumLength = 6)]
[Display(Name = "EmailVerifyCode")]
public string Code { get; set; }
}
}

7
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/ChangePhoneNumberInput.cs → aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/ChangePhoneNumberDto.cs

@ -5,7 +5,7 @@ using Volo.Abp.Validation;
namespace LINGYUN.Abp.Identity
{
public class ChangePhoneNumberInput
public class ChangePhoneNumberDto
{
/// <summary>
/// 新手机号
@ -13,12 +13,15 @@ namespace LINGYUN.Abp.Identity
[Required]
[Phone]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))]
[Display(Name = "PhoneNumber")]
public string NewPhoneNumber { get; set; }
/// <summary>
/// 安全验证码
/// </summary>
[Required]
[DisableAuditing]
[StringLength(6)]
[StringLength(6, MinimumLength = 6)]
[Display(Name = "SmsVerifyCode")]
public string Code { get; set; }
}
}

2
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/IdentityUserTwoFactorEnabledDto.cs → aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/ChangeTwoFactorEnabledDto.cs

@ -1,6 +1,6 @@
namespace LINGYUN.Abp.Identity
{
public class IdentityUserTwoFactorEnabledDto
public class ChangeTwoFactorEnabledDto
{
public bool Enabled { get; set; }
}

18
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/SendChangeEmailAddressCodeDto.cs

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Identity;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.Identity
{
public class SendChangeEmailAddressCodeDto
{
/// <summary>
/// 新邮件地址
/// </summary>
[Required]
[EmailAddress]
[Display(Name = "EmailAddress")]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))]
public string NewEmailAddress { get; set; }
}
}

18
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/Dto/SendChangePhoneNumberCodeDto.cs

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Identity;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.Identity
{
public class SendChangePhoneNumberCodeDto
{
/// <summary>
/// 新手机号
/// </summary>
[Required]
[Phone]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))]
[Display(Name = "PhoneNumber")]
public string NewPhoneNumber { get; set; }
}
}

2
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/IIdentityUserAppService.cs

@ -37,7 +37,7 @@ namespace LINGYUN.Abp.Identity
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
Task ChangeTwoFactorEnabledAsync(Guid id, IdentityUserTwoFactorEnabledDto input);
Task ChangeTwoFactorEnabledAsync(Guid id, ChangeTwoFactorEnabledDto input);
/// <summary>
/// 变更用户密码
/// </summary>

19
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application.Contracts/LINGYUN/Abp/Identity/IMyProfileAppService.cs

@ -6,10 +6,25 @@ namespace LINGYUN.Abp.Identity
public interface IMyProfileAppService : IApplicationService
{
/// <summary>
/// 变更双因素验
/// 改变二次认
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task ChangeTwoFactorEnabledAsync(IdentityUserTwoFactorEnabledDto input);
Task ChangeTwoFactorEnabledAsync(ChangeTwoFactorEnabledDto input);
/// <summary>
/// 发送改变手机号验证码
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task SendChangePhoneNumberCodeAsync(SendChangePhoneNumberCodeDto input);
/// <summary>
/// 改变手机绑定
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <remarks>
/// 需二次认证,主要是为了无法用到重定向页面修改相关信息的地方(点名微信小程序)
/// </remarks>
Task ChangePhoneNumberAsync(ChangePhoneNumberDto input);
}
}

2
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application/LINGYUN/Abp/Identity/IdentityUserAppService.cs

@ -124,7 +124,7 @@ namespace LINGYUN.Abp.Identity
}
[Authorize(Volo.Abp.Identity.IdentityPermissions.Users.Update)]
public virtual async Task ChangeTwoFactorEnabledAsync(Guid id, IdentityUserTwoFactorEnabledDto input)
public virtual async Task ChangeTwoFactorEnabledAsync(Guid id, ChangeTwoFactorEnabledDto input)
{
var user = await UserManager.GetByIdAsync(id);

69
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application/LINGYUN/Abp/Identity/MyProfileAppService.cs

@ -1,7 +1,13 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.Identity;
using Volo.Abp.Identity.Settings;
using Volo.Abp.Settings;
using Volo.Abp.Users;
namespace LINGYUN.Abp.Identity
@ -9,24 +15,83 @@ namespace LINGYUN.Abp.Identity
[Authorize]
public class MyProfileAppService : IdentityAppServiceBase, IMyProfileAppService
{
protected IDistributedCache<SmsSecurityTokenCacheItem> SecurityTokenCache { get; }
protected IUserSecurityCodeSender SecurityCodeSender { get; }
protected IdentityUserManager UserManager { get; }
protected IIdentityUserRepository UserRepository { get; }
public MyProfileAppService(
IdentityUserManager userManager,
IIdentityUserRepository userRepository)
IIdentityUserRepository userRepository,
IUserSecurityCodeSender securityCodeSender,
IDistributedCache<SmsSecurityTokenCacheItem> securityTokenCache)
{
UserManager = userManager;
UserRepository = userRepository;
SecurityCodeSender = securityCodeSender;
SecurityTokenCache = SecurityTokenCache;
}
public virtual async Task ChangeTwoFactorEnabledAsync(IdentityUserTwoFactorEnabledDto input)
public virtual async Task ChangeTwoFactorEnabledAsync(ChangeTwoFactorEnabledDto input)
{
if (!await SettingProvider.IsTrueAsync(IdentitySettingNames.TwoFactor.UsersCanChange))
{
throw new BusinessException(Volo.Abp.Identity.IdentityErrorCodes.CanNotChangeTwoFactor);
}
var user = await UserManager.GetByIdAsync(CurrentUser.GetId());
(await UserManager.SetTwoFactorEnabledWithAccountConfirmedAsync(user, input.Enabled)).CheckErrors();
await CurrentUnitOfWork.SaveChangesAsync();
}
public virtual async Task SendChangePhoneNumberCodeAsync(SendChangePhoneNumberCodeDto input)
{
var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.NewPhoneNumber, "SmsChangePhoneNumber");
var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey);
var interval = await SettingProvider.GetAsync(Settings.IdentitySettingNames.User.SmsRepetInterval, 1);
if (securityTokenCacheItem != null)
{
throw new UserFriendlyException(L["SendRepeatPhoneVerifyCode", interval]);
}
// 是否已有用户使用手机号绑定
if (await UserRepository.IsPhoneNumberConfirmedAsync(input.NewPhoneNumber))
{
throw new BusinessException(IdentityErrorCodes.DuplicatePhoneNumber);
}
var user = await UserManager.GetByIdAsync(CurrentUser.GetId());
var template = await SettingProvider.GetOrNullAsync(Settings.IdentitySettingNames.User.SmsPhoneNumberConfirmed);
var token = await UserManager.GenerateChangePhoneNumberTokenAsync(user, input.NewPhoneNumber);
// 发送验证码
await SecurityCodeSender.SendPhoneConfirmedCodeAsync(input.NewPhoneNumber, token, template);
securityTokenCacheItem = new SmsSecurityTokenCacheItem(token, user.ConcurrencyStamp);
await SecurityTokenCache
.SetAsync(securityTokenCacheKey, securityTokenCacheItem,
new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval)
});
}
public virtual async Task ChangePhoneNumberAsync(ChangePhoneNumberDto input)
{
// 是否已有用户使用手机号绑定
if (await UserRepository.IsPhoneNumberConfirmedAsync(input.NewPhoneNumber))
{
throw new BusinessException(IdentityErrorCodes.DuplicatePhoneNumber);
}
//TODO: 可以查询缓存用 securityTokenCacheItem.SecurityToken 与 user.SecurityStamp 作对比
var user = await UserManager.GetByIdAsync(CurrentUser.GetId());
// 更换手机号
(await UserManager.ChangePhoneNumberAsync(user, input.NewPhoneNumber, input.Code)).CheckErrors();
await CurrentUnitOfWork.SaveChangesAsync();
var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.NewPhoneNumber, "SmsChangePhoneNumber");
await SecurityTokenCache.RemoveAsync(securityTokenCacheKey);
}
}
}

20
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/IUserSecurityCodeSender.cs

@ -0,0 +1,20 @@
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Identity
{
public interface IUserSecurityCodeSender
{
Task SendPhoneConfirmedCodeAsync(
string phone,
string token,
string template, // 传递模板号
CancellationToken cancellation = default);
Task SendEmailConfirmedCodeAsync(
string userName,
string email,
string token,
CancellationToken cancellation = default);
}
}

8
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/IdentityErrorCodes.cs

@ -14,5 +14,13 @@
/// 手机号码已被使用
/// </summary>
public const string DuplicatePhoneNumber = "Volo.Abp.Identity:020007";
/// <summary>
/// 你不能修改你的手机绑定信息
/// </summary>
public const string UsersCanNotChangePhoneNumber = "Volo.Abp.Identity:020008";
/// <summary>
/// 你不能修改你的邮件绑定信息
/// </summary>
public const string UsersCanNotChangeEmailAddress = "Volo.Abp.Identity:020009";
}
}

20
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/en.json

@ -14,6 +14,7 @@
"OrganizationUnit:AddChildren": "Add Children",
"OrganizationUnit:AddMember": "Add Member",
"OrganizationUnit:AddRole": "Add Role",
"OrganizationUnit:DisplayName": "Display Name",
"OrganizationUnit:WillDelete": "Organization: {0} will be deleted",
"OrganizationUnit:AreYouSureRemoveUser": "Are you sure you want to delete user {0} from your organization?",
"OrganizationUnit:AreYouSureRemoveRole": "Are you sure you want to remove the role {0} from the organization?",
@ -41,6 +42,23 @@
"IdentityClaim:ValueType": "Value type",
"Volo.Abp.Identity:020005": "The static claim type cannot be changed!",
"Volo.Abp.Identity:020006": "Unable to delete static claim type!",
"Volo.Abp.Identity:020007": "The phone number is already tied to another user!"
"Volo.Abp.Identity:020007": "The phone number is already tied to another user!",
"Volo.Abp.Identity:020008": "You can't modify your phone's binding information!",
"Volo.Abp.Identity:020009": "You cannot modify your email binding information!",
"Volo.Abp.Identity:DuplicatePhoneNumber": "Phone number '{0}' is already taken.",
"DisplayName:Abp.Identity.User.SmsNewUserRegister": "Register sms template",
"Description:Abp.Identity.User.SmsNewUserRegister": "When the user registers, he/she should send the template number of the SMS verification code and fill in the template number of the corresponding cloud platform registration",
"DisplayName:Abp.Identity.User.SmsUserSignin": "Signin sms template",
"Description:Abp.Identity.User.SmsUserSignin": "When the user logs in, he/she should send the template number of the SMS verification code and fill in the template number of the corresponding cloud platform registration",
"DisplayName:Abp.Identity.User.SmsResetPassword": "Reset password sms template",
"Description:Abp.Identity.User.SmsResetPassword": "When the user resets the password, he/she sends the template number of SMS verification code and fills in the template number registered on the cloud platform",
"DisplayName:Abp.Identity.User.SmsPhoneNumberConfirmed": "Phone number confirmation template",
"Description:Abp.Identity.User.SmsPhoneNumberConfirmed": "The user confirms the mobile phone verification code template",
"DisplayName:Abp.Identity.User.SmsRepetInterval": "SMS verification code validity(min)",
"Description:Abp.Identity.User.SmsRepetInterval": "The valid time for the user to send SMS verification code, unit m, default 3min",
"DisplayName:SmsVerifyCode": "SMS verification code",
"DisplayName:EmailVerifyCode": "Mail verification code",
"DisplayName:WeChatCode": "Wechat login code",
"SendRepeatSmsVerifyCode": "Phone verification code cannot be sent repeatedly within {0} minutes!"
}
}

19
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/zh-Hans.json

@ -42,6 +42,23 @@
"IdentityClaim:ValueType": "值类型",
"Volo.Abp.Identity:020005": "无法变更静态声明类型!",
"Volo.Abp.Identity:020006": "无法删除静态声明类型!",
"Volo.Abp.Identity:020007": "手机号码已被其他用户绑定!"
"Volo.Abp.Identity:020007": "手机号码已被其他用户绑定!",
"Volo.Abp.Identity:020008": "你不能修改你的手机绑定信息!",
"Volo.Abp.Identity:020009": "你不能修改你的邮件绑定信息!",
"Volo.Abp.Identity:DuplicatePhoneNumber": "手机号 '{0}' 已存在.",
"DisplayName:Abp.Identity.User.SmsNewUserRegister": "新用户注册模板",
"Description:Abp.Identity.User.SmsNewUserRegister": "新用户通过手机注册账号验证码模板",
"DisplayName:Abp.Identity.User.SmsUserSignin": "用户登录模板",
"Description:Abp.Identity.User.SmsUserSignin": "用户通过手机登录验证码模板",
"DisplayName:Abp.Identity.User.SmsResetPassword": "密码找回模板",
"Description:Abp.Identity.User.SmsResetPassword": "用户通过手机找回密码验证码模板",
"DisplayName:Abp.Identity.User.SmsPhoneNumberConfirmed": "手机确认模板",
"Description:Abp.Identity.User.SmsPhoneNumberConfirmed": "用户确认手机号验证码模板",
"DisplayName:Abp.Identity.User.SmsRepetInterval": "重复发送间隔时间(min)",
"Description:Abp.Identity.User.SmsRepetInterval": "验证码重复发送的最小时间差,单位为分",
"DisplayName:SmsVerifyCode": "短信验证码",
"DisplayName:EmailVerifyCode": "邮件验证码",
"DisplayName:WeChatCode": "微信登录凭证",
"SendRepeatSmsVerifyCode": "手机验证码不能在 {0} 分钟内重复发送!"
}
}

121
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Security/DefaultTotpService.cs

@ -0,0 +1,121 @@
using System;
using System.Diagnostics;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.Identity.Security
{
/// <summary>
/// 微软的实现
/// See: Microsoft.AspNetCore.Identity.Rfc6238AuthenticationService
/// </summary>
internal class DefaultTotpService : ITotpService, ISingletonDependency
{
private static readonly TimeSpan _timestep = TimeSpan.FromMinutes(3);
private static readonly Encoding _encoding = new UTF8Encoding(false, true);
#if NETSTANDARD2_0
private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
#endif
// Generates a new 80-bit security token
public static byte[] GenerateRandomKey()
{
byte[] bytes = new byte[20];
#if NETSTANDARD2_0
_rng.GetBytes(bytes);
#else
RandomNumberGenerator.Fill(bytes);
#endif
return bytes;
}
internal static int ComputeTotp(HashAlgorithm hashAlgorithm, ulong timestepNumber, string modifier)
{
// # of 0's = length of pin
const int Mod = 1000000;
// See https://tools.ietf.org/html/rfc4226
// We can add an optional modifier
var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber));
var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifier));
// Generate DT string
var offset = hash[hash.Length - 1] & 0xf;
Debug.Assert(offset + 4 < hash.Length);
var binaryCode = (hash[offset] & 0x7f) << 24
| (hash[offset + 1] & 0xff) << 16
| (hash[offset + 2] & 0xff) << 8
| (hash[offset + 3] & 0xff);
return binaryCode % Mod;
}
private static byte[] ApplyModifier(byte[] input, string modifier)
{
if (String.IsNullOrEmpty(modifier))
{
return input;
}
var modifierBytes = _encoding.GetBytes(modifier);
var combined = new byte[checked(input.Length + modifierBytes.Length)];
Buffer.BlockCopy(input, 0, combined, 0, input.Length);
Buffer.BlockCopy(modifierBytes, 0, combined, input.Length, modifierBytes.Length);
return combined;
}
// More info: https://tools.ietf.org/html/rfc6238#section-4
private static ulong GetCurrentTimeStepNumber()
{
#if NETSTANDARD2_0
var delta = DateTime.UtcNow - _unixEpoch;
#else
var delta = DateTimeOffset.UtcNow - DateTimeOffset.UnixEpoch;
#endif
return (ulong)(delta.Ticks / _timestep.Ticks);
}
public int GenerateCode(byte[] securityToken, string modifier = null)
{
if (securityToken == null)
{
throw new ArgumentNullException(nameof(securityToken));
}
// Allow a variance of no greater than 9 minutes in either direction
var currentTimeStep = GetCurrentTimeStepNumber();
using (var hashAlgorithm = new HMACSHA1(securityToken))
{
return ComputeTotp(hashAlgorithm, currentTimeStep, modifier);
}
}
public bool ValidateCode(byte[] securityToken, int code, string modifier = null)
{
if (securityToken == null)
{
throw new ArgumentNullException(nameof(securityToken));
}
// Allow a variance of no greater than 9 minutes in either direction
var currentTimeStep = GetCurrentTimeStepNumber();
using (var hashAlgorithm = new HMACSHA1(securityToken))
{
for (var i = -2; i <= 2; i++)
{
var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep + i), modifier);
if (computedTotp == code)
{
return true;
}
}
}
// No match
return false;
}
}
}

12
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Security/ITotpService.cs

@ -0,0 +1,12 @@
namespace LINGYUN.Abp.Identity.Security
{
/// <summary>
/// totp算法服务
/// </summary>
public interface ITotpService
{
int GenerateCode(byte[] securityToken, string modifier = null);
bool ValidateCode(byte[] securityToken, int code, string modifier = null);
}
}

52
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Settings/IdentitySettingDefinitionProvider.cs

@ -8,6 +8,58 @@ namespace LINGYUN.Abp.Identity.Settings
{
public override void Define(ISettingDefinitionContext context)
{
context.Add(
new SettingDefinition(
name: IdentitySettingNames.User.SmsNewUserRegister,
defaultValue: "",
displayName: L("DisplayName:Abp.Identity.User.SmsNewUserRegister"),
description: L("Description:Abp.Identity.User.SmsNewUserRegister"),
isVisibleToClients: true)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
name: IdentitySettingNames.User.SmsUserSignin,
defaultValue: "",
displayName: L("DisplayName:Abp.Identity.User.SmsUserSignin"),
description: L("Description:Abp.Identity.User.SmsUserSignin"),
isVisibleToClients: true)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
name: IdentitySettingNames.User.SmsResetPassword,
defaultValue: "",
displayName: L("DisplayName:Abp.Identity.User.SmsResetPassword"),
description: L("Description:Abp.Identity.User.SmsResetPassword"),
isVisibleToClients: true)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
name: IdentitySettingNames.User.SmsPhoneNumberConfirmed,
defaultValue: "",
displayName: L("DisplayName:Abp.Identity.User.SmsPhoneNumberConfirmed"),
description: L("Description:Abp.Identity.User.SmsPhoneNumberConfirmed"),
isVisibleToClients: true)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
name: IdentitySettingNames.User.SmsRepetInterval,
defaultValue: "5",
displayName: L("DisplayName:Abp.Identity.User.SmsRepetInterval"),
description: L("Description:Abp.Identity.User.SmsRepetInterval"),
isVisibleToClients: true)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName)
);
}
private static LocalizableString L(string name)

25
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Settings/IdentitySettingNames.cs

@ -2,5 +2,30 @@
{
public static class IdentitySettingNames
{
private const string Prefix = "Abp.Identity";
public static class User
{
private const string UserPrefix = Prefix + ".User";
/// <summary>
/// 用户手机验证短信模板
/// </summary>
public const string SmsPhoneNumberConfirmed = UserPrefix + ".SmsPhoneNumberConfirmed";
/// <summary>
/// 用户注册短信验证码模板号
/// </summary>
public const string SmsNewUserRegister = UserPrefix + ".SmsNewUserRegister";
/// <summary>
/// 用户登录短信验证码模板号
/// </summary>
public const string SmsUserSignin = UserPrefix + ".SmsUserSignin";
/// <summary>
/// 用户重置密码短信验证码模板号
/// </summary>
public const string SmsResetPassword = UserPrefix + ".SmsResetPassword";
/// <summary>
/// 验证码重复间隔时间
/// </summary>
public const string SmsRepetInterval = UserPrefix + ".SmsRepetInterval";
}
}
}

68
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IIdentityUserRepository.cs

@ -8,21 +8,83 @@ namespace LINGYUN.Abp.Identity
{
public interface IIdentityUserRepository : Volo.Abp.Identity.IIdentityUserRepository
{
/// <summary>
/// 手机号是否已被使用
/// </summary>
/// <param name="phoneNumber"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<bool> IsPhoneNumberUedAsync(
string phoneNumber,
CancellationToken cancellationToken = default);
/// <summary>
/// 手机号是否已确认(绑定)
/// </summary>
/// <param name="phoneNumber"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<bool> IsPhoneNumberConfirmedAsync(
string phoneNumber,
CancellationToken cancellationToken = default);
/// <summary>
/// 邮件地址是否已确认(绑定)
/// </summary>
/// <param name="normalizedEmail"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<bool> IsNormalizedEmailConfirmedAsync(
string normalizedEmail,
CancellationToken cancellationToken = default);
/// <summary>
/// 通过手机号查询用户
/// </summary>
/// <param name="phoneNumber">手机号码</param>
/// <param name="isConfirmed">是否已确认过</param>
/// <param name="includeDetails"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IdentityUser> FindByPhoneNumberAsync(
string phoneNumber,
bool isConfirmed = true,
bool includeDetails = false,
CancellationToken cancellationToken = default);
/// <summary>
/// 通过用户主键列表获取用户
/// </summary>
/// <param name="userIds"></param>
/// <param name="includeDetails"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<List<IdentityUser>> GetListByIdListAsync(
List<Guid> userIds,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
/// <summary>
/// 获取用户所有的组织机构列表
/// </summary>
/// <param name="userId"></param>
/// <param name="filter"></param>
/// <param name="includeDetails"></param>
/// <param name="skipCount"></param>
/// <param name="maxResultCount"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<List<OrganizationUnit>> GetOrganizationUnitsAsync(
Guid id,
Guid userId,
string filter = null,
bool includeDetails = false,
int skipCount = 1,
int maxResultCount = 10,
CancellationToken cancellationToken = default
);
/// <summary>
///
/// </summary>
/// <param name="organizationUnitId"></param>
/// <param name="filter"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<long> GetUsersInOrganizationUnitCountAsync(
Guid organizationUnitId,
string filter = null,

39
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/SmsSecurityTokenCacheItem.cs

@ -0,0 +1,39 @@
namespace LINGYUN.Abp.Identity
{
/// <summary>
/// 短信安全令牌验证缓存
/// </summary>
public class SmsSecurityTokenCacheItem
{
/// <summary>
/// 用于验证的Token
/// </summary>
public string Token { get; set; }
/// <summary>
/// 用于验证的安全令牌
/// </summary>
public string SecurityToken { get; set; }
public SmsSecurityTokenCacheItem()
{
}
public SmsSecurityTokenCacheItem(string token, string securityToken)
{
Token = token;
SecurityToken = securityToken;
}
/// <summary>
/// 生成查询Key
/// </summary>
/// <param name="phoneNumber">手机号</param>
/// <param name="purpose">安全令牌用途</param>
/// <returns></returns>
public static string CalculateCacheKey(string phoneNumber, string purpose)
{
return "Totp:" + purpose + ";p:" + phoneNumber;
}
}
}

57
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/Microsoft/AspNetCore/Identity/PhoneNumberUserValidator.cs

@ -0,0 +1,57 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Identity.Localization;
using IIdentityUserRepository = LINGYUN.Abp.Identity.IIdentityUserRepository;
namespace Microsoft.AspNetCore.Identity
{
[Dependency(ServiceLifetime.Scoped, ReplaceServices = true)]
[ExposeServices(typeof(IUserValidator<IdentityUser>))]
public class PhoneNumberUserValidator : UserValidator<IdentityUser>
{
private readonly IStringLocalizer _stringLocalizer;
private readonly IIdentityUserRepository _userRepository;
public PhoneNumberUserValidator(
IIdentityUserRepository userRepository,
IStringLocalizer<IdentityResource> stringLocalizer)
{
_userRepository = userRepository;
_stringLocalizer = stringLocalizer;
}
public override async Task<IdentityResult> ValidateAsync(UserManager<IdentityUser> manager, IdentityUser user)
{
var errors = new List<IdentityError>();
await ValidatePhoneNumberAsync(manager, user, errors);
return (errors.Count > 0)
? IdentityResult.Failed(errors.ToArray())
: await base.ValidateAsync(manager, user);
}
protected virtual async Task ValidatePhoneNumberAsync(UserManager<IdentityUser> manager, IdentityUser user, ICollection<IdentityError> errors)
{
var phoneNumber = await manager.GetPhoneNumberAsync(user);
if (phoneNumber.IsNullOrWhiteSpace())
{
return;
}
var findUser = await _userRepository.FindByPhoneNumberAsync(phoneNumber, false);
if (findUser != null && !findUser.Id.Equals(user.Id))
{
//errors.Add(new IdentityError
//{
// Code = "DuplicatePhoneNumber",
// Description = _stringLocalizer["DuplicatePhoneNumber", phoneNumber]
//});
throw new UserFriendlyException(_stringLocalizer["Volo.Abp.Identity:DuplicatePhoneNumber", phoneNumber]);
}
}
}
}

38
aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs

@ -20,6 +20,44 @@ namespace LINGYUN.Abp.Identity.EntityFrameworkCore
{
}
public virtual async Task<bool> IsPhoneNumberUedAsync(
string phoneNumber,
CancellationToken cancellationToken = default)
{
return await DbSet.IncludeDetails(false)
.AnyAsync(user => user.PhoneNumber == phoneNumber,
GetCancellationToken(cancellationToken));
}
public virtual async Task<bool> IsPhoneNumberConfirmedAsync(
string phoneNumber,
CancellationToken cancellationToken = default)
{
return await DbSet.IncludeDetails(false)
.AnyAsync(user => user.PhoneNumber == phoneNumber && user.PhoneNumberConfirmed,
GetCancellationToken(cancellationToken));
}
public virtual async Task<bool> IsNormalizedEmailConfirmedAsync(
string normalizedEmail,
CancellationToken cancellationToken = default)
{
return await DbSet.IncludeDetails(false)
.AnyAsync(user => user.NormalizedEmail == normalizedEmail && user.EmailConfirmed,
GetCancellationToken(cancellationToken));
}
public virtual async Task<IdentityUser> FindByPhoneNumberAsync(
string phoneNumber,
bool isConfirmed = true,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await DbSet.IncludeDetails(includeDetails)
.Where(user => user.PhoneNumber == phoneNumber && user.PhoneNumberConfirmed == isConfirmed)
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<IdentityUser>> GetListByIdListAsync(
List<Guid> userIds,
bool includeDetails = false,

8
aspnet-core/modules/identity/LINGYUN.Abp.Identity.HttpApi/LINGYUN/Abp/Identity/AbpIdentityHttpApiModule.cs

@ -1,4 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.Identity.Localization;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Identity
@ -10,6 +12,12 @@ namespace LINGYUN.Abp.Identity
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
{
options.AddAssemblyResource(typeof(IdentityResource), typeof(AbpIdentityApplicationContractsModule).Assembly);
options.AddAssemblyResource(typeof(IdentityResource), typeof(Volo.Abp.Identity.AbpIdentityApplicationContractsModule).Assembly);
});
PreConfigure<IMvcBuilder>(mvcBuilder =>
{
mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpIdentityHttpApiModule).Assembly);

2
aspnet-core/modules/identity/LINGYUN.Abp.Identity.HttpApi/LINGYUN/Abp/Identity/IdentityUserController.cs

@ -87,7 +87,7 @@ namespace LINGYUN.Abp.Identity
[HttpPut]
[Route("change-two-factor")]
public virtual async Task ChangeTwoFactorEnabledAsync(Guid id, IdentityUserTwoFactorEnabledDto input)
public virtual async Task ChangeTwoFactorEnabledAsync(Guid id, ChangeTwoFactorEnabledDto input)
{
await UserAppService.ChangeTwoFactorEnabledAsync(id, input);
}

16
aspnet-core/modules/identity/LINGYUN.Abp.Identity.HttpApi/LINGYUN/Abp/Identity/MyProfileController.cs

@ -22,9 +22,23 @@ namespace LINGYUN.Abp.Identity
[HttpPut]
[Route("change-two-factor")]
public virtual async Task ChangeTwoFactorEnabledAsync(IdentityUserTwoFactorEnabledDto input)
public virtual async Task ChangeTwoFactorEnabledAsync(ChangeTwoFactorEnabledDto input)
{
await MyProfileAppService.ChangeTwoFactorEnabledAsync(input);
}
[HttpPut]
[Route("send-phone-number-change-code")]
public virtual async Task SendChangePhoneNumberCodeAsync(SendChangePhoneNumberCodeDto input)
{
await MyProfileAppService.SendChangePhoneNumberCodeAsync(input);
}
[HttpPut]
[Route("change-phone-number")]
public virtual async Task ChangePhoneNumberAsync(ChangePhoneNumberDto input)
{
await MyProfileAppService.ChangePhoneNumberAsync(input);
}
}
}

6
aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.HttpApi/LINGYUN/Abp/IdentityServer/AbpIdentityServerHttpApiModule.cs

@ -1,6 +1,7 @@
using Localization.Resources.AbpUi;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.IdentityServer.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
@ -15,6 +16,11 @@ namespace LINGYUN.Abp.IdentityServer
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
{
options.AddAssemblyResource(typeof(AbpIdentityServerResource), typeof(AbpIdentityServerApplicationContractsModule).Assembly);
});
PreConfigure<IMvcBuilder>(mvcBuilder =>
{
mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpIdentityServerHttpApiModule).Assembly);

2
aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.SmsValidator/LINGYUN.Abp.IdentityServer.SmsValidator.csproj

@ -22,7 +22,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\account\LINGYUN.Abp.Account.Domain\LINGYUN.Abp.Account.Domain.csproj" />
<ProjectReference Include="..\..\identity\LINGYUN.Abp.Identity.Domain\LINGYUN.Abp.Identity.Domain.csproj" />
</ItemGroup>
</Project>

13
aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.SmsValidator/LINGYUN/Abp/IdentityServer/SmsValidator/SmsTokenGrantValidator.cs

@ -3,7 +3,7 @@ using IdentityServer4.Events;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Validation;
using LINGYUN.Abp.Account;
using LINGYUN.Abp.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
@ -25,8 +25,6 @@ namespace LINGYUN.Abp.IdentityServer.SmsValidator
protected UserManager<IdentityUser> UserManager { get; }
protected SignInManager<IdentityUser> SignInManager { get; }
protected IStringLocalizer<AbpIdentityServerResource> Localizer { get; }
protected PhoneNumberTokenProvider<IdentityUser> PhoneNumberTokenProvider { get; }
public SmsTokenGrantValidator(
IEventService eventService,
@ -34,7 +32,6 @@ namespace LINGYUN.Abp.IdentityServer.SmsValidator
SignInManager<IdentityUser> signInManager,
IIdentityUserRepository userRepository,
IStringLocalizer<AbpIdentityServerResource> stringLocalizer,
PhoneNumberTokenProvider<IdentityUser> phoneNumberTokenProvider,
ILogger<SmsTokenGrantValidator> logger)
{
Logger = logger;
@ -43,7 +40,6 @@ namespace LINGYUN.Abp.IdentityServer.SmsValidator
SignInManager = signInManager;
Localizer = stringLocalizer;
UserRepository = userRepository;
PhoneNumberTokenProvider = phoneNumberTokenProvider;
}
public string GrantType => SmsValidatorConsts.SmsValidatorGrantTypeName;
@ -76,7 +72,8 @@ namespace LINGYUN.Abp.IdentityServer.SmsValidator
Localizer["InvalidGrant:PhoneNumberNotRegister"]);
return;
}
var validResult = await PhoneNumberTokenProvider.ValidateAsync(SmsValidatorConsts.SmsValidatorPurpose, phoneToken, UserManager, currentUser);
var validResult = await UserManager.VerifyTwoFactorTokenAsync(currentUser, TokenOptions.DefaultPhoneProvider, phoneToken);
if (!validResult)
{
Logger.LogWarning("Authentication failed for token: {0}, reason: invalid token", phoneToken);
@ -85,6 +82,7 @@ namespace LINGYUN.Abp.IdentityServer.SmsValidator
await EventService.RaiseAsync(new UserLoginFailureEvent(currentUser.UserName, $"invalid phone verify code {phoneToken}", false));
return;
}
var sub = await UserManager.GetUserIdAsync(currentUser);
var additionalClaims = new List<Claim>();
@ -95,6 +93,9 @@ namespace LINGYUN.Abp.IdentityServer.SmsValidator
await EventService.RaiseAsync(new UserLoginSuccessEvent(currentUser.UserName, phoneNumber, null));
context.Result = new GrantValidationResult(sub, OidcConstants.AuthenticationMethods.ConfirmationBySms, additionalClaims.ToArray());
// 登录之后需要更新安全令牌
(await UserManager.UpdateSecurityStampAsync(currentUser)).CheckErrors();
}
}
}

8
aspnet-core/services/account/AuthServer.Host/AuthIdentityServerModule.cs

@ -1,7 +1,8 @@
using DotNetCore.CAP;
using LINGYUN.Abp.Account;
using LINGYUN.Abp.EventBus.CAP;
using LINGYUN.Abp.Identity.EntityFrameworkCore;
using LINGYUN.Abp.IdentityServer;
using LINGYUN.Abp.IdentityServer.EntityFrameworkCore;
using LINGYUN.Abp.MultiTenancy.DbFinder;
using LINGYUN.Abp.PermissionManagement.Identity;
using LINYUN.Abp.Sms.Aliyun;
@ -35,8 +36,6 @@ using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.MySQL;
using Volo.Abp.FeatureManagement.EntityFrameworkCore;
using Volo.Abp.Identity;
using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.IdentityServer.EntityFrameworkCore;
using Volo.Abp.IdentityServer.Jwt;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
@ -52,7 +51,6 @@ using Volo.Abp.VirtualFileSystem;
namespace AuthServer.Host
{
[DependsOn(
typeof(AbpAccountDomainModule),
typeof(AbpAccountWebIdentityServerModule),
typeof(AbpAccountApplicationModule),
typeof(AbpAspNetCoreMvcUiMultiTenancyModule),
@ -170,7 +168,7 @@ namespace AuthServer.Host
{
options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
// 邮件登录地址
options.Applications["MVC"].Urls[LINGYUN.Abp.Account.AccountUrlNames.MailLoginVerify] = "Account/VerifyCode";
options.Applications["MVC"].Urls["EmailVerifyLogin"] = "Account/VerifyCode";
});
context.Services.ConfigureNonBreakingSameSiteCookies();

5
aspnet-core/services/account/AuthServer.Host/AuthServer.Host.csproj

@ -47,18 +47,17 @@
<PackageReference Include="Volo.Abp.AspNetCore.MultiTenancy" Version="3.3.0" />
<PackageReference Include="Volo.Abp.FeatureManagement.EntityFrameworkCore" Version="3.3.0" />
<PackageReference Include="Volo.Abp.SettingManagement.EntityFrameworkCore" Version="3.3.0" />
<PackageReference Include="Volo.Abp.Identity.EntityFrameworkCore" Version="3.3.0" />
<PackageReference Include="Volo.Abp.IdentityServer.EntityFrameworkCore" Version="3.3.0" />
<PackageReference Include="Volo.Abp.TenantManagement.EntityFrameworkCore" Version="3.3.0" />
<PackageReference Include="Volo.Abp.PermissionManagement.EntityFrameworkCore" Version="3.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\modules\account\LINGYUN.Abp.Account.Domain\LINGYUN.Abp.Account.Domain.csproj" />
<ProjectReference Include="..\..\..\modules\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj" />
<ProjectReference Include="..\..\..\modules\common\LINGYUN.Abp.Sms.Aliyun\LINGYUN.Abp.Sms.Aliyun.csproj" />
<ProjectReference Include="..\..\..\modules\identityServer\LINGYUN.Abp.IdentityServer.EntityFrameworkCore\LINGYUN.Abp.IdentityServer.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\..\modules\identityServer\LINGYUN.Abp.IdentityServer.WeChatValidator\LINGYUN.Abp.IdentityServer.WeChatValidator.csproj" />
<ProjectReference Include="..\..\..\modules\identityServer\LINGYUN.Abp.IdentityServer.SmsValidator\LINGYUN.Abp.IdentityServer.SmsValidator.csproj" />
<ProjectReference Include="..\..\..\modules\identity\LINGYUN.Abp.Identity.EntityFrameworkCore\LINGYUN.Abp.Identity.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\..\modules\identity\LINGYUN.Abp.PermissionManagement.Domain.Identity\LINGYUN.Abp.PermissionManagement.Domain.Identity.csproj" />
<ProjectReference Include="..\..\..\modules\tenants\LINGYUN.Abp.MultiTenancy.DbFinder\LINGYUN.Abp.MultiTenancy.DbFinder.csproj" />
<ProjectReference Include="..\..\..\modules\tenants\LINGYUN.Abp.MultiTenancy\LINGYUN.Abp.MultiTenancy.csproj" />

52
aspnet-core/services/account/AuthServer.Host/EntityFrameworkCore/Identity/EfCoreIdentityUserExtensionRepository.cs

@ -1,52 +0,0 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Identity;
using Volo.Abp.Identity.EntityFrameworkCore;
namespace AuthServer.Host.EntityFrameworkCore.Identity
{
public class EfCoreIdentityUserRepository : EfCoreRepository<IdentityDbContext, IdentityUser, Guid>, LINGYUN.Abp.Account.IIdentityUserRepository,
ITransientDependency
{
public EfCoreIdentityUserRepository(
IDbContextProvider<IdentityDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public virtual async Task<bool> PhoneNumberHasRegistedAsync(string phoneNumber)
{
return await DbSet.AnyAsync(x => x.PhoneNumberConfirmed && x.PhoneNumber.Equals(phoneNumber));
}
public virtual async Task<Guid?> GetIdByPhoneNumberAsync(string phoneNumber)
{
return await DbSet
.Where(x => x.PhoneNumber.Equals(phoneNumber))
.Select(x => x.Id)
.FirstOrDefaultAsync();
}
public virtual async Task<IdentityUser> FindByPhoneNumberAsync(string phoneNumber)
{
return await WithDetails()
.Where(usr => usr.PhoneNumber.Equals(phoneNumber))
.AsNoTracking()
.FirstOrDefaultAsync();
}
public override IQueryable<IdentityUser> WithDetails()
{
return DbSet
.Include(x => x.Claims)
.Include(x => x.Roles)
.Include(x => x.Logins)
.Include(x => x.Tokens);
}
}
}

5
aspnet-core/services/account/AuthServer.Host/Pages/Account/SendCode.cshtml.cs

@ -1,4 +1,5 @@
using AuthServer.Host.Emailing;
using LINGYUN.Abp.Identity.Settings;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
@ -100,8 +101,8 @@ namespace LINGYUN.Abp.Account.Web.Pages.Account
else if (Input.SelectedProvider == "Phone")
{
var phoneNumber = await UserManager.GetPhoneNumberAsync(user);
var templateCode = await SettingProvider.GetOrNullAsync(AccountSettingNames.SmsSigninTemplateCode);
Check.NotNullOrWhiteSpace(templateCode, nameof(AccountSettingNames.SmsSigninTemplateCode));
var templateCode = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsUserSignin);
Check.NotNullOrWhiteSpace(templateCode, nameof(IdentitySettingNames.User.SmsUserSignin));
// TODO: 以后扩展短信模板发送
var smsMessage = new SmsMessage(phoneNumber, code);

12
aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/AbpIdentityServerAdminHttpApiHostModule.cs

@ -1,5 +1,4 @@
using DotNetCore.CAP;
using IdentityModel;
using LINGYUN.Abp.EventBus.CAP;
using LINGYUN.Abp.ExceptionHandling;
using LINGYUN.Abp.ExceptionHandling.Emailing;
@ -14,13 +13,11 @@ using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using StackExchange.Redis;
using System;
using System.Text;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.AspNetCore.Authentication.JwtBearer;
using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy;
using Volo.Abp.AspNetCore.Security.Claims;
@ -34,7 +31,6 @@ using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.MySQL;
using Volo.Abp.Identity.Localization;
using Volo.Abp.IdentityServer.EntityFrameworkCore;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
@ -50,16 +46,14 @@ namespace LINGYUN.Abp.IdentityServer4
{
[DependsOn(
typeof(AbpAspNetCoreMvcUiMultiTenancyModule),
typeof(LINGYUN.Abp.Identity.AbpIdentityHttpApiModule),
typeof(LINGYUN.Abp.Identity.AbpIdentityApplicationModule),
typeof(LINGYUN.Abp.Account.AbpAccountApplicationModule),
typeof(LINGYUN.Abp.Account.AbpAccountHttpApiModule),
typeof(LINGYUN.Abp.Identity.AbpIdentityApplicationModule),
typeof(LINGYUN.Abp.Identity.AbpIdentityHttpApiModule),
typeof(LINGYUN.Abp.IdentityServer.AbpIdentityServerApplicationModule),
typeof(LINGYUN.Abp.IdentityServer.AbpIdentityServerHttpApiModule),
typeof(LINGYUN.Abp.Identity.EntityFrameworkCore.AbpIdentityEntityFrameworkCoreModule),
typeof(LINGYUN.Abp.IdentityServer.EntityFrameworkCore.AbpIdentityServerEntityFrameworkCoreModule),
typeof(AbpAccountApplicationModule),
typeof(AbpAccountHttpApiModule),
typeof(AbpEntityFrameworkCoreMySQLModule),
typeof(AbpAuditLoggingEntityFrameworkCoreModule),
typeof(AbpTenantManagementEntityFrameworkCoreModule),
@ -180,7 +174,7 @@ namespace LINGYUN.Abp.IdentityServer4
// 滑动过期30天
options.GlobalCacheEntryOptions.SlidingExpiration = TimeSpan.FromDays(30);
// 绝对过期60天
options.GlobalCacheEntryOptions.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60);
options.GlobalCacheEntryOptions.AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(60);
});
Configure<AbpDistributedEntityEventOptions>(options =>

5
aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/Emailing/Templates/EmailConfirmed.tpl

@ -0,0 +1,5 @@
<div style="position: absolute;">
<span>{{L "EmailConfirmed" model.user}}</span>
<p style="display:block; padding:0 50px; width: 150px; height:48px; line-height:48px; color:#cc0000; font-size:26px; background:#9c9797; font-weight:bold;">{{model.code}}</p>
<span>{{L "EmailConfirmedRemarks"}}</span>
</div>

26
aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/Emailing/Templates/IdentityEmailTemplateDefinitionProvider.cs

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Emailing.Templates;
using Volo.Abp.Localization;
using Volo.Abp.TextTemplating;
using Volo.Abp.Identity.Localization;
namespace LINGYUN.Abp.IdentityServer4.Emailing.Templates
{
public class IdentityEmailTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition(
IdentityEmailTemplates.EmailConfirmed,
displayName: LocalizableString.Create<IdentityResource>($"TextTemplate:{IdentityEmailTemplates.EmailConfirmed}"),
layout: StandardEmailTemplates.Layout,
localizationResource: typeof(IdentityResource)
).WithVirtualFilePath("/LINGYUN/Abp/IdentityServer4/Emailing/Templates/EmailConfirmed.tpl", true)
);
}
}
}

7
aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/Emailing/Templates/IdentityEmailTemplates.cs

@ -0,0 +1,7 @@
namespace LINGYUN.Abp.IdentityServer4.Emailing.Templates
{
public static class IdentityEmailTemplates
{
public const string EmailConfirmed = "Abp.Identity.EmailConfirmed";
}
}

42
aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/EntityFrameworkCore/Identity/EfCoreIdentityUserExtensionRepository.cs

@ -1,42 +0,0 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Identity;
using Volo.Abp.Identity.EntityFrameworkCore;
namespace LINGYUN.Abp.IdentityServer4.Identity
{
public class EfCoreIdentityUserRepository : EfCoreRepository<IdentityDbContext, IdentityUser, Guid>, Abp.Account.IIdentityUserRepository,
ITransientDependency
{
public EfCoreIdentityUserRepository(
IDbContextProvider<IdentityDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public virtual async Task<bool> PhoneNumberHasRegistedAsync(string phoneNumber)
{
return await DbSet.AnyAsync(x => x.PhoneNumberConfirmed && x.PhoneNumber.Equals(phoneNumber));
}
public virtual async Task<Guid?> GetIdByPhoneNumberAsync(string phoneNumber)
{
return await DbSet
.Where(x => x.PhoneNumber.Equals(phoneNumber))
.Select(x => x.Id)
.FirstOrDefaultAsync();
}
public virtual async Task<IdentityUser> FindByPhoneNumberAsync(string phoneNumber)
{
return await DbSet.Where(usr => usr.PhoneNumber.Equals(phoneNumber))
.AsNoTracking()
.FirstOrDefaultAsync();
}
}
}

11
aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/LINGYUN.Abp.IdentityServer4.HttpApi.Host.csproj

@ -11,6 +11,11 @@
</ItemGroup>
<ItemGroup>
<None Remove="Emailing\Templates\EmailConfirmed.tpl" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Emailing\Templates\EmailConfirmed.tpl" />
<EmbeddedResource Include="Localization\en.json" />
<EmbeddedResource Include="Localization\zh-Hans.json" />
</ItemGroup>
@ -61,4 +66,10 @@
<ProjectReference Include="..\..\..\modules\tenants\LINGYUN.Abp.MultiTenancy.DbFinder\LINGYUN.Abp.MultiTenancy.DbFinder.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

14
aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/Properties/launchSettings.json

@ -1,7 +1,7 @@
{
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:49612",
"sslPort": 0
@ -10,11 +10,11 @@
"profiles": {
"LINGYUN.Abp.IdentityServer4.HttpApi.Host": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "http://127.0.0.1:30015",
"environmentVariables": {
"COMPlus_legacyCorruptedStateExceptionsPolicy": "1",
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"applicationUrl": "http://127.0.0.1:30015"
}
}
}
}

66
aspnet-core/services/identity-server/LINGYUN.Abp.IdentityServer4.HttpApi.Host/UserSecurityCodeSender.cs

@ -0,0 +1,66 @@
using LINGYUN.Abp.Identity;
using LINGYUN.Abp.IdentityServer4.Emailing.Templates;
using Microsoft.Extensions.Localization;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Emailing;
using Volo.Abp.Identity.Localization;
using Volo.Abp.Sms;
using Volo.Abp.TextTemplating;
namespace LINGYUN.Abp.IdentityServer4
{
public class UserSecurityCodeSender : IUserSecurityCodeSender, ITransientDependency
{
protected IEmailSender EmailSender { get; }
protected ITemplateRenderer TemplateRenderer { get; }
protected IStringLocalizer<IdentityResource> Localizer { get; }
protected ISmsSender SmsSender { get; }
public UserSecurityCodeSender(
ISmsSender smsSender,
IEmailSender emailSender,
ITemplateRenderer templateRenderer,
IStringLocalizer<IdentityResource> localizer)
{
SmsSender = smsSender;
EmailSender = emailSender;
TemplateRenderer = templateRenderer;
Localizer = localizer;
}
public virtual async Task SendEmailConfirmedCodeAsync(
string userName,
string email,
string token,
CancellationToken cancellation = default)
{
var emailContent = await TemplateRenderer.RenderAsync(
IdentityEmailTemplates.EmailConfirmed,
new { user = userName, code = token });
await EmailSender.SendAsync(
email,
Localizer["EmailConfirmed"],
emailContent);
}
public virtual async Task SendPhoneConfirmedCodeAsync(
string phone,
string token,
string template,
CancellationToken cancellation = default)
{
Check.NotNullOrWhiteSpace(template, nameof(template));
var smsMessage = new SmsMessage(phone, token);
smsMessage.Properties.Add("code", token);
smsMessage.Properties.Add("TemplateCode", template);
await SmsSender.SendAsync(smsMessage);
}
}
}

35
vueJs/src/api/users.ts

@ -169,13 +169,40 @@ export default class UserApiService {
})
}
public static sendPhoneVerifyCode(phoneVerify: PhoneVerify) {
const _url = '/api/account/phone/verify'
return ApiService.HttpRequest<any>({
/** 发送短信登录验证码 */
public static sendSmsSigninCode(phoneNumber: string) {
const _url = '/api/account/phone/send-signin-code'
return ApiService.HttpRequest<void>({
baseURL: IdentityServiceUrl,
url: _url,
method: 'POST',
data: phoneVerify
data: {
phoneNumber: phoneNumber
}
})
}
public static sendSmsResetPasswordCode(phoneNumber: string) {
const _url = '/api/account/phone/send-password-reset-code'
return ApiService.HttpRequest<void>({
baseURL: IdentityServiceUrl,
url: _url,
method: 'POST',
data: {
phoneNumber: phoneNumber
}
})
}
public static sendSmsRegisterCode(phoneNumber: string) {
const _url = '/api/account/phone/send-register-code'
return ApiService.HttpRequest<void>({
baseURL: IdentityServiceUrl,
url: _url,
method: 'POST',
data: {
phoneNumber: phoneNumber
}
})
}

7
vueJs/src/views/login/index.vue

@ -157,7 +157,7 @@ import { Dictionary } from 'vue-router/types/router'
import TenantBox from '@/components/TenantBox/index.vue'
import LangSelect from '@/components/LangSelect/index.vue'
import { Component, Vue, Watch } from 'vue-property-decorator'
import UserService, { PhoneVerify, VerifyType } from '@/api/users'
import UserService from '@/api/users'
import { AbpModule } from '@/store/modules/abp'
@Component({
@ -300,10 +300,7 @@ export default class extends Vue {
frmLogin.validateField('phoneNumber', (errorMsg: string) => {
if (!errorMsg) {
this.sending = true
const phoneVerify = new PhoneVerify()
phoneVerify.phoneNumber = this.loginForm.phoneNumber
phoneVerify.verifyType = VerifyType.Signin
UserService.sendPhoneVerifyCode(phoneVerify).then(() => {
UserService.sendSmsSigninCode(this.loginForm.phoneNumber).then(() => {
let interValTime = 60
const sendingName = this.l('login.afterSendVerifyCode')
const sendedName = this.l('login.sendVerifyCode')

7
vueJs/src/views/register/index.vue

@ -124,7 +124,7 @@ import { Dictionary } from 'vue-router/types/router'
import TenantBox from '@/components/TenantBox/index.vue'
import LangSelect from '@/components/LangSelect/index.vue'
import { Component, Vue, Watch } from 'vue-property-decorator'
import UserService, { PhoneVerify, VerifyType, UserRegisterData } from '@/api/users'
import UserService, { UserRegisterData } from '@/api/users'
import { AbpModule } from '@/store/modules/abp'
@Component({
@ -246,10 +246,7 @@ export default class extends Vue {
frmLogin.validateField('phoneNumber', (errorMsg: string) => {
if (!errorMsg) {
this.sending = true
const phoneVerify = new PhoneVerify()
phoneVerify.phoneNumber = this.registerForm.phoneNumber
phoneVerify.verifyType = VerifyType.Register
UserService.sendPhoneVerifyCode(phoneVerify).then(() => {
UserService.sendSmsRegisterCode(this.registerForm.phoneNumber).then(() => {
let interValTime = 60
const sendingName = this.l('login.afterSendVerifyCode')
const sendedName = this.l('login.sendVerifyCode')

7
vueJs/src/views/reset-password/index.vue

@ -112,7 +112,7 @@ import { Dictionary } from 'vue-router/types/router'
import TenantBox from '@/components/TenantBox/index.vue'
import LangSelect from '@/components/LangSelect/index.vue'
import { Component, Vue, Watch } from 'vue-property-decorator'
import UserService, { PhoneVerify, VerifyType, UserResetPasswordData } from '@/api/users'
import UserService, { UserResetPasswordData } from '@/api/users'
import { AbpModule } from '@/store/modules/abp'
@Component({
@ -226,10 +226,7 @@ export default class extends Vue {
frmResetPassword.validateField('phoneNumber', (errorMsg: string) => {
if (!errorMsg) {
this.sending = true
const phoneVerify = new PhoneVerify()
phoneVerify.phoneNumber = this.resetPasswordForm.phoneNumber
phoneVerify.verifyType = VerifyType.ResetPassword
UserService.sendPhoneVerifyCode(phoneVerify).then(() => {
UserService.sendSmsResetPasswordCode(this.resetPasswordForm.phoneNumber).then(() => {
let interValTime = 60
const sendingName = this.l('login.afterSendVerifyCode')
const sendedName = this.l('login.sendVerifyCode')

Loading…
Cancel
Save