28 changed files with 452 additions and 68 deletions
@ -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); |
|
||||
} |
|
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,29 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks> |
||||
|
<AssemblyName>LINGYUN.Abp.Account.Security</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.Account.Security</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="LINGYUN\Abp\Account\Security\Templates\*.tpl" /> |
||||
|
<None Remove="LINGYUN\Abp\Account\Security\Localization\Resources\*.json" /> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\Account\Security\Templates\*.tpl" /> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\Account\Security\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Emailing" /> |
||||
|
<PackageReference Include="Volo.Abp.Sms" /> |
||||
|
<PackageReference Include="Volo.Abp.UI.Navigation" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -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<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpAccountSecurityModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<AccountSecurityResource>("en") |
||||
|
.AddVirtualJson("/LINGYUN/Abp/Account/Security/Localization/Resources"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -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<AccountSecurityResource> StringLocalizer { get; } |
||||
|
protected IAppUrlProvider AppUrlProvider { get; } |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
|
||||
|
public AccountEmailSecurityCodeSender( |
||||
|
IEmailSender emailSender, |
||||
|
ICurrentTenant currentTenant, |
||||
|
IAppUrlProvider appUrlProvider, |
||||
|
ITemplateRenderer templateRenderer, |
||||
|
IStringLocalizer<AccountSecurityResource> 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; |
||||
|
} |
||||
|
} |
||||
@ -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"; |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Account.Security; |
||||
|
/// <summary>
|
||||
|
/// 邮件安全码发送接口
|
||||
|
/// </summary>
|
||||
|
public interface IAccountEmailSecurityCodeSender |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 发送邮件登录验证码
|
||||
|
/// </summary>
|
||||
|
/// <param name="code">验证码</param>
|
||||
|
/// <param name="userName">用户名</param>
|
||||
|
/// <param name="emailAddress">邮件地址</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task SendLoginCodeAsync( |
||||
|
string code, |
||||
|
string userName, |
||||
|
string emailAddress); |
||||
|
/// <summary>
|
||||
|
/// 发送邮件确认链接
|
||||
|
/// </summary>
|
||||
|
/// <param name="userId">用户Id</param>
|
||||
|
/// <param name="userEmail">用户邮件</param>
|
||||
|
/// <param name="confirmToken">确认Token</param>
|
||||
|
/// <param name="appName">应用名称</param>
|
||||
|
/// <param name="returnUrl">回调路径</param>
|
||||
|
/// <param name="returnUrlHash">回调路径Hash</param>
|
||||
|
/// <param name="userTenantId">用户租户Id</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task SendConfirmLinkAsync( |
||||
|
Guid userId, |
||||
|
string userEmail, |
||||
|
string confirmToken, |
||||
|
string appName, |
||||
|
string returnUrl = null, |
||||
|
string returnUrlHash = null, |
||||
|
Guid? userTenantId = null |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Account.Security; |
||||
|
/// <summary>
|
||||
|
/// 短信安全码发送接口
|
||||
|
/// </summary>
|
||||
|
public interface IAccountSmsSecurityCodeSender |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 发送短信验证码
|
||||
|
/// </summary>
|
||||
|
/// <param name="phone">手机号</param>
|
||||
|
/// <param name="code">短信验证码</param>
|
||||
|
/// <param name="templateCode">短信验证模板</param>
|
||||
|
/// <param name="cancellation"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task SendAsync( |
||||
|
string phone, |
||||
|
string code, |
||||
|
string templateCode, |
||||
|
CancellationToken cancellation = default); |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Account.Security.Localization; |
||||
|
|
||||
|
[LocalizationResourceName("AbpAccountSecurity")] |
||||
|
public class AccountSecurityResource |
||||
|
{ |
||||
|
} |
||||
@ -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}<br/><p>Your email security verification code is as follows. Please enter the verification code to proceed to the next step.</p><p>If not operated by you, please ignore this email.</p>", |
||||
|
"MailSecurityVerifyRemarks": "<p>(If it is not in the form of a link, copy the address to the browser address bar for further access)</p><p>Thank you for your visit and have a good time!</p>", |
||||
|
"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." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"TextTemplate:Abp.Account.MailSecurityVerifyLink": "邮件验证模板", |
||||
|
"TextTemplate:Abp.Account.MailConfirmLink": "邮件确认模板", |
||||
|
"MailSecurityVerify": "邮件安全验证", |
||||
|
"VerifyMyEmailAddress": "您好<br/><p>您此次邮件安全验证码如下,请输入验证码进行下一步操作。</p><p>如非你本人操作,请忽略此邮件。</p>", |
||||
|
"MailSecurityVerifyRemarks": "此邮件为系统所发,请勿直接回复。", |
||||
|
"ClickToValidation": "点击进行验证", |
||||
|
"Validation": "验证", |
||||
|
"EmailConfirm": "邮件确认", |
||||
|
"EmailConfirmInfo": "我们收到了邮件确认请求!如果你发起了此请求,请单击以下链接以确认邮件地址.", |
||||
|
"ConfirmMyEmail": "绑定我的邮箱地址", |
||||
|
"YourEmailIsSuccessfullyConfirm": "您的邮件地址绑定成功." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
namespace LINGYUN.Abp.Account.Security.Templates; |
||||
|
|
||||
|
public static class AccountEmailTemplates |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 邮件地址确认
|
||||
|
/// </summary>
|
||||
|
public const string MailConfirmLink = "Abp.Account.MailConfirmLink"; |
||||
|
/// <summary>
|
||||
|
/// 邮件安全验证
|
||||
|
/// </summary>
|
||||
|
public const string MailSecurityVerifyLink = "Abp.Account.MailSecurityVerifyLink"; |
||||
|
} |
||||
@ -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<AccountSecurityResource>(name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
<h3>{{L "EmailConfirm"}}</h3> |
||||
|
|
||||
|
<p>{{L "EmailConfirmInfo"}}</p> |
||||
|
|
||||
|
<div> |
||||
|
<a href="{{model.link}}">{{L "ConfirmMyEmail"}}</a> |
||||
|
</div> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<div style="position: absolute;"> |
||||
|
<span>{{L "VerifyMyEmailAddress"}}{{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 "MailSecurityVerifyRemarks"}}</span> |
||||
|
</div> |
||||
Loading…
Reference in new issue