From 32540d570777b29a785a6eaaed16962bffd56350 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Wed, 18 Sep 2019 14:14:31 -0700 Subject: [PATCH 1/7] Fix resend email confirmation Update src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml Co-Authored-By: campersau Resend not reset Update ref Add new email confirmation link to templates test Fix test Revert website change --- ...osoft.AspNetCore.Identity.UI.netcoreapp.cs | 32 ++++++ .../Identity/Pages/V3/Account/Login.cshtml | 2 +- .../Identity/Pages/V3/Account/Login.cshtml.cs | 29 ----- .../V3/Account/ResendEmailConfirmation.cshtml | 26 +++++ .../Account/ResendEmailConfirmation.cshtml.cs | 103 ++++++++++++++++++ .../Identity/Pages/V4/Account/Login.cshtml | 3 + .../V4/Account/ResendEmailConfirmation.cshtml | 26 +++++ .../Account/ResendEmailConfirmation.cshtml.cs | 103 ++++++++++++++++++ .../IdentitySample.DefaultUI/web.config | 8 +- .../Identity.FunctionalTests/LoginTests.cs | 27 +++++ .../Pages/Account/Login.cs | 10 ++ .../Pages/Account/ResendEmailConfirmation.cs | 26 +++++ .../Identity.FunctionalTests/UserStories.cs | 10 ++ src/ProjectTemplates/test/Helpers/PageUrls.cs | 1 + src/ProjectTemplates/test/MvcTemplateTest.cs | 1 + .../test/RazorPagesTemplateTest.cs | 1 + 16 files changed, 376 insertions(+), 32 deletions(-) create mode 100644 src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml create mode 100644 src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs create mode 100644 src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml create mode 100644 src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs create mode 100644 src/Identity/test/Identity.FunctionalTests/Pages/Account/ResendEmailConfirmation.cs diff --git a/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs b/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs index 946e9c778cd5..416de9f01409 100644 --- a/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs +++ b/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs @@ -208,6 +208,22 @@ public InputModel() { } } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] + public abstract partial class ResendEmailConfirmationModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel + { + protected ResendEmailConfirmationModel() { } + [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] + public Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal.ResendEmailConfirmationModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual void OnGet() { } + public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } + public partial class InputModel + { + public InputModel() { } + [System.ComponentModel.DataAnnotations.EmailAddressAttribute] + [System.ComponentModel.DataAnnotations.RequiredAttribute] + public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + } + [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] public partial class ResetPasswordConfirmationModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { public ResetPasswordConfirmationModel() { } @@ -658,6 +674,22 @@ public InputModel() { } } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] + public abstract partial class ResendEmailConfirmationModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel + { + protected ResendEmailConfirmationModel() { } + [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] + public Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal.ResendEmailConfirmationModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual void OnGet() { } + public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } + public partial class InputModel + { + public InputModel() { } + [System.ComponentModel.DataAnnotations.EmailAddressAttribute] + [System.ComponentModel.DataAnnotations.RequiredAttribute] + public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + } + [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] public partial class ResetPasswordConfirmationModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { public ResetPasswordConfirmationModel() { } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml index a58edae8a87a..e17a218ba27b 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml @@ -42,7 +42,7 @@ Register as a new user

- + Resend email confirmation

diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs index 74ec7bb1cd89..6006fb7f7f8f 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs @@ -169,34 +169,5 @@ public override async Task OnPostAsync(string returnUrl = null) // If we got this far, something failed, redisplay form return Page(); } - - public override async Task OnPostSendVerificationEmailAsync() - { - if (!ModelState.IsValid) - { - return Page(); - } - - var user = await _userManager.FindByEmailAsync(Input.Email); - if (user == null) - { - ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); - } - - var userId = await _userManager.GetUserIdAsync(user); - var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); - var callbackUrl = Url.Page( - "/Account/ConfirmEmail", - pageHandler: null, - values: new { userId = userId, code = code }, - protocol: Request.Scheme); - await _emailSender.SendEmailAsync( - Input.Email, - "Confirm your email", - $"Please confirm your account by clicking here."); - - ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); - return Page(); - } } } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml new file mode 100644 index 000000000000..910891fb4682 --- /dev/null +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml @@ -0,0 +1,26 @@ +@page +@model ResendEmailConfirmationModel +@{ + ViewData["Title"] = "Resend email confirmation"; +} + +

@ViewData["Title"]

+

Enter your email.

+
+
+
+
+
+
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs new file mode 100644 index 000000000000..7ed2a11e5db0 --- /dev/null +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs @@ -0,0 +1,103 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal +{ + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [AllowAnonymous] + [IdentityDefaultUI(typeof(ResendEmailConfirmationModel<>))] + public abstract class ResendEmailConfirmationModel : PageModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [EmailAddress] + public string Email { get; set; } + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual void OnGet() => throw new NotImplementedException(); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class ResendEmailConfirmationModel : ResendEmailConfirmationModel where TUser : class + { + private readonly UserManager _userManager; + private readonly IEmailSender _emailSender; + + public ResendEmailConfirmationModel(UserManager userManager, IEmailSender emailSender) + { + _userManager = userManager; + _emailSender = emailSender; + } + + public override void OnGet() + { + } + + public override async Task OnPostAsync() + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _userManager.FindByEmailAsync(Input.Email); + if (user == null) + { + ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); + return Page(); + } + + var userId = await _userManager.GetUserIdAsync(user); + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + var callbackUrl = Url.Page( + "/Account/ConfirmEmail", + pageHandler: null, + values: new { userId = userId, code = code }, + protocol: Request.Scheme); + await _emailSender.SendEmailAsync( + Input.Email, + "Confirm your email", + $"Please confirm your account by clicking here."); + + ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); + return Page(); + } + } +} diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml index b98655e0f629..8f6f28a3a55c 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml @@ -41,6 +41,9 @@

Register as a new user

+

+ Resend email confirmation +

diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml new file mode 100644 index 000000000000..16d11e9d8ebd --- /dev/null +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml @@ -0,0 +1,26 @@ +@page +@model ResendEmailConfirmationModel +@{ + ViewData["Title"] = "Resend email confirmation"; +} + +

@ViewData["Title"]

+

Enter your email.

+
+
+
+
+
+
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs new file mode 100644 index 000000000000..0635782a4202 --- /dev/null +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs @@ -0,0 +1,103 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal +{ + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [AllowAnonymous] + [IdentityDefaultUI(typeof(ResendEmailConfirmationModel<>))] + public abstract class ResendEmailConfirmationModel : PageModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [EmailAddress] + public string Email { get; set; } + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual void OnGet() => throw new NotImplementedException(); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class ResendEmailConfirmationModel : ResendEmailConfirmationModel where TUser : class + { + private readonly UserManager _userManager; + private readonly IEmailSender _emailSender; + + public ResendEmailConfirmationModel(UserManager userManager, IEmailSender emailSender) + { + _userManager = userManager; + _emailSender = emailSender; + } + + public override void OnGet() + { + } + + public override async Task OnPostAsync() + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _userManager.FindByEmailAsync(Input.Email); + if (user == null) + { + ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); + return Page(); + } + + var userId = await _userManager.GetUserIdAsync(user); + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + var callbackUrl = Url.Page( + "/Account/ConfirmEmail", + pageHandler: null, + values: new { userId = userId, code = code }, + protocol: Request.Scheme); + await _emailSender.SendEmailAsync( + Input.Email, + "Confirm your email", + $"Please confirm your account by clicking here."); + + ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); + return Page(); + } + } +} diff --git a/src/Identity/samples/IdentitySample.DefaultUI/web.config b/src/Identity/samples/IdentitySample.DefaultUI/web.config index f7ac679334bc..6d25118e27e6 100644 --- a/src/Identity/samples/IdentitySample.DefaultUI/web.config +++ b/src/Identity/samples/IdentitySample.DefaultUI/web.config @@ -2,8 +2,12 @@ - + - + + + + + \ No newline at end of file diff --git a/src/Identity/test/Identity.FunctionalTests/LoginTests.cs b/src/Identity/test/Identity.FunctionalTests/LoginTests.cs index 8a5125495ab2..bb7529a3a6c2 100644 --- a/src/Identity/test/Identity.FunctionalTests/LoginTests.cs +++ b/src/Identity/test/Identity.FunctionalTests/LoginTests.cs @@ -230,6 +230,33 @@ void ConfigureTestServices(IServiceCollection services) => services await UserStories.LoginExistingUserAsync(newClient, userName, password); } + [Fact] + public async Task CanResendConfirmingEmail() + { + // Arrange + var emailSender = new ContosoEmailSender(); + void ConfigureTestServices(IServiceCollection services) => services + .SetupTestEmailSender(emailSender) + .SetupEmailRequired(); + + var server = ServerFactory.WithWebHostBuilder(whb => whb.ConfigureServices(ConfigureTestServices)); + + var client = server.CreateClient(); + var newClient = server.CreateClient(); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + + var loggedIn = await UserStories.RegisterNewUserAsync(client, userName, password); + + // Act & Assert + // Use a new client to simulate a new browser session. + await UserStories.ResendConfirmEmailAsync(server.CreateClient(), userName); + Assert.Equal(2, emailSender.SentEmails.Count); + var email = emailSender.SentEmails.Last(); + await UserStories.ConfirmEmailAsync(email, newClient); + } + [Fact] public async Task CanLogInAfterConfirmingEmail_WithGlobalAuthorizeFilter() { diff --git a/src/Identity/test/Identity.FunctionalTests/Pages/Account/Login.cs b/src/Identity/test/Identity.FunctionalTests/Pages/Account/Login.cs index 7db145000fa5..6960ac89645f 100644 --- a/src/Identity/test/Identity.FunctionalTests/Pages/Account/Login.cs +++ b/src/Identity/test/Identity.FunctionalTests/Pages/Account/Login.cs @@ -13,6 +13,7 @@ public class Login : DefaultUIPage { private readonly IHtmlFormElement _loginForm; private readonly IHtmlAnchorElement _forgotPasswordLink; + private readonly IHtmlAnchorElement _reconfirmLink; private readonly IHtmlFormElement _externalLoginForm; private readonly IHtmlElement _contosoButton; private readonly IHtmlElement _loginButton; @@ -26,6 +27,7 @@ public Login( _loginForm = HtmlAssert.HasForm("#account", login); _loginButton = HtmlAssert.HasElement("#login-submit", login); _forgotPasswordLink = HtmlAssert.HasLink("#forgot-password", login); + _reconfirmLink = HtmlAssert.HasLink("#resend-confirmation", login); if (Context.ContosoLoginEnabled) { _externalLoginForm = HtmlAssert.HasForm("#external-account", login); @@ -52,6 +54,14 @@ public async Task ClickForgotPasswordLinkAsync() return new ForgotPassword(Client, forgotPassword, Context); } + public async Task ClickReconfirmEmailLinkAsync() + { + var response = await Client.GetAsync(_reconfirmLink.Href); + var forgotPassword = await ResponseAssert.IsHtmlDocumentAsync(response); + + return new ResendEmailConfirmation(Client, forgotPassword, Context); + } + public async Task LoginValidUserAsync(string userName, string password) { var loggedIn = await SendLoginForm(userName, password); diff --git a/src/Identity/test/Identity.FunctionalTests/Pages/Account/ResendEmailConfirmation.cs b/src/Identity/test/Identity.FunctionalTests/Pages/Account/ResendEmailConfirmation.cs new file mode 100644 index 000000000000..d41cb142d8b0 --- /dev/null +++ b/src/Identity/test/Identity.FunctionalTests/Pages/Account/ResendEmailConfirmation.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using AngleSharp.Dom.Html; + +namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account +{ + public class ResendEmailConfirmation : DefaultUIPage + { + private readonly IHtmlFormElement _resendForm; + + public ResendEmailConfirmation(HttpClient client, IHtmlDocument document, DefaultUIContext context) : base(client, document, context) + { + _resendForm = HtmlAssert.HasForm(document); + } + + public Task ResendAsync(string email) + => Client.SendAsync(_resendForm, new Dictionary + { + ["Input_Email"] = email + }); + } +} diff --git a/src/Identity/test/Identity.FunctionalTests/UserStories.cs b/src/Identity/test/Identity.FunctionalTests/UserStories.cs index 2cc1cde82275..2610139fe053 100644 --- a/src/Identity/test/Identity.FunctionalTests/UserStories.cs +++ b/src/Identity/test/Identity.FunctionalTests/UserStories.cs @@ -194,6 +194,16 @@ internal static async Task ConfirmEmailAsync(IdentityEmail email, .WithConfirmedEmail()); } + internal static async Task ResendConfirmEmailAsync(HttpClient client, string email) + { + var index = await Index.CreateAsync(client); + var login = await index.ClickLoginLinkAsync(); + var reconfirm = await login.ClickReconfirmEmailLinkAsync(); + var response = await reconfirm.ResendAsync(email); + ResponseAssert.IsOK(response); + Assert.Contains("Verification email sent.", await response.Content.ReadAsStringAsync()); + } + internal static async Task ForgotPasswordAsync(HttpClient client, string userName) { var index = await Index.CreateAsync(client); diff --git a/src/ProjectTemplates/test/Helpers/PageUrls.cs b/src/ProjectTemplates/test/Helpers/PageUrls.cs index afb0783cbc4d..deb12fb16c65 100644 --- a/src/ProjectTemplates/test/Helpers/PageUrls.cs +++ b/src/ProjectTemplates/test/Helpers/PageUrls.cs @@ -12,6 +12,7 @@ public static class PageUrls public const string LoginUrl = "/Identity/Account/Login"; public const string RegisterUrl = "/Identity/Account/Register"; public const string ForgotPassword = "/Identity/Account/ForgotPassword"; + public const string ResendEmailConfirmation = "/Identity/Account/ResendEmailConfirmation"; public const string ExternalArticle = "https://go.microsoft.com/fwlink/?LinkID=532715"; } } diff --git a/src/ProjectTemplates/test/MvcTemplateTest.cs b/src/ProjectTemplates/test/MvcTemplateTest.cs index 95f6ce784add..cb83a5e06e3d 100644 --- a/src/ProjectTemplates/test/MvcTemplateTest.cs +++ b/src/ProjectTemplates/test/MvcTemplateTest.cs @@ -175,6 +175,7 @@ public async Task MvcTemplate_IndividualAuthImplAsync(bool useLocalDB) PageUrls.PrivacyUrl, PageUrls.ForgotPassword, PageUrls.RegisterUrl, + PageUrls.ResendEmailConfirmation, PageUrls.ExternalArticle, PageUrls.PrivacyUrl } }, diff --git a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs index 637d7179b571..bd9782b99b3c 100644 --- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs @@ -174,6 +174,7 @@ public async Task RazorPagesTemplate_IndividualAuthImplAsync(bool useLocalDB) PageUrls.PrivacyUrl, PageUrls.ForgotPassword, PageUrls.RegisterUrl, + PageUrls.ResendEmailConfirmation, PageUrls.ExternalArticle, PageUrls.PrivacyUrl } }, From 79e68d6e40ad50afdc5db5991a413f67e14f0f39 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Wed, 18 Sep 2019 14:14:31 -0700 Subject: [PATCH 2/7] Fix resend email confirmation Update src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml Co-Authored-By: campersau Resend not reset Update ref Add new email confirmation link to templates test Fix test Revert website change --- ...osoft.AspNetCore.Identity.UI.netcoreapp.cs | 32 ++++++ .../Identity/Pages/V3/Account/Login.cshtml | 2 +- .../Identity/Pages/V3/Account/Login.cshtml.cs | 29 ----- .../V3/Account/ResendEmailConfirmation.cshtml | 26 +++++ .../Account/ResendEmailConfirmation.cshtml.cs | 103 ++++++++++++++++++ .../Identity/Pages/V4/Account/Login.cshtml | 3 + .../V4/Account/ResendEmailConfirmation.cshtml | 26 +++++ .../Account/ResendEmailConfirmation.cshtml.cs | 103 ++++++++++++++++++ .../IdentitySample.DefaultUI/web.config | 8 +- .../Identity.FunctionalTests/LoginTests.cs | 27 +++++ .../Pages/Account/Login.cs | 10 ++ .../Pages/Account/ResendEmailConfirmation.cs | 26 +++++ .../Identity.FunctionalTests/UserStories.cs | 10 ++ src/ProjectTemplates/test/Helpers/PageUrls.cs | 1 + src/ProjectTemplates/test/MvcTemplateTest.cs | 1 + .../test/RazorPagesTemplateTest.cs | 1 + 16 files changed, 376 insertions(+), 32 deletions(-) create mode 100644 src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml create mode 100644 src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs create mode 100644 src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml create mode 100644 src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs create mode 100644 src/Identity/test/Identity.FunctionalTests/Pages/Account/ResendEmailConfirmation.cs diff --git a/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs b/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs index 946e9c778cd5..416de9f01409 100644 --- a/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs +++ b/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs @@ -208,6 +208,22 @@ public InputModel() { } } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] + public abstract partial class ResendEmailConfirmationModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel + { + protected ResendEmailConfirmationModel() { } + [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] + public Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal.ResendEmailConfirmationModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual void OnGet() { } + public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } + public partial class InputModel + { + public InputModel() { } + [System.ComponentModel.DataAnnotations.EmailAddressAttribute] + [System.ComponentModel.DataAnnotations.RequiredAttribute] + public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + } + [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] public partial class ResetPasswordConfirmationModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { public ResetPasswordConfirmationModel() { } @@ -658,6 +674,22 @@ public InputModel() { } } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] + public abstract partial class ResendEmailConfirmationModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel + { + protected ResendEmailConfirmationModel() { } + [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] + public Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal.ResendEmailConfirmationModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual void OnGet() { } + public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } + public partial class InputModel + { + public InputModel() { } + [System.ComponentModel.DataAnnotations.EmailAddressAttribute] + [System.ComponentModel.DataAnnotations.RequiredAttribute] + public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + } + [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] public partial class ResetPasswordConfirmationModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { public ResetPasswordConfirmationModel() { } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml index 8ebfba2d1bdf..8f4559fb78e3 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml @@ -42,7 +42,7 @@ Register as a new user

- + Resend email confirmation

diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs index 74ec7bb1cd89..6006fb7f7f8f 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml.cs @@ -169,34 +169,5 @@ public override async Task OnPostAsync(string returnUrl = null) // If we got this far, something failed, redisplay form return Page(); } - - public override async Task OnPostSendVerificationEmailAsync() - { - if (!ModelState.IsValid) - { - return Page(); - } - - var user = await _userManager.FindByEmailAsync(Input.Email); - if (user == null) - { - ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); - } - - var userId = await _userManager.GetUserIdAsync(user); - var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); - var callbackUrl = Url.Page( - "/Account/ConfirmEmail", - pageHandler: null, - values: new { userId = userId, code = code }, - protocol: Request.Scheme); - await _emailSender.SendEmailAsync( - Input.Email, - "Confirm your email", - $"Please confirm your account by clicking here."); - - ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); - return Page(); - } } } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml new file mode 100644 index 000000000000..910891fb4682 --- /dev/null +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml @@ -0,0 +1,26 @@ +@page +@model ResendEmailConfirmationModel +@{ + ViewData["Title"] = "Resend email confirmation"; +} + +

@ViewData["Title"]

+

Enter your email.

+
+
+
+
+
+
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs new file mode 100644 index 000000000000..7ed2a11e5db0 --- /dev/null +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs @@ -0,0 +1,103 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal +{ + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [AllowAnonymous] + [IdentityDefaultUI(typeof(ResendEmailConfirmationModel<>))] + public abstract class ResendEmailConfirmationModel : PageModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [EmailAddress] + public string Email { get; set; } + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual void OnGet() => throw new NotImplementedException(); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class ResendEmailConfirmationModel : ResendEmailConfirmationModel where TUser : class + { + private readonly UserManager _userManager; + private readonly IEmailSender _emailSender; + + public ResendEmailConfirmationModel(UserManager userManager, IEmailSender emailSender) + { + _userManager = userManager; + _emailSender = emailSender; + } + + public override void OnGet() + { + } + + public override async Task OnPostAsync() + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _userManager.FindByEmailAsync(Input.Email); + if (user == null) + { + ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); + return Page(); + } + + var userId = await _userManager.GetUserIdAsync(user); + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + var callbackUrl = Url.Page( + "/Account/ConfirmEmail", + pageHandler: null, + values: new { userId = userId, code = code }, + protocol: Request.Scheme); + await _emailSender.SendEmailAsync( + Input.Email, + "Confirm your email", + $"Please confirm your account by clicking here."); + + ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); + return Page(); + } + } +} diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml index 7db27382777b..47b87509561e 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml @@ -41,6 +41,9 @@

Register as a new user

+

+ Resend email confirmation +

diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml new file mode 100644 index 000000000000..16d11e9d8ebd --- /dev/null +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml @@ -0,0 +1,26 @@ +@page +@model ResendEmailConfirmationModel +@{ + ViewData["Title"] = "Resend email confirmation"; +} + +

@ViewData["Title"]

+

Enter your email.

+
+
+
+
+
+
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs new file mode 100644 index 000000000000..0635782a4202 --- /dev/null +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs @@ -0,0 +1,103 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal +{ + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [AllowAnonymous] + [IdentityDefaultUI(typeof(ResendEmailConfirmationModel<>))] + public abstract class ResendEmailConfirmationModel : PageModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [EmailAddress] + public string Email { get; set; } + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual void OnGet() => throw new NotImplementedException(); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class ResendEmailConfirmationModel : ResendEmailConfirmationModel where TUser : class + { + private readonly UserManager _userManager; + private readonly IEmailSender _emailSender; + + public ResendEmailConfirmationModel(UserManager userManager, IEmailSender emailSender) + { + _userManager = userManager; + _emailSender = emailSender; + } + + public override void OnGet() + { + } + + public override async Task OnPostAsync() + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _userManager.FindByEmailAsync(Input.Email); + if (user == null) + { + ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); + return Page(); + } + + var userId = await _userManager.GetUserIdAsync(user); + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + var callbackUrl = Url.Page( + "/Account/ConfirmEmail", + pageHandler: null, + values: new { userId = userId, code = code }, + protocol: Request.Scheme); + await _emailSender.SendEmailAsync( + Input.Email, + "Confirm your email", + $"Please confirm your account by clicking here."); + + ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); + return Page(); + } + } +} diff --git a/src/Identity/samples/IdentitySample.DefaultUI/web.config b/src/Identity/samples/IdentitySample.DefaultUI/web.config index f7ac679334bc..6d25118e27e6 100644 --- a/src/Identity/samples/IdentitySample.DefaultUI/web.config +++ b/src/Identity/samples/IdentitySample.DefaultUI/web.config @@ -2,8 +2,12 @@ - + - + + + + + \ No newline at end of file diff --git a/src/Identity/test/Identity.FunctionalTests/LoginTests.cs b/src/Identity/test/Identity.FunctionalTests/LoginTests.cs index 8a5125495ab2..bb7529a3a6c2 100644 --- a/src/Identity/test/Identity.FunctionalTests/LoginTests.cs +++ b/src/Identity/test/Identity.FunctionalTests/LoginTests.cs @@ -230,6 +230,33 @@ void ConfigureTestServices(IServiceCollection services) => services await UserStories.LoginExistingUserAsync(newClient, userName, password); } + [Fact] + public async Task CanResendConfirmingEmail() + { + // Arrange + var emailSender = new ContosoEmailSender(); + void ConfigureTestServices(IServiceCollection services) => services + .SetupTestEmailSender(emailSender) + .SetupEmailRequired(); + + var server = ServerFactory.WithWebHostBuilder(whb => whb.ConfigureServices(ConfigureTestServices)); + + var client = server.CreateClient(); + var newClient = server.CreateClient(); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + + var loggedIn = await UserStories.RegisterNewUserAsync(client, userName, password); + + // Act & Assert + // Use a new client to simulate a new browser session. + await UserStories.ResendConfirmEmailAsync(server.CreateClient(), userName); + Assert.Equal(2, emailSender.SentEmails.Count); + var email = emailSender.SentEmails.Last(); + await UserStories.ConfirmEmailAsync(email, newClient); + } + [Fact] public async Task CanLogInAfterConfirmingEmail_WithGlobalAuthorizeFilter() { diff --git a/src/Identity/test/Identity.FunctionalTests/Pages/Account/Login.cs b/src/Identity/test/Identity.FunctionalTests/Pages/Account/Login.cs index 7db145000fa5..6960ac89645f 100644 --- a/src/Identity/test/Identity.FunctionalTests/Pages/Account/Login.cs +++ b/src/Identity/test/Identity.FunctionalTests/Pages/Account/Login.cs @@ -13,6 +13,7 @@ public class Login : DefaultUIPage { private readonly IHtmlFormElement _loginForm; private readonly IHtmlAnchorElement _forgotPasswordLink; + private readonly IHtmlAnchorElement _reconfirmLink; private readonly IHtmlFormElement _externalLoginForm; private readonly IHtmlElement _contosoButton; private readonly IHtmlElement _loginButton; @@ -26,6 +27,7 @@ public Login( _loginForm = HtmlAssert.HasForm("#account", login); _loginButton = HtmlAssert.HasElement("#login-submit", login); _forgotPasswordLink = HtmlAssert.HasLink("#forgot-password", login); + _reconfirmLink = HtmlAssert.HasLink("#resend-confirmation", login); if (Context.ContosoLoginEnabled) { _externalLoginForm = HtmlAssert.HasForm("#external-account", login); @@ -52,6 +54,14 @@ public async Task ClickForgotPasswordLinkAsync() return new ForgotPassword(Client, forgotPassword, Context); } + public async Task ClickReconfirmEmailLinkAsync() + { + var response = await Client.GetAsync(_reconfirmLink.Href); + var forgotPassword = await ResponseAssert.IsHtmlDocumentAsync(response); + + return new ResendEmailConfirmation(Client, forgotPassword, Context); + } + public async Task LoginValidUserAsync(string userName, string password) { var loggedIn = await SendLoginForm(userName, password); diff --git a/src/Identity/test/Identity.FunctionalTests/Pages/Account/ResendEmailConfirmation.cs b/src/Identity/test/Identity.FunctionalTests/Pages/Account/ResendEmailConfirmation.cs new file mode 100644 index 000000000000..d41cb142d8b0 --- /dev/null +++ b/src/Identity/test/Identity.FunctionalTests/Pages/Account/ResendEmailConfirmation.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using AngleSharp.Dom.Html; + +namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account +{ + public class ResendEmailConfirmation : DefaultUIPage + { + private readonly IHtmlFormElement _resendForm; + + public ResendEmailConfirmation(HttpClient client, IHtmlDocument document, DefaultUIContext context) : base(client, document, context) + { + _resendForm = HtmlAssert.HasForm(document); + } + + public Task ResendAsync(string email) + => Client.SendAsync(_resendForm, new Dictionary + { + ["Input_Email"] = email + }); + } +} diff --git a/src/Identity/test/Identity.FunctionalTests/UserStories.cs b/src/Identity/test/Identity.FunctionalTests/UserStories.cs index 2cc1cde82275..2610139fe053 100644 --- a/src/Identity/test/Identity.FunctionalTests/UserStories.cs +++ b/src/Identity/test/Identity.FunctionalTests/UserStories.cs @@ -194,6 +194,16 @@ internal static async Task ConfirmEmailAsync(IdentityEmail email, .WithConfirmedEmail()); } + internal static async Task ResendConfirmEmailAsync(HttpClient client, string email) + { + var index = await Index.CreateAsync(client); + var login = await index.ClickLoginLinkAsync(); + var reconfirm = await login.ClickReconfirmEmailLinkAsync(); + var response = await reconfirm.ResendAsync(email); + ResponseAssert.IsOK(response); + Assert.Contains("Verification email sent.", await response.Content.ReadAsStringAsync()); + } + internal static async Task ForgotPasswordAsync(HttpClient client, string userName) { var index = await Index.CreateAsync(client); diff --git a/src/ProjectTemplates/test/Helpers/PageUrls.cs b/src/ProjectTemplates/test/Helpers/PageUrls.cs index afb0783cbc4d..deb12fb16c65 100644 --- a/src/ProjectTemplates/test/Helpers/PageUrls.cs +++ b/src/ProjectTemplates/test/Helpers/PageUrls.cs @@ -12,6 +12,7 @@ public static class PageUrls public const string LoginUrl = "/Identity/Account/Login"; public const string RegisterUrl = "/Identity/Account/Register"; public const string ForgotPassword = "/Identity/Account/ForgotPassword"; + public const string ResendEmailConfirmation = "/Identity/Account/ResendEmailConfirmation"; public const string ExternalArticle = "https://go.microsoft.com/fwlink/?LinkID=532715"; } } diff --git a/src/ProjectTemplates/test/MvcTemplateTest.cs b/src/ProjectTemplates/test/MvcTemplateTest.cs index 9fc1ab4a043f..fea6d42783cd 100644 --- a/src/ProjectTemplates/test/MvcTemplateTest.cs +++ b/src/ProjectTemplates/test/MvcTemplateTest.cs @@ -183,6 +183,7 @@ public async Task MvcTemplate_IndividualAuth(bool useLocalDB) PageUrls.PrivacyUrl, PageUrls.ForgotPassword, PageUrls.RegisterUrl, + PageUrls.ResendEmailConfirmation, PageUrls.ExternalArticle, PageUrls.PrivacyUrl } }, diff --git a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs index 7cd41d56b26e..ad7c967d48d6 100644 --- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs @@ -172,6 +172,7 @@ public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB) PageUrls.PrivacyUrl, PageUrls.ForgotPassword, PageUrls.RegisterUrl, + PageUrls.ResendEmailConfirmation, PageUrls.ExternalArticle, PageUrls.PrivacyUrl } }, From 97b7b01116b30179d5bbf40bf0fff42ad70062d3 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Wed, 2 Oct 2019 22:36:43 -0700 Subject: [PATCH 3/7] Encode the code --- .../Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs index 7ed2a11e5db0..54e6afb839d2 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.WebUtilities; namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal { @@ -86,6 +87,7 @@ public override async Task OnPostAsync() var userId = await _userManager.GetUserIdAsync(user); var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); var callbackUrl = Url.Page( "/Account/ConfirmEmail", pageHandler: null, From 79dbae2000925827705b2631d49c8ff0b469ab3d Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Wed, 2 Oct 2019 22:37:18 -0700 Subject: [PATCH 4/7] Fix V4 encoding --- .../Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs index 0635782a4202..7e9c9b69b9f4 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.WebUtilities; namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal { @@ -86,6 +87,7 @@ public override async Task OnPostAsync() var userId = await _userManager.GetUserIdAsync(user); var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); var callbackUrl = Url.Page( "/Account/ConfirmEmail", pageHandler: null, From 4364575deb2dd1b8d3468e31444f61139b877793 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 4 Nov 2019 12:11:36 -0800 Subject: [PATCH 5/7] Fix using --- .../Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs | 1 + .../Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs index 54e6afb839d2..d1d2ef784698 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResendEmailConfirmation.cshtml.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel.DataAnnotations; +using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs index 7e9c9b69b9f4..559ce47bc525 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResendEmailConfirmation.cshtml.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel.DataAnnotations; +using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; From be8f8608c5164b9139a651d3fa4ba3b76e1ddbd6 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 4 Nov 2019 13:09:10 -0800 Subject: [PATCH 6/7] Update Microsoft.AspNetCore.Identity.UI.netcoreapp.cs --- .../UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs b/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs index 39a607ce5555..0fc1197ed66a 100644 --- a/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs +++ b/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs @@ -211,7 +211,7 @@ public abstract partial class ResendEmailConfirmationModel : Microsoft.AspNetCor { protected ResendEmailConfirmationModel() { } [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] - public Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal.ResendEmailConfirmationModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal.ResendEmailConfirmationModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public virtual void OnGet() { } public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } public partial class InputModel @@ -219,7 +219,7 @@ public partial class InputModel public InputModel() { } [System.ComponentModel.DataAnnotations.EmailAddressAttribute] [System.ComponentModel.DataAnnotations.RequiredAttribute] - public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] @@ -677,7 +677,7 @@ public abstract partial class ResendEmailConfirmationModel : Microsoft.AspNetCor { protected ResendEmailConfirmationModel() { } [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] - public Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal.ResendEmailConfirmationModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal.ResendEmailConfirmationModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public virtual void OnGet() { } public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } public partial class InputModel @@ -685,7 +685,7 @@ public partial class InputModel public InputModel() { } [System.ComponentModel.DataAnnotations.EmailAddressAttribute] [System.ComponentModel.DataAnnotations.RequiredAttribute] - public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] From 17593980d08d8e251a8a69ebee2ad5f8314542d7 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 4 Nov 2019 14:54:36 -0800 Subject: [PATCH 7/7] Fix ref --- .../UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs b/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs index 0fc1197ed66a..4f25f3b9fab4 100644 --- a/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs +++ b/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp.cs @@ -211,7 +211,7 @@ public abstract partial class ResendEmailConfirmationModel : Microsoft.AspNetCor { protected ResendEmailConfirmationModel() { } [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] - public Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal.ResendEmailConfirmationModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal.ResendEmailConfirmationModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual void OnGet() { } public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } public partial class InputModel @@ -219,7 +219,7 @@ public partial class InputModel public InputModel() { } [System.ComponentModel.DataAnnotations.EmailAddressAttribute] [System.ComponentModel.DataAnnotations.RequiredAttribute] - public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] @@ -677,7 +677,7 @@ public abstract partial class ResendEmailConfirmationModel : Microsoft.AspNetCor { protected ResendEmailConfirmationModel() { } [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] - public Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal.ResendEmailConfirmationModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal.ResendEmailConfirmationModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual void OnGet() { } public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } public partial class InputModel @@ -685,7 +685,7 @@ public partial class InputModel public InputModel() { } [System.ComponentModel.DataAnnotations.EmailAddressAttribute] [System.ComponentModel.DataAnnotations.RequiredAttribute] - public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute]