From 3e30d5205513fff5e520991dede9613755573014 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Sat, 17 Feb 2018 21:49:30 -0800 Subject: [PATCH] [Fixes #1639] Reset password doesn't work --- .../Areas/Identity/Pages/Account/Login.cshtml | 2 +- .../Pages/Account/ResetPassword.cshtml.cs | 11 ++--- test/Identity.FunctionalTests/LoginTests.cs | 27 ++++++++++++ .../Pages/Account/ForgotPassword.cs | 32 ++++++++++++++ .../Account/ForgotPasswordConfirmation.cs | 15 +++++++ .../Pages/Account/Login.cs | 11 +++++ .../Pages/Account/ResetPassword.cs | 43 +++++++++++++++++++ .../Account/ResetPasswordConfirmation.cs | 13 ++++++ test/Identity.FunctionalTests/UserStories.cs | 24 ++++++++++- 9 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 test/Identity.FunctionalTests/Pages/Account/ForgotPassword.cs create mode 100644 test/Identity.FunctionalTests/Pages/Account/ForgotPasswordConfirmation.cs create mode 100644 test/Identity.FunctionalTests/Pages/Account/ResetPassword.cs create mode 100644 test/Identity.FunctionalTests/Pages/Account/ResetPasswordConfirmation.cs diff --git a/src/UI/Areas/Identity/Pages/Account/Login.cshtml b/src/UI/Areas/Identity/Pages/Account/Login.cshtml index fe682d8d33..875fe3ee7f 100644 --- a/src/UI/Areas/Identity/Pages/Account/Login.cshtml +++ b/src/UI/Areas/Identity/Pages/Account/Login.cshtml @@ -36,7 +36,7 @@

- Forgot your password? + Forgot your password?

Register as a new user diff --git a/src/UI/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs index 1a6ea168f2..8e8511aff0 100644 --- a/src/UI/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs @@ -31,12 +31,13 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } + [Required] public string Code { get; set; } - public virtual IActionResult OnGet(string code = null) => throw new NotImplementedException(); - - public virtual Task OnPostAsync() => throw new NotImplementedException(); } + public virtual IActionResult OnGet(string code = null) => throw new NotImplementedException(); + + public virtual Task OnPostAsync() => throw new NotImplementedException(); } internal class ResetPasswordModel : ResetPasswordModel where TUser : IdentityUser @@ -48,7 +49,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal _userManager = userManager; } - public IActionResult OnGet(string code = null) + public override IActionResult OnGet(string code = null) { if (code == null) { @@ -64,7 +65,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal } } - public async Task OnPostAsync() + public override async Task OnPostAsync() { if (!ModelState.IsValid) { diff --git a/test/Identity.FunctionalTests/LoginTests.cs b/test/Identity.FunctionalTests/LoginTests.cs index 9fde74fc53..2a9f8209c9 100644 --- a/test/Identity.FunctionalTests/LoginTests.cs +++ b/test/Identity.FunctionalTests/LoginTests.cs @@ -145,5 +145,32 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests await UserStories.RegisterNewUserWithSocialLoginAsync(client, userName, email); await UserStories.LoginWithSocialLoginAsync(newClient, userName); } + + [Fact] + public async Task CanLogInAfterResettingThePassword() + { + // Arrange + var emailSender = new ContosoEmailSender(); + var server = ServerFactory.CreateServer(b => b.ConfigureServices(s => + s.SetupTestEmailSender(emailSender))); + var client = ServerFactory.CreateDefaultClient(server); + var resetPasswordClient = ServerFactory.CreateDefaultClient(server); + var newClient = ServerFactory.CreateDefaultClient(server); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + var newPassword = $"!New.Password1$"; + + await UserStories.RegisterNewUserAsync(client, userName, password); + var registrationEmail = Assert.Single(emailSender.SentEmails); + await UserStories.ConfirmEmailAsync(registrationEmail, client); + + // Act & Assert + await UserStories.ForgotPasswordAsync(resetPasswordClient, userName); + Assert.Equal(2, emailSender.SentEmails.Count); + var email = emailSender.SentEmails[1]; + await UserStories.ResetPasswordAsync(resetPasswordClient, email, userName, newPassword); + await UserStories.LoginExistingUserAsync(newClient, userName, newPassword); + } } } diff --git a/test/Identity.FunctionalTests/Pages/Account/ForgotPassword.cs b/test/Identity.FunctionalTests/Pages/Account/ForgotPassword.cs new file mode 100644 index 0000000000..72d5334d11 --- /dev/null +++ b/test/Identity.FunctionalTests/Pages/Account/ForgotPassword.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using AngleSharp.Dom.Html; + +namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account +{ + public class ForgotPassword : DefaultUIPage + { + private readonly IHtmlFormElement _forgotPasswordForm; + + public ForgotPassword(HttpClient client, IHtmlDocument document, DefaultUIContext context) : base(client, document, context) + { + _forgotPasswordForm = HtmlAssert.HasForm(document); + } + + public async Task SendForgotPasswordAsync(string email) + { + var response = await Client.SendAsync(_forgotPasswordForm, new Dictionary + { + ["Input_Email"] = email + }); + var goToForgotPasswordConfirmation = ResponseAssert.IsRedirect(response); + var forgotPasswordConfirmationResponse = await Client.GetAsync(goToForgotPasswordConfirmation); + var forgotPasswordConfirmation = await ResponseAssert.IsHtmlDocumentAsync(forgotPasswordConfirmationResponse); + + return new ForgotPasswordConfirmation(Client, forgotPasswordConfirmation, Context); + } + } +} diff --git a/test/Identity.FunctionalTests/Pages/Account/ForgotPasswordConfirmation.cs b/test/Identity.FunctionalTests/Pages/Account/ForgotPasswordConfirmation.cs new file mode 100644 index 0000000000..82f0d4acc0 --- /dev/null +++ b/test/Identity.FunctionalTests/Pages/Account/ForgotPasswordConfirmation.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using AngleSharp.Dom.Html; + +namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account +{ + public class ForgotPasswordConfirmation : DefaultUIPage + { + public ForgotPasswordConfirmation(HttpClient client, IHtmlDocument document, DefaultUIContext context) : base(client, document, context) + { + } + } +} diff --git a/test/Identity.FunctionalTests/Pages/Account/Login.cs b/test/Identity.FunctionalTests/Pages/Account/Login.cs index a5792a279b..365cbc1252 100644 --- a/test/Identity.FunctionalTests/Pages/Account/Login.cs +++ b/test/Identity.FunctionalTests/Pages/Account/Login.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using AngleSharp.Dom.Html; +using Microsoft.AspNetCore.Identity.FunctionalTests.Pages.Account; using Xunit; namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account @@ -12,6 +13,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account public class Login : DefaultUIPage { private readonly IHtmlFormElement _loginForm; + private readonly IHtmlAnchorElement _forgotPasswordLink; private readonly IHtmlFormElement _externalLoginForm; private readonly IHtmlElement _contosoButton; @@ -22,6 +24,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account : base(client, login, context) { _loginForm = HtmlAssert.HasForm("#account", login); + _forgotPasswordLink = HtmlAssert.HasLink("#forgot-password", login); if (Context.ContosoLoginEnabled) { _externalLoginForm = HtmlAssert.HasForm("#external-account", login); @@ -40,6 +43,14 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account return new Contoso.Login(Client, contosoLogin, Context); } + public async Task ClickForgotPasswordLinkAsync() + { + var response = await Client.GetAsync(_forgotPasswordLink.Href); + var forgotPassword = await ResponseAssert.IsHtmlDocumentAsync(response); + + return new ForgotPassword(Client, forgotPassword, Context); + } + public async Task LoginValidUserAsync(string userName, string password) { var loggedIn = await SendLoginForm(userName, password); diff --git a/test/Identity.FunctionalTests/Pages/Account/ResetPassword.cs b/test/Identity.FunctionalTests/Pages/Account/ResetPassword.cs new file mode 100644 index 0000000000..f5a1eea932 --- /dev/null +++ b/test/Identity.FunctionalTests/Pages/Account/ResetPassword.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using AngleSharp.Dom.Html; + +namespace Microsoft.AspNetCore.Identity.FunctionalTests.Pages.Account +{ + public class ResetPassword : DefaultUIPage + { + private readonly IHtmlFormElement _resetPasswordForm; + + public ResetPassword(HttpClient client, IHtmlDocument resetPassword, DefaultUIContext context) : base(client, resetPassword, context) + { + _resetPasswordForm = HtmlAssert.HasForm(resetPassword); + } + + internal static async Task CreateAsync(IHtmlAnchorElement link, HttpClient client, DefaultUIContext context) + { + var resetPasswordResponse = await client.GetAsync(link.Href); + var resetPassword = await ResponseAssert.IsHtmlDocumentAsync(resetPasswordResponse); + + return new ResetPassword(client, resetPassword, context); + } + + public async Task SendNewPasswordAsync(string email, string newPassword) + { + var resetPasswordResponse = await Client.SendAsync(_resetPasswordForm, new Dictionary + { + ["Input_Email"] = email, + ["Input_Password"] = newPassword, + ["Input_ConfirmPassword"] = newPassword + }); + + var goToResetPasswordConfirmation = ResponseAssert.IsRedirect(resetPasswordResponse); + var resetPasswordConfirmationResponse = await Client.GetAsync(goToResetPasswordConfirmation); + var resetPasswordConfirmation = await ResponseAssert.IsHtmlDocumentAsync(resetPasswordConfirmationResponse); + + return new ResetPasswordConfirmation(Client, resetPasswordConfirmation, Context); + } + } +} diff --git a/test/Identity.FunctionalTests/Pages/Account/ResetPasswordConfirmation.cs b/test/Identity.FunctionalTests/Pages/Account/ResetPasswordConfirmation.cs new file mode 100644 index 0000000000..1bc03ac447 --- /dev/null +++ b/test/Identity.FunctionalTests/Pages/Account/ResetPasswordConfirmation.cs @@ -0,0 +1,13 @@ +using System.Net.Http; +using AngleSharp.Dom.Html; + +namespace Microsoft.AspNetCore.Identity.FunctionalTests.Pages.Account +{ + public class ResetPasswordConfirmation : DefaultUIPage + { + public ResetPasswordConfirmation(HttpClient client, IHtmlDocument resetPasswordConfirmation, DefaultUIContext context) + : base(client, resetPasswordConfirmation, context) + { + } + } +} \ No newline at end of file diff --git a/test/Identity.FunctionalTests/UserStories.cs b/test/Identity.FunctionalTests/UserStories.cs index 9a42eb403a..c772a6f428 100644 --- a/test/Identity.FunctionalTests/UserStories.cs +++ b/test/Identity.FunctionalTests/UserStories.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Threading.Tasks; using AngleSharp.Dom.Html; using Identity.DefaultUI.WebSite.Services; +using Microsoft.AspNetCore.Identity.FunctionalTests.Account; using Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage; using Microsoft.AspNetCore.Identity.FunctionalTests.Pages.Account; using Xunit; @@ -36,7 +37,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests internal static async Task RegisterNewUserWithSocialLoginAsync(HttpClient client, string userName, string email) { - var index = await Index.CreateAsync(client,new DefaultUIContext().WithSocialLoginEnabled()); + var index = await Index.CreateAsync(client, new DefaultUIContext().WithSocialLoginEnabled()); var login = await index.ClickLoginLinkAsync(); @@ -114,5 +115,26 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests .WithExistingUser() .WithConfirmedEmail()); } + + internal static async Task ForgotPasswordAsync(HttpClient client, string userName) + { + var index = await Index.CreateAsync(client); + + var login = await index.ClickLoginLinkAsync(); + + var forgotPassword = await login.ClickForgotPasswordLinkAsync(); + + return await forgotPassword.SendForgotPasswordAsync(userName); + } + + internal static async Task ResetPasswordAsync(HttpClient client, IdentityEmail resetPasswordEmail, string email, string newPassword) + { + var emailBody = HtmlAssert.IsHtmlFragment(resetPasswordEmail.Body); + var linkElement = HtmlAssert.HasElement("a", emailBody); + var link = Assert.IsAssignableFrom(linkElement); + + var resetPassword = await ResetPassword.CreateAsync(link, client, new DefaultUIContext().WithExistingUser()); + return await resetPassword.SendNewPasswordAsync(email, newPassword); + } } }