From 015e5acad28502f17a77c83eef58769710281539 Mon Sep 17 00:00:00 2001 From: colin Date: Sat, 8 Nov 2025 11:38:07 +0800 Subject: [PATCH] feat(account): add `AbpAccountSecurityModule` --- .../LINGYUN.Abp.Account.Application.csproj | 2 +- .../Account/AbpAccountApplicationModule.cs | 8 +- .../LINGYUN/Abp/Account/AccountAppService.cs | 12 +- .../Account/IAccountSmsSecurityCodeSender.cs | 13 -- .../Abp/Account/MyProfileAppService.cs | 15 +- .../Emailing/AbpAccountEmailingModule.cs | 2 + .../Emailing/IAccountEmailConfirmSender.cs | 1 + .../Emailing/IAccountEmailVerifySender.cs | 4 +- .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 ++++ .../LINGYUN.Abp.Account.Security.csproj | 29 ++++ .../Security/AbpAccountSecurityModule.cs | 31 ++++ .../AccountEmailSecurityCodeSender.cs | 135 ++++++++++++++++++ .../Security}/AccountSmsSecurityCodeSender.cs | 48 +++---- .../Abp/Account/Security/AccountUrlNames.cs | 7 + .../IAccountEmailSecurityCodeSender.cs | 41 ++++++ .../Security/IAccountSmsSecurityCodeSender.cs | 23 +++ .../Localization/AccountSecurityResource.cs | 8 ++ .../Security/Localization/Resources/en.json | 16 +++ .../Localization/Resources/zh-Hans.json | 16 +++ .../Templates/AccountEmailTemplates.cs | 13 ++ .../AccountTemplateDefinitionProvider.cs | 32 +++++ .../Security/Templates/MailConfirm.tpl | 7 + .../Security/Templates/MailSecurityVerify.tpl | 5 + .../AbpAccountWebModule.cs | 4 +- .../LINGYUN.Abp.Account.Web.csproj | 2 +- .../Pages/Account/Login.cshtml | 1 + .../Pages/Account/SendCode.cshtml.cs | 12 +- 28 files changed, 452 insertions(+), 68 deletions(-) delete mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/IAccountSmsSecurityCodeSender.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xml create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xsd create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN.Abp.Account.Security.csproj create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AbpAccountSecurityModule.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountEmailSecurityCodeSender.cs rename aspnet-core/modules/account/{LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account => LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security}/AccountSmsSecurityCodeSender.cs (88%) create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountUrlNames.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountEmailSecurityCodeSender.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountSmsSecurityCodeSender.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/AccountSecurityResource.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/en.json create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/zh-Hans.json create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountEmailTemplates.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountTemplateDefinitionProvider.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailConfirm.tpl create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailSecurityVerify.tpl diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN.Abp.Account.Application.csproj b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN.Abp.Account.Application.csproj index eda58b75f..13f7a3fe6 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN.Abp.Account.Application.csproj +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN.Abp.Account.Application.csproj @@ -25,7 +25,7 @@ - + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AbpAccountApplicationModule.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AbpAccountApplicationModule.cs index f5590e8f8..ef781343e 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AbpAccountApplicationModule.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AbpAccountApplicationModule.cs @@ -1,5 +1,5 @@ -using LINGYUN.Abp.Account.Emailing; -using LINGYUN.Abp.Account.Emailing.Localization; +using LINGYUN.Abp.Account.Security; +using LINGYUN.Abp.Account.Security.Localization; using LINGYUN.Abp.Identity; using LINGYUN.Abp.WeChat.MiniProgram; using Microsoft.Extensions.DependencyInjection; @@ -16,7 +16,7 @@ namespace LINGYUN.Abp.Account; [DependsOn( typeof(Volo.Abp.Account.AbpAccountApplicationModule), typeof(AbpAccountApplicationContractsModule), - typeof(AbpAccountEmailingModule), + typeof(AbpAccountSecurityModule), typeof(AbpIdentityDomainModule), typeof(AbpBlobStoringModule), typeof(AbpWeChatMiniProgramModule))] @@ -45,7 +45,7 @@ public class AbpAccountApplicationModule : AbpModule { options.Resources .Get() - .AddBaseTypes(typeof(AccountEmailingResource)); + .AddBaseTypes(typeof(AccountSecurityResource)); }); } } diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs index e27278844..c4f87f336 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs @@ -1,4 +1,4 @@ -using LINGYUN.Abp.Account.Emailing; +using LINGYUN.Abp.Account.Security; using LINGYUN.Abp.Identity; using LINGYUN.Abp.Identity.Security; using LINGYUN.Abp.Identity.Settings; @@ -123,7 +123,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi var code = TotpService.GenerateCode(Encoding.Unicode.GetBytes(securityToken), securityTokenCacheKey); securityTokenCacheItem = new SecurityTokenCacheItem(code.ToString(), securityToken); - await SecurityCodeSender.SendSmsCodeAsync( + await SecurityCodeSender.SendAsync( input.PhoneNumber, securityTokenCacheItem.Token, template); await SecurityTokenCache @@ -227,7 +227,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi // 生成二次认证码 var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider); // 发送短信验证码 - await SecurityCodeSender.SendSmsCodeAsync(input.PhoneNumber, code, template); + await SecurityCodeSender.SendAsync(input.PhoneNumber, code, template); // 缓存这个手机号的记录,防重复 securityTokenCacheItem = new SecurityTokenCacheItem(code, user.SecurityStamp); await SecurityTokenCache @@ -294,7 +294,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsUserSignin); // 发送登录验证码短信 - await SecurityCodeSender.SendSmsCodeAsync(input.PhoneNumber, code, template); + await SecurityCodeSender.SendAsync(input.PhoneNumber, code, template); // 缓存登录验证码状态,防止同一手机号重复发送 securityTokenCacheItem = new SecurityTokenCacheItem(code, user.SecurityStamp); await SecurityTokenCache @@ -307,7 +307,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi public async virtual Task SendEmailSigninCodeAsync(SendEmailSigninCodeDto input) { - var sender = LazyServiceProvider.LazyGetRequiredService(); + var sender = LazyServiceProvider.LazyGetRequiredService(); var user = await UserManager.FindByEmailAsync(input.EmailAddress); @@ -322,7 +322,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultEmailProvider); - await sender.SendMailLoginVerifyCodeAsync(code, user.UserName, user.Email); + await sender.SendLoginCodeAsync(code, user.UserName, user.Email); } public async virtual Task> GetTwoFactorProvidersAsync(GetTwoFactorProvidersInput input) diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/IAccountSmsSecurityCodeSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/IAccountSmsSecurityCodeSender.cs deleted file mode 100644 index d5c89d474..000000000 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/IAccountSmsSecurityCodeSender.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Account; - -public interface IAccountSmsSecurityCodeSender -{ - Task SendSmsCodeAsync( - string phone, - string token, - string template, // 传递模板号 - CancellationToken cancellation = default); -} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/MyProfileAppService.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/MyProfileAppService.cs index a0cb80856..a04a78274 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/MyProfileAppService.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/MyProfileAppService.cs @@ -1,4 +1,4 @@ -using LINGYUN.Abp.Account.Emailing; +using LINGYUN.Abp.Account.Security; using LINGYUN.Abp.Identity; using LINGYUN.Abp.Identity.Security; using LINGYUN.Abp.Identity.Session; @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; using System; -using System.IO; using System.Linq; using System.Net; using System.Text; @@ -20,7 +19,6 @@ using Volo.Abp.Caching; using Volo.Abp.Content; using Volo.Abp.Data; using Volo.Abp.Identity; -using Volo.Abp.Security.Claims; using Volo.Abp.Settings; using Volo.Abp.Users; using IIdentitySessionRepository = LINGYUN.Abp.Identity.IIdentitySessionRepository; @@ -31,23 +29,21 @@ namespace LINGYUN.Abp.Account; public class MyProfileAppService : AccountApplicationServiceBase, IMyProfileAppService { protected IDistributedCache SecurityTokenCache { get; } - protected IAccountSmsSecurityCodeSender SecurityCodeSender { get; } protected Identity.IIdentityUserRepository UserRepository { get; } protected IdentitySecurityLogManager IdentitySecurityLogManager { get; } + protected IAccountSmsSecurityCodeSender SmsSecurityCodeSender => LazyServiceProvider.LazyGetRequiredService(); + protected IAccountEmailSecurityCodeSender EmailSecurityCodeSender => LazyServiceProvider.LazyGetRequiredService(); protected IAuthenticatorUriGenerator AuthenticatorUriGenerator => LazyServiceProvider.LazyGetRequiredService(); - protected IIdentitySessionManager IdentitySessionManager => LazyServiceProvider.LazyGetRequiredService(); protected IIdentitySessionRepository IdentitySessionRepository => LazyServiceProvider.LazyGetRequiredService(); protected IUserPictureProvider UserPictureProvider => LazyServiceProvider.LazyGetRequiredService(); public MyProfileAppService( Identity.IIdentityUserRepository userRepository, - IAccountSmsSecurityCodeSender securityCodeSender, IdentitySecurityLogManager identitySecurityLogManager, IDistributedCache securityTokenCache) { UserRepository = userRepository; - SecurityCodeSender = securityCodeSender; IdentitySecurityLogManager = identitySecurityLogManager; SecurityTokenCache = securityTokenCache; @@ -147,7 +143,7 @@ public class MyProfileAppService : AccountApplicationServiceBase, IMyProfileAppS var template = await SettingProvider.GetOrNullAsync(Identity.Settings.IdentitySettingNames.User.SmsPhoneNumberConfirmed); var token = await UserManager.GenerateChangePhoneNumberTokenAsync(user, input.NewPhoneNumber); // 发送验证码 - await SecurityCodeSender.SendSmsCodeAsync(input.NewPhoneNumber, token, template); + await SmsSecurityCodeSender.SendAsync(input.NewPhoneNumber, token, template); securityTokenCacheItem = new SecurityTokenCacheItem(token, user.ConcurrencyStamp); await SecurityTokenCache @@ -191,9 +187,8 @@ public class MyProfileAppService : AccountApplicationServiceBase, IMyProfileAppS var token = await UserManager.GenerateEmailConfirmationTokenAsync(user); var confirmToken = WebUtility.UrlEncode(token); - var sender = LazyServiceProvider.LazyGetRequiredService(); - await sender.SendEmailConfirmLinkAsync( + await EmailSecurityCodeSender.SendConfirmLinkAsync( user.Id, user.Email, confirmToken, diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/AbpAccountEmailingModule.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/AbpAccountEmailingModule.cs index a8328c793..13170d5a4 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/AbpAccountEmailingModule.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/AbpAccountEmailingModule.cs @@ -1,4 +1,5 @@ using LINGYUN.Abp.Account.Emailing.Localization; +using System; using Volo.Abp.Emailing; using Volo.Abp.Localization; using Volo.Abp.Modularity; @@ -10,6 +11,7 @@ namespace LINGYUN.Abp.Account.Emailing; [DependsOn( typeof(AbpEmailingModule), typeof(AbpUiNavigationModule))] +[Obsolete("This module has been deprecated. Please use AbpAccountSecurityModule.")] public class AbpAccountEmailingModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailConfirmSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailConfirmSender.cs index 6a2f693f4..4a233f340 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailConfirmSender.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailConfirmSender.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; namespace LINGYUN.Abp.Account.Emailing; +[Obsolete("This interface has been deprecated. Please use LINGYUN.Abp.Account.Security.IAccountEmailSecurityCodeSender.")] public interface IAccountEmailConfirmSender { Task SendEmailConfirmLinkAsync( diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailVerifySender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailVerifySender.cs index 10c898dbc..2ada5d647 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailVerifySender.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailVerifySender.cs @@ -1,7 +1,9 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; namespace LINGYUN.Abp.Account.Emailing; +[Obsolete("This interface has been deprecated. Please use LINGYUN.Abp.Account.Security.IAccountEmailSecurityCodeSender.")] public interface IAccountEmailVerifySender { Task SendMailLoginVerifyCodeAsync( diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xsd b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN.Abp.Account.Security.csproj b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN.Abp.Account.Security.csproj new file mode 100644 index 000000000..6ac829797 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN.Abp.Account.Security.csproj @@ -0,0 +1,29 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0 + LINGYUN.Abp.Account.Security + LINGYUN.Abp.Account.Security + false + false + false + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AbpAccountSecurityModule.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AbpAccountSecurityModule.cs new file mode 100644 index 000000000..37fd2513d --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AbpAccountSecurityModule.cs @@ -0,0 +1,31 @@ +using LINGYUN.Abp.Account.Security.Localization; +using Volo.Abp.Emailing; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.Sms; +using Volo.Abp.UI.Navigation; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.Account.Security; + +[DependsOn( + typeof(AbpEmailingModule), + typeof(AbpSmsModule), + typeof(AbpUiNavigationModule))] +public class AbpAccountSecurityModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/LINGYUN/Abp/Account/Security/Localization/Resources"); + }); + } +} \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountEmailSecurityCodeSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountEmailSecurityCodeSender.cs new file mode 100644 index 000000000..64e16fb20 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountEmailSecurityCodeSender.cs @@ -0,0 +1,135 @@ +using LINGYUN.Abp.Account.Security.Localization; +using LINGYUN.Abp.Account.Security.Templates; +using Microsoft.Extensions.Localization; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using System.Web; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Emailing; +using Volo.Abp.MultiTenancy; +using Volo.Abp.TextTemplating; +using Volo.Abp.UI.Navigation.Urls; + +namespace LINGYUN.Abp.Account.Security; + +[Dependency(ReplaceServices = true)] +[ExposeServices( + typeof(IAccountEmailSecurityCodeSender), + typeof(AccountEmailSecurityCodeSender))] +public class AccountEmailSecurityCodeSender : + IAccountEmailSecurityCodeSender, + ITransientDependency +{ + protected ITemplateRenderer TemplateRenderer { get; } + protected IEmailSender EmailSender { get; } + protected IStringLocalizer StringLocalizer { get; } + protected IAppUrlProvider AppUrlProvider { get; } + protected ICurrentTenant CurrentTenant { get; } + + public AccountEmailSecurityCodeSender( + IEmailSender emailSender, + ICurrentTenant currentTenant, + IAppUrlProvider appUrlProvider, + ITemplateRenderer templateRenderer, + IStringLocalizer accountLocalizer) + { + EmailSender = emailSender; + CurrentTenant = currentTenant; + AppUrlProvider = appUrlProvider; + StringLocalizer = accountLocalizer; + TemplateRenderer = templateRenderer; + } + + public async virtual Task SendLoginCodeAsync( + string code, + string userName, + string emailAddress) + { + var emailContent = await TemplateRenderer.RenderAsync( + AccountEmailTemplates.MailSecurityVerifyLink, + new { code = code, user = userName } + ); + + await EmailSender.SendAsync( + emailAddress, + StringLocalizer["MailSecurityVerify"], + emailContent + ); + } + + public async virtual Task SendConfirmLinkAsync( + Guid userId, + string userEmail, + string confirmToken, + string appName, + string returnUrl = null, + string returnUrlHash = null, + Guid? userTenantId = null) + { + Debug.Assert(CurrentTenant.Id == userTenantId, "This method can only work for current tenant!"); + + var url = await AppUrlProvider.GetUrlAsync(appName, AccountUrlNames.EmailConfirm); + + var link = $"{url}?userId={userId}&{TenantResolverConsts.DefaultTenantKey}={userTenantId}&confirmToken={UrlEncoder.Default.Encode(confirmToken)}"; + + if (!returnUrl.IsNullOrEmpty()) + { + link += "&returnUrl=" + NormalizeReturnUrl(returnUrl); + } + + if (!returnUrlHash.IsNullOrEmpty()) + { + link += "&returnUrlHash=" + returnUrlHash; + } + + var emailContent = await TemplateRenderer.RenderAsync( + AccountEmailTemplates.MailConfirmLink, + new { link = link } + ); + + await EmailSender.SendAsync( + userEmail, + StringLocalizer["EmailConfirm"], + emailContent + ); + } + + protected virtual string NormalizeReturnUrl(string returnUrl) + { + if (returnUrl.IsNullOrEmpty()) + { + return returnUrl; + } + + //Handling openid connect login + if (returnUrl.StartsWith("/connect/authorize/callback", StringComparison.OrdinalIgnoreCase)) + { + if (returnUrl.Contains("?")) + { + var queryPart = returnUrl.Split('?')[1]; + var queryParameters = queryPart.Split('&'); + foreach (var queryParameter in queryParameters) + { + if (queryParameter.Contains("=")) + { + var queryParam = queryParameter.Split('='); + if (queryParam[0] == "redirect_uri") + { + return HttpUtility.UrlDecode(queryParam[1]); + } + } + } + } + } + + if (returnUrl.StartsWith("/connect/authorize?", StringComparison.OrdinalIgnoreCase)) + { + return HttpUtility.UrlEncode(returnUrl); + } + + return returnUrl; + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountSmsSecurityCodeSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountSmsSecurityCodeSender.cs similarity index 88% rename from aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountSmsSecurityCodeSender.cs rename to aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountSmsSecurityCodeSender.cs index 551bb10a2..0b54fb383 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountSmsSecurityCodeSender.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountSmsSecurityCodeSender.cs @@ -4,29 +4,29 @@ using Volo.Abp; using Volo.Abp.DependencyInjection; using Volo.Abp.Sms; -namespace LINGYUN.Abp.Account; +namespace LINGYUN.Abp.Account.Security; -public class AccountSmsSecurityCodeSender : IAccountSmsSecurityCodeSender, ITransientDependency -{ - protected ISmsSender SmsSender { get; } - - public AccountSmsSecurityCodeSender(ISmsSender smsSender) - { - SmsSender = smsSender; - } - - public async virtual Task SendSmsCodeAsync( - 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); - } +public class AccountSmsSecurityCodeSender : IAccountSmsSecurityCodeSender, ITransientDependency +{ + protected ISmsSender SmsSender { get; } + + public AccountSmsSecurityCodeSender(ISmsSender smsSender) + { + SmsSender = smsSender; + } + + public async virtual Task SendAsync( + 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); + } } diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountUrlNames.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountUrlNames.cs new file mode 100644 index 000000000..28104f22a --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountUrlNames.cs @@ -0,0 +1,7 @@ +namespace LINGYUN.Abp.Account.Security; + +public static class AccountUrlNames +{ + public const string EmailConfirm = "Abp.Account.EmailConfirm"; + public const string EmailVerifyLogin = "Abp.Account.EmailVerifyLogin"; +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountEmailSecurityCodeSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountEmailSecurityCodeSender.cs new file mode 100644 index 000000000..c2f1f7d9b --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountEmailSecurityCodeSender.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Account.Security; +/// +/// 邮件安全码发送接口 +/// +public interface IAccountEmailSecurityCodeSender +{ + /// + /// 发送邮件登录验证码 + /// + /// 验证码 + /// 用户名 + /// 邮件地址 + /// + Task SendLoginCodeAsync( + string code, + string userName, + string emailAddress); + /// + /// 发送邮件确认链接 + /// + /// 用户Id + /// 用户邮件 + /// 确认Token + /// 应用名称 + /// 回调路径 + /// 回调路径Hash + /// 用户租户Id + /// + Task SendConfirmLinkAsync( + Guid userId, + string userEmail, + string confirmToken, + string appName, + string returnUrl = null, + string returnUrlHash = null, + Guid? userTenantId = null + ); +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountSmsSecurityCodeSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountSmsSecurityCodeSender.cs new file mode 100644 index 000000000..6b22163d1 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountSmsSecurityCodeSender.cs @@ -0,0 +1,23 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Account.Security; +/// +/// 短信安全码发送接口 +/// +public interface IAccountSmsSecurityCodeSender +{ + /// + /// 发送短信验证码 + /// + /// 手机号 + /// 短信验证码 + /// 短信验证模板 + /// + /// + Task SendAsync( + string phone, + string code, + string templateCode, + CancellationToken cancellation = default); +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/AccountSecurityResource.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/AccountSecurityResource.cs new file mode 100644 index 000000000..2121fe1e3 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/AccountSecurityResource.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Account.Security.Localization; + +[LocalizationResourceName("AbpAccountSecurity")] +public class AccountSecurityResource +{ +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/en.json b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/en.json new file mode 100644 index 000000000..ff2ebb3ca --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/en.json @@ -0,0 +1,16 @@ +{ + "culture": "en", + "texts": { + "TextTemplate:Abp.Account.MailSecurityVerifyLink": "Mail security validation template", + "TextTemplate:Abp.Account.MailConfirmLink": "Mail confirm template", + "MailSecurityVerify": "Mail security verification", + "VerifyMyEmailAddress": "Hello {0}

Your email security verification code is as follows. Please enter the verification code to proceed to the next step.

If not operated by you, please ignore this email.

", + "MailSecurityVerifyRemarks": "

(If it is not in the form of a link, copy the address to the browser address bar for further access)

Thank you for your visit and have a good time!

", + "ClickToValidation": "Click link verification", + "Validation": "Validation", + "EmailConfirm": "Email confirm", + "EmailConfirmInfo": "We received an email confirmation request! If you initiated this request, please click the link below to confirm the email address.", + "ConfirmMyEmail": "Bind my email address", + "YourEmailIsSuccessfullyConfirm": "Your email address was bound successfully." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/zh-Hans.json b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..bf4fe71f5 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/zh-Hans.json @@ -0,0 +1,16 @@ +{ + "culture": "zh-Hans", + "texts": { + "TextTemplate:Abp.Account.MailSecurityVerifyLink": "邮件验证模板", + "TextTemplate:Abp.Account.MailConfirmLink": "邮件确认模板", + "MailSecurityVerify": "邮件安全验证", + "VerifyMyEmailAddress": "您好

您此次邮件安全验证码如下,请输入验证码进行下一步操作。

如非你本人操作,请忽略此邮件。

", + "MailSecurityVerifyRemarks": "此邮件为系统所发,请勿直接回复。", + "ClickToValidation": "点击进行验证", + "Validation": "验证", + "EmailConfirm": "邮件确认", + "EmailConfirmInfo": "我们收到了邮件确认请求!如果你发起了此请求,请单击以下链接以确认邮件地址.", + "ConfirmMyEmail": "绑定我的邮箱地址", + "YourEmailIsSuccessfullyConfirm": "您的邮件地址绑定成功." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountEmailTemplates.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountEmailTemplates.cs new file mode 100644 index 000000000..f7d1509e5 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountEmailTemplates.cs @@ -0,0 +1,13 @@ +namespace LINGYUN.Abp.Account.Security.Templates; + +public static class AccountEmailTemplates +{ + /// + /// 邮件地址确认 + /// + public const string MailConfirmLink = "Abp.Account.MailConfirmLink"; + /// + /// 邮件安全验证 + /// + public const string MailSecurityVerifyLink = "Abp.Account.MailSecurityVerifyLink"; +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountTemplateDefinitionProvider.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountTemplateDefinitionProvider.cs new file mode 100644 index 000000000..3cfef3389 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountTemplateDefinitionProvider.cs @@ -0,0 +1,32 @@ +using LINGYUN.Abp.Account.Security.Localization; +using Volo.Abp.Emailing.Templates; +using Volo.Abp.Localization; +using Volo.Abp.TextTemplating; + +namespace LINGYUN.Abp.Account.Security.Templates; + +public class AccountTemplateDefinitionProvider : TemplateDefinitionProvider +{ + public override void Define(ITemplateDefinitionContext context) + { + context.Add( + new TemplateDefinition( + AccountEmailTemplates.MailSecurityVerifyLink, + displayName: L($"TextTemplate:{AccountEmailTemplates.MailSecurityVerifyLink}"), + layout: StandardEmailTemplates.Layout, + localizationResource: typeof(AccountSecurityResource) + ).WithVirtualFilePath("/LINGYUN/Abp/Account/Security/Templates/MailSecurityVerify.tpl", true), + new TemplateDefinition( + AccountEmailTemplates.MailConfirmLink, + displayName: L($"TextTemplate:{AccountEmailTemplates.MailConfirmLink}"), + layout: StandardEmailTemplates.Layout, + localizationResource: typeof(AccountSecurityResource) + ).WithVirtualFilePath("/LINGYUN/Abp/Account/Security/Templates/MailConfirm.tpl", true) + ); + } + + private static ILocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailConfirm.tpl b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailConfirm.tpl new file mode 100644 index 000000000..49bdcf0f8 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailConfirm.tpl @@ -0,0 +1,7 @@ +

{{L "EmailConfirm"}}

+ +

{{L "EmailConfirmInfo"}}

+ + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailSecurityVerify.tpl b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailSecurityVerify.tpl new file mode 100644 index 000000000..60a3bbf0a --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailSecurityVerify.tpl @@ -0,0 +1,5 @@ +
+ {{L "VerifyMyEmailAddress"}}{{model.user}} +

{{model.code}}

+ {{L "MailSecurityVerifyRemarks"}} +
\ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs index 827cd6c77..115c671d4 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs @@ -1,4 +1,4 @@ -using LINGYUN.Abp.Account.Emailing; +using LINGYUN.Abp.Account.Security; using LINGYUN.Abp.Account.Web.Bundling; using LINGYUN.Abp.Account.Web.ProfileManagement; using LINGYUN.Abp.Identity; @@ -25,8 +25,8 @@ namespace LINGYUN.Abp.Account.Web; [DependsOn( typeof(AbpSmsModule), typeof(VoloAbpAccountWebModule), + typeof(AbpAccountSecurityModule), typeof(AbpIdentityDomainModule), - typeof(AbpAccountEmailingModule), typeof(AbpIdentityAspNetCoreQrCodeModule), typeof(AbpAccountApplicationContractsModule))] public class AbpAccountWebModule : AbpModule diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/LINGYUN.Abp.Account.Web.csproj b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/LINGYUN.Abp.Account.Web.csproj index 6b5fb6bfd..f3802064a 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/LINGYUN.Abp.Account.Web.csproj +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/LINGYUN.Abp.Account.Web.csproj @@ -44,7 +44,7 @@ - + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml index cdda0a3d3..412060c4e 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml @@ -63,6 +63,7 @@ }
+ @* @L["PlatformLogin"] *@ @L["Login"] @if (Model.ShowCancelButton) { diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendCode.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendCode.cshtml.cs index 2bb13dd6d..cc8fe74b9 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendCode.cshtml.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendCode.cshtml.cs @@ -1,4 +1,4 @@ -using LINGYUN.Abp.Account.Emailing; +using LINGYUN.Abp.Account.Security; using LINGYUN.Abp.Identity.Settings; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; @@ -33,14 +33,14 @@ namespace LINGYUN.Abp.Account.Web.Pages.Account protected ISmsSender SmsSender { get; } - protected IAccountEmailVerifySender AccountEmailVerifySender { get; } + protected IAccountEmailSecurityCodeSender EmailSecurityCodeSender { get; } public SendCodeModel( ISmsSender smsSender, - IAccountEmailVerifySender accountEmailVerifySender) + IAccountEmailSecurityCodeSender emailSecurityCodeSender) { SmsSender = smsSender; - AccountEmailVerifySender = accountEmailVerifySender; + EmailSecurityCodeSender = emailSecurityCodeSender; LocalizationResourceType = typeof(AccountResource); } @@ -91,8 +91,8 @@ namespace LINGYUN.Abp.Account.Web.Pages.Account if (Input.SelectedProvider == "Email") { - await AccountEmailVerifySender - .SendMailLoginVerifyCodeAsync( + await EmailSecurityCodeSender + .SendLoginCodeAsync( code, user.UserName, user.Email);