Skip to content

Commit

Permalink
Added forget/reset password feature to account module
Browse files Browse the repository at this point in the history
  • Loading branch information
realLiangshiwei committed Aug 20, 2020
1 parent 478e93f commit 8614a45
Show file tree
Hide file tree
Showing 29 changed files with 626 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@ namespace Volo.Abp.Account
public interface IAccountAppService : IApplicationService
{
Task<IdentityUserDto> RegisterAsync(RegisterDto input);

Task SendPasswordResetCodeAsync(SendPasswordResetCodeDto input);

Task ResetPasswordAsync(ResetPasswordDto input);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@
"LoggedOutTitle": "Signed Out",
"LoggedOutText": "You have been signed out and you will be redirected soon.",
"ReturnToText": "Click here to redirect to {0}",
"OrLoginWith": "Or login with;"
"OrLoginWith": "Or login with;",
"ForgotPassword": "Forgot password?",
"SendPasswordResetLink_Information": "A password reset link will be sent to your email to reset your password. If you don't get an email within a few minutes, please re-try.",
"PasswordResetMailSentMessage": "Account recovery email sent to your e-mail address. If you don't see this email in your inbox within 15 minutes, look for it in your junk mail folder. If you find it there, please mark it as -Not Junk-. ",
"ResetPassword": "Reset Password",
"ConfirmPassword": "Confirm (repeat) the password",
"ResetPassword_Information": "Please enter your new password.",
"YourPasswordIsSuccessfullyReset": "Your password is successfully reset.",
"GoToTheApplication": "Go to the application",
"BackToLogin": "Back to login"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@
"DisplayName:Abp.Account.IsSelfRegistrationEnabled": "Je lastna registracija uporabnika omogočena",
"Description:Abp.Account.IsSelfRegistrationEnabled": "Ali lahko uporabnik sam registrira račun.",
"DisplayName:Abp.Account.EnableLocalLogin": "Avtenticirajte se z lokalnim računom",
"Description:Abp.Account.EnableLocalLogin": "Označuje, ali bo strežnik uporabnikom omogočil avtentikacijo z lokalnim računom."
"Description:Abp.Account.EnableLocalLogin": "Označuje, ali bo strežnik uporabnikom omogočil avtentikacijo z lokalnim računom.",
"ForgotPassword": "Ste pozabili geslo?",
"SendPasswordResetLink_Information": "Na vaš e-poštni naslov bo poslana povezava za ponastavitev gesla za ponastavitev gesla. Če v nekaj minutah ne dobite e-poštnega sporočila, poskusite znova.",
"PasswordResetMailSentMessage": "E-poštno sporočilo za obnovitev računa je bilo poslano na vaš e-poštni naslov. Če v 15 minutah tega e-poštnega sporočila ne vidite v mapi »Prejeto«, ga poiščite v mapi z neželeno pošto. Če ga najdete tam, ga prosimo označite kot -Ni neželeno-. ",
"ResetPassword": "Ponastavitev gesla",
"ConfirmPassword": "Potrditev (ponovitev) gesla",
"ResetPassword_Information": "Prosimo vnesite vaše novo geslo.",
"YourPasswordIsSuccessfullyReset": "Vaše geslo je uspešno ponastavljeno.",
"BackToLogin": "Nazaj na prijavo"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@
"Description:Abp.Account.EnableLocalLogin": "Sunucunun, kullanıcıların yerel bir hesapla kimlik doğrulamasına izin verip vermeyeceğini belirtir.",
"LoggedOutTitle": "Çıkış Yaptınız",
"LoggedOutText": "Çıkış yaptınız ve birazdan yönlendirileceksiniz.",
"ReturnToText": "{0} uygulamasına dönmek için tıklayın."
"ReturnToText": "{0} uygulamasına dönmek için tıklayın.",
"ForgotPassword": "Şifremi unuttum",
"SendPasswordResetLink_Information": "E-posta adresinize bir şifre sıfırlama bağlantısı gönderilecektir. Birkaç dakika içerisinde bir e-posta almazsanız lütfen tekrar deneyin.",
"PasswordResetMailSentMessage": "E-posta adresinize bir şifre sıfırlama bağlantısı gönderilmiştir. Lütfen e-posta adresinizi kontrol ediniz. Eğer 15 dakika içinde, bu e-postayı gelen kutusunda bulamazsanız, gereksiz veya istenmeyen e-posta kutularına bakınız.",
"ResetPassword": "Şifre Yenileme",
"ConfirmPassword": "Şifre (tekrar)",
"ResetPassword_Information": "Lütfen yeni şifrenizi belirleyin.",
"YourPasswordIsSuccessfullyReset": "Şifreniz başarıyla sıfırlandı.",
"GoToTheApplication": "Uygulamaya git",
"BackToLogin": "Girişe dön"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@
"Description:Abp.Account.EnableLocalLogin": "服务器是否将允许用户使用本地帐户进行身份验证。",
"LoggedOutTitle": "注销",
"LoggedOutText": "你已成功注销并将马上返回.",
"ReturnToText": "点击此处返回到 {0}"
"ReturnToText": "点击此处返回到 {0}",
"ForgotPassword": "忘记密码?",
"SendPasswordResetLink_Information": "密码重置链接将发送到您的电子邮件以重置密码. 如果您在几分钟内没有收到电子邮件,请重试.",
"PasswordResetMailSentMessage": "帐户恢复电子邮件已发送到您的电子邮件地址. 如果您在15分钟内未在收件箱中看到此电子邮件,请检查垃圾邮件,并标记为非垃圾邮件.",
"ResetPassword": "重设密码",
"ConfirmPassword": "确认(重复)密码",
"ResetPassword_Information": "请输入您的新密码.",
"YourPasswordIsSuccessfullyReset": "您的密码已经被重置成功.",
"GoToTheApplication": "转到应用程序",
"BackToLogin": "返回登录"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Auditing;

namespace Volo.Abp.Account
{
public class ResetPasswordDto
{
public Guid UserId { get; set; }

[Required]
public string ResetToken { get; set; }

[Required]
[DisableAuditing]
public string Password { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Identity;
using Volo.Abp.Validation;

namespace Volo.Abp.Account
{
public class SendPasswordResetCodeDto
{
[Required]
[EmailAddress]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))]
public string Email { get; set; }

[Required]
public string AppName { get; set; }

public string ReturnUrl { get; set; }

public string ReturnUrlHash { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@
<RootNamespace />
</PropertyGroup>

<ItemGroup>
<EmbeddedResource Include="Volo\Abp\Account\Emailing\Templates\*.tpl" />
<None Remove="Volo\Abp\Account\Emailing\Templates\*.tpl" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Text.Encodings.Web" Version="4.7.1" />
<ProjectReference Include="..\Volo.Abp.Account.Application.Contracts\Volo.Abp.Account.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\identity\src\Volo.Abp.Identity.Application\Volo.Abp.Identity.Application.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.UI.Navigation\Volo.Abp.UI.Navigation.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Emailing\Volo.Abp.Emailing.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
using Volo.Abp.Identity;
using Volo.Abp.Emailing;
using Volo.Abp.Identity;
using Volo.Abp.Modularity;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.UI.Navigation;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.VirtualFileSystem;

namespace Volo.Abp.Account
{
[DependsOn(
typeof(AbpAccountApplicationContractsModule),
typeof(AbpIdentityApplicationModule),
typeof(AbpUiNavigationModule)
typeof(AbpUiNavigationModule),
typeof(AbpEmailingModule)
)]
public class AbpAccountApplicationModule : AbpModule
{
Expand All @@ -19,6 +21,11 @@ public override void ConfigureServices(ServiceConfigurationContext context)
{
options.FileSets.AddEmbedded<AbpAccountApplicationModule>();
});

Configure<AppUrlOptions>(options =>
{
options.Applications["MVC"].Urls[AccountUrlNames.PasswordReset] = "Account/ResetPassword";
});
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.Account.Emailing;
using Volo.Abp.Account.Settings;
using Volo.Abp.Application.Services;
using Volo.Abp.Identity;
Expand All @@ -12,12 +13,18 @@ public class AccountAppService : ApplicationService, IAccountAppService
{
protected IIdentityRoleRepository RoleRepository { get; }
protected IdentityUserManager UserManager { get; }
protected IAccountEmailer AccountEmailer { get; }
protected IdentitySecurityLogManager IdentitySecurityLogManager { get; }

public AccountAppService(
IdentityUserManager userManager,
IIdentityRoleRepository roleRepository)
IIdentityRoleRepository roleRepository,
IAccountEmailer accountEmailer,
IdentitySecurityLogManager identitySecurityLogManager)
{
RoleRepository = roleRepository;
AccountEmailer = accountEmailer;
IdentitySecurityLogManager = identitySecurityLogManager;
UserManager = userManager;
}

Expand All @@ -35,6 +42,37 @@ public virtual async Task<IdentityUserDto> RegisterAsync(RegisterDto input)
return ObjectMapper.Map<IdentityUser, IdentityUserDto>(user);
}

public virtual async Task SendPasswordResetCodeAsync(SendPasswordResetCodeDto input)
{
var user = await GetUserByEmail(input.Email);
var resetToken = await UserManager.GeneratePasswordResetTokenAsync(user);
await AccountEmailer.SendPasswordResetLinkAsync(user, resetToken, input.AppName, input.ReturnUrl, input.ReturnUrlHash);
}

public virtual async Task ResetPasswordAsync(ResetPasswordDto input)
{
var user = await UserManager.GetByIdAsync(input.UserId);
(await UserManager.ResetPasswordAsync(user, input.ResetToken, input.Password)).CheckErrors();

await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = IdentitySecurityLogActionConsts.ChangePassword
});
}

protected virtual async Task<IdentityUser> GetUserByEmail(string email)
{
var user = await UserManager.FindByEmailAsync(email);
if (user == null)
{
throw new BusinessException("Volo.Account:InvalidEmailAddress")
.WithData("Email", email);
}

return user;
}

protected virtual async Task CheckSelfRegistrationAsync()
{
if (!await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
Expand All @@ -43,4 +81,4 @@ protected virtual async Task CheckSelfRegistrationAsync()
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Volo.Abp.Account
{
public static class AccountUrlNames
{
public const string PasswordReset = "Abp.Account.PasswordReset";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System;
using System.Diagnostics;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using System.Web;
using Microsoft.Extensions.Localization;
using Volo.Abp.Account.Emailing.Templates;
using Volo.Abp.Account.Localization;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Emailing;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.TextTemplating;
using Volo.Abp.UI.Navigation.Urls;

namespace Volo.Abp.Account.Emailing
{
public class AccountEmailer : IAccountEmailer, ITransientDependency
{
protected ITemplateRenderer TemplateRenderer { get; }
protected IEmailSender EmailSender { get; }
protected IStringLocalizer<AccountResource> StringLocalizer { get; }
protected IAppUrlProvider AppUrlProvider { get; }
protected ICurrentTenant CurrentTenant { get; }

public AccountEmailer(
IEmailSender emailSender,
ITemplateRenderer templateRenderer,
IStringLocalizer<AccountResource> stringLocalizer,
IAppUrlProvider appUrlProvider,
ICurrentTenant currentTenant)
{
EmailSender = emailSender;
StringLocalizer = stringLocalizer;
AppUrlProvider = appUrlProvider;
CurrentTenant = currentTenant;
TemplateRenderer = templateRenderer;
}

public virtual async Task SendPasswordResetLinkAsync(
IdentityUser user,
string resetToken,
string appName,
string returnUrl = null,
string returnUrlHash = null)
{
Debug.Assert(CurrentTenant.Id == user.TenantId, "This method can only work for current tenant!");

var url = await AppUrlProvider.GetResetPasswordUrlAsync(appName);

var link = $"{url}?userId={user.Id}&tenantId={user.TenantId}&resetToken={UrlEncoder.Default.Encode(resetToken)}";

if (!returnUrl.IsNullOrEmpty())
{
link += "&returnUrl=" + NormalizeReturnUrl(returnUrl);
}

if (!returnUrlHash.IsNullOrEmpty())
{
link += "&returnUrlHash=" + returnUrlHash;
}

var emailContent = await TemplateRenderer.RenderAsync(
AccountEmailTemplates.PasswordResetLink,
new { link = link }
);

await EmailSender.SendAsync(
user.Email,
StringLocalizer["PasswordReset"],
emailContent
);
}

private 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]);
}
}
}
}
}

return returnUrl;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Threading.Tasks;
using Volo.Abp.UI.Navigation.Urls;

namespace Volo.Abp.Account.Emailing
{
public static class AppUrlProviderAccountExtensions
{
public static Task<string> GetResetPasswordUrlAsync(this IAppUrlProvider appUrlProvider, string appName)
{
return appUrlProvider.GetUrlAsync(appName, AccountUrlNames.PasswordReset);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Threading.Tasks;
using Volo.Abp.Identity;

namespace Volo.Abp.Account.Emailing
{
public interface IAccountEmailer
{
Task SendPasswordResetLinkAsync(
IdentityUser user,
string resetToken,
string appName,
string returnUrl = null,
string returnUrlHash = null
);
}
}
Loading

0 comments on commit 8614a45

Please sign in to comment.