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);
+ }
}
}