diff --git a/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp3.0.cs b/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp3.0.cs index 5975369b8999..d2a9cf133809 100644 --- a/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp3.0.cs +++ b/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp3.0.cs @@ -106,7 +106,7 @@ public SecurityStampValidator(Microsoft.Extensions.Options.IOptions where TUser : class { - public SignInManager(Microsoft.AspNetCore.Identity.UserManager userManager, Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor, Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory claimsFactory, Microsoft.Extensions.Options.IOptions optionsAccessor, Microsoft.Extensions.Logging.ILogger> logger, Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes) { } + public SignInManager(Microsoft.AspNetCore.Identity.UserManager userManager, Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor, Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory claimsFactory, Microsoft.Extensions.Options.IOptions optionsAccessor, Microsoft.Extensions.Logging.ILogger> logger, Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes, Microsoft.AspNetCore.Identity.IUserConfirmation confirmation) { } public Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory ClaimsFactory { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Http.HttpContext Context { get { throw null; } set { } } public virtual Microsoft.Extensions.Logging.ILogger Logger { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } diff --git a/src/Identity/Core/src/IdentityServiceCollectionExtensions.cs b/src/Identity/Core/src/IdentityServiceCollectionExtensions.cs index b0d60defd9a1..45e3d567eb09 100644 --- a/src/Identity/Core/src/IdentityServiceCollectionExtensions.cs +++ b/src/Identity/Core/src/IdentityServiceCollectionExtensions.cs @@ -88,6 +88,7 @@ public static IdentityBuilder AddIdentity( services.TryAddScoped>(); services.TryAddScoped>(); services.TryAddScoped, UserClaimsPrincipalFactory>(); + services.TryAddScoped, DefaultUserConfirmation>(); services.TryAddScoped>(); services.TryAddScoped>(); services.TryAddScoped>(); diff --git a/src/Identity/Core/src/SignInManager.cs b/src/Identity/Core/src/SignInManager.cs index f805e596ab06..2711de0333ab 100644 --- a/src/Identity/Core/src/SignInManager.cs +++ b/src/Identity/Core/src/SignInManager.cs @@ -31,12 +31,14 @@ public class SignInManager where TUser : class /// The accessor used to access the . /// The logger used to log messages, warnings and errors. /// The scheme provider that is used enumerate the authentication schemes. + /// The used check whether a user account is confirmed. public SignInManager(UserManager userManager, IHttpContextAccessor contextAccessor, IUserClaimsPrincipalFactory claimsFactory, IOptions optionsAccessor, ILogger> logger, - IAuthenticationSchemeProvider schemes) + IAuthenticationSchemeProvider schemes, + IUserConfirmation confirmation) { if (userManager == null) { @@ -57,11 +59,13 @@ public SignInManager(UserManager userManager, Options = optionsAccessor?.Value ?? new IdentityOptions(); Logger = logger; _schemes = schemes; + _confirmation = confirmation; } private readonly IHttpContextAccessor _contextAccessor; private HttpContext _context; private IAuthenticationSchemeProvider _schemes; + private IUserConfirmation _confirmation; /// /// Gets the used to log messages from the manager. @@ -148,7 +152,11 @@ public virtual async Task CanSignInAsync(TUser user) Logger.LogWarning(1, "User {userId} cannot sign in without a confirmed phone number.", await UserManager.GetUserIdAsync(user)); return false; } - + if (Options.SignIn.RequireConfirmedAccount && !(await _confirmation.IsConfirmedAsync(UserManager, user))) + { + Logger.LogWarning(4, "User {userId} cannot sign in without a confirmed account.", await UserManager.GetUserIdAsync(user)); + return false; + } return true; } diff --git a/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.netcoreapp3.0.cs b/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.netcoreapp3.0.cs index fce3a8894fc8..f934eb8db2b7 100644 --- a/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.netcoreapp3.0.cs +++ b/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.netcoreapp3.0.cs @@ -26,6 +26,12 @@ public DefaultPersonalDataProtector(Microsoft.AspNetCore.Identity.ILookupProtect public virtual string Protect(string data) { throw null; } public virtual string Unprotect(string data) { throw null; } } + public partial class DefaultUserConfirmation : Microsoft.AspNetCore.Identity.IUserConfirmation where TUser : class + { + public DefaultUserConfirmation() { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task IsConfirmedAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + } public partial class EmailTokenProvider : Microsoft.AspNetCore.Identity.TotpSecurityStampBasedTokenProvider where TUser : class { public EmailTokenProvider() { } @@ -194,6 +200,10 @@ public partial interface IUserClaimStore : Microsoft.AspNetCore.Identity. System.Threading.Tasks.Task RemoveClaimsAsync(TUser user, System.Collections.Generic.IEnumerable claims, System.Threading.CancellationToken cancellationToken); System.Threading.Tasks.Task ReplaceClaimAsync(TUser user, System.Security.Claims.Claim claim, System.Security.Claims.Claim newClaim, System.Threading.CancellationToken cancellationToken); } + public partial interface IUserConfirmation where TUser : class + { + System.Threading.Tasks.Task IsConfirmedAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user); + } public partial interface IUserEmailStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class { System.Threading.Tasks.Task FindByEmailAsync(string normalizedEmail, System.Threading.CancellationToken cancellationToken); @@ -396,6 +406,7 @@ public RoleValidator(Microsoft.AspNetCore.Identity.IdentityErrorDescriber errors public partial class SignInOptions { public SignInOptions() { } + public bool RequireConfirmedAccount { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool RequireConfirmedEmail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool RequireConfirmedPhoneNumber { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } diff --git a/src/Identity/Extensions.Core/src/DefaultUserConfirmation.cs b/src/Identity/Extensions.Core/src/DefaultUserConfirmation.cs new file mode 100644 index 000000000000..3e624b1e02f9 --- /dev/null +++ b/src/Identity/Extensions.Core/src/DefaultUserConfirmation.cs @@ -0,0 +1,29 @@ +// 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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Identity +{ + /// + /// Default implementation of . + /// + /// The type encapsulating a user. + public class DefaultUserConfirmation : IUserConfirmation where TUser : class + { + /// + /// Determines whether the specified is confirmed. + /// + /// The that can be used to retrieve user properties. + /// The user. + /// The that represents the asynchronous operation, containing the of the confirmation operation. + public async virtual Task IsConfirmedAsync(UserManager manager, TUser user) + { + if (!await manager.IsEmailConfirmedAsync(user)) + { + return false; + } + return true; + } + } +} diff --git a/src/Identity/Extensions.Core/src/IUserConfirmation.cs b/src/Identity/Extensions.Core/src/IUserConfirmation.cs new file mode 100644 index 000000000000..ccd1d6b3d16d --- /dev/null +++ b/src/Identity/Extensions.Core/src/IUserConfirmation.cs @@ -0,0 +1,22 @@ +// 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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Identity +{ + /// + /// Provides an abstraction for confirmation of user accounts. + /// + /// The type encapsulating a user. + public interface IUserConfirmation where TUser : class + { + /// + /// Determines whether the specified is confirmed. + /// + /// The that can be used to retrieve user properties. + /// The user. + /// Whether the user is confirmed. + Task IsConfirmedAsync(UserManager manager, TUser user); + } +} diff --git a/src/Identity/Extensions.Core/src/IdentityServiceCollectionExtensions.cs b/src/Identity/Extensions.Core/src/IdentityServiceCollectionExtensions.cs index 3dcbd68119a3..568628a3cc87 100644 --- a/src/Identity/Extensions.Core/src/IdentityServiceCollectionExtensions.cs +++ b/src/Identity/Extensions.Core/src/IdentityServiceCollectionExtensions.cs @@ -41,6 +41,7 @@ public static IdentityBuilder AddIdentityCore(this IServiceCollection ser services.TryAddScoped, PasswordValidator>(); services.TryAddScoped, PasswordHasher>(); services.TryAddScoped(); + services.TryAddScoped, DefaultUserConfirmation>(); // No interface for the error describer so we can add errors without rev'ing the interface services.TryAddScoped(); services.TryAddScoped, UserClaimsPrincipalFactory>(); diff --git a/src/Identity/Extensions.Core/src/SignInOptions.cs b/src/Identity/Extensions.Core/src/SignInOptions.cs index 3ae19eb8a399..38244cdd66bd 100644 --- a/src/Identity/Extensions.Core/src/SignInOptions.cs +++ b/src/Identity/Extensions.Core/src/SignInOptions.cs @@ -19,5 +19,11 @@ public class SignInOptions /// /// True if a user must have a confirmed telephone number before they can sign in, otherwise false. public bool RequireConfirmedPhoneNumber { get; set; } + + /// + /// Gets or sets a flag indicating whether a confirmed account is required to sign in. Defaults to false. + /// + /// True if a user must have a confirmed account before they can sign in, otherwise false. + public bool RequireConfirmedAccount { get; set; } } -} \ No newline at end of file +} diff --git a/src/Identity/Specification.Tests/src/UserManagerSpecificationTests.cs b/src/Identity/Specification.Tests/src/UserManagerSpecificationTests.cs index 82d352c93181..6681c524b1c3 100644 --- a/src/Identity/Specification.Tests/src/UserManagerSpecificationTests.cs +++ b/src/Identity/Specification.Tests/src/UserManagerSpecificationTests.cs @@ -1710,6 +1710,34 @@ public async Task CanChangeEmail() Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); } + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanChangeEmailOnlyIfEmailSame() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser("foouser"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + var newEmail = await manager.GetUserNameAsync(user) + "@en.vec"; + var token1 = await manager.GenerateChangeEmailTokenAsync(user, newEmail); + var token2 = await manager.GenerateChangeEmailTokenAsync(user, "should@fail.com"); + IdentityResultAssert.IsSuccess(await manager.ChangeEmailAsync(user, newEmail, token1)); + Assert.True(await manager.IsEmailConfirmedAsync(user)); + Assert.Equal(await manager.GetEmailAsync(user), newEmail); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, "should@fail.com", token2)); + } + /// /// Test. /// diff --git a/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp3.0.cs b/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp3.0.cs index 462e9f8800b4..dbff0d0dea5e 100644 --- a/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp3.0.cs +++ b/src/Identity/UI/ref/Microsoft.AspNetCore.Identity.UI.netcoreapp3.0.cs @@ -37,9 +37,19 @@ public AccessDeniedModel() { } public void OnGet() { } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] + public abstract partial class ConfirmEmailChangeModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel + { + protected ConfirmEmailChangeModel() { } + [Microsoft.AspNetCore.Mvc.TempDataAttribute] + public string StatusMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual System.Threading.Tasks.Task OnGetAsync(string userId, string email, string code) { throw null; } + } + [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] public abstract partial class ConfirmEmailModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { protected ConfirmEmailModel() { } + [Microsoft.AspNetCore.Mvc.TempDataAttribute] + public string StatusMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public virtual System.Threading.Tasks.Task OnGetAsync(string userId, string code) { throw null; } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] @@ -103,6 +113,7 @@ protected LoginModel() { } public string ReturnUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public virtual System.Threading.Tasks.Task OnGetAsync(string returnUrl = null) { throw null; } public virtual System.Threading.Tasks.Task OnPostAsync(string returnUrl = null) { throw null; } + public virtual System.Threading.Tasks.Task OnPostSendVerificationEmailAsync() { throw null; } public partial class InputModel { public InputModel() { } @@ -165,6 +176,15 @@ public void OnGet() { } public virtual System.Threading.Tasks.Task OnPost(string returnUrl = null) { throw null; } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] + public partial class RegisterConfirmationModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel + { + public RegisterConfirmationModel() { } + public bool DisplayConfirmAccountLink { [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 { } } + public string EmailConfirmationUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual System.Threading.Tasks.Task OnGetAsync(string email) { throw null; } + } + [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] public abstract partial class RegisterModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { protected RegisterModel() { } @@ -284,6 +304,27 @@ protected DownloadPersonalDataModel() { } public virtual Microsoft.AspNetCore.Mvc.IActionResult OnGet() { throw null; } public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } } + public abstract partial class EmailModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel + { + protected EmailModel() { } + public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] + public Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.Internal.EmailModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool IsEmailConfirmed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + [Microsoft.AspNetCore.Mvc.TempDataAttribute] + public string StatusMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual System.Threading.Tasks.Task OnGetAsync() { throw null; } + public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } + public virtual System.Threading.Tasks.Task OnPostSendVerificationEmailAsync() { throw null; } + public partial class InputModel + { + public InputModel() { } + [System.ComponentModel.DataAnnotations.DisplayAttribute(Name="New email")] + [System.ComponentModel.DataAnnotations.EmailAddressAttribute] + [System.ComponentModel.DataAnnotations.RequiredAttribute] + public string NewEmail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + } public partial class EnableAuthenticatorModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { public EnableAuthenticatorModel() { } @@ -335,19 +376,14 @@ public abstract partial class IndexModel : Microsoft.AspNetCore.Mvc.RazorPages.P protected IndexModel() { } [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] public Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.Internal.IndexModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool IsEmailConfirmed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } [Microsoft.AspNetCore.Mvc.TempDataAttribute] public string StatusMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Username { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public virtual System.Threading.Tasks.Task OnGetAsync() { throw null; } public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } - public virtual System.Threading.Tasks.Task OnPostSendVerificationEmailAsync() { 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 { } } [System.ComponentModel.DataAnnotations.DisplayAttribute(Name="Phone number")] [System.ComponentModel.DataAnnotations.PhoneAttribute] public string PhoneNumber { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } @@ -358,6 +394,7 @@ public static partial class ManageNavPages public static string ChangePassword { get { throw null; } } public static string DeletePersonalData { get { throw null; } } public static string DownloadPersonalData { get { throw null; } } + public static string Email { get { throw null; } } public static string ExternalLogins { get { throw null; } } public static string Index { get { throw null; } } public static string PersonalData { get { throw null; } } @@ -365,6 +402,7 @@ public static partial class ManageNavPages public static string ChangePasswordNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext) { throw null; } public static string DeletePersonalDataNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext) { throw null; } public static string DownloadPersonalDataNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext) { throw null; } + public static string EmailNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext) { throw null; } public static string ExternalLoginsNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext) { throw null; } public static string IndexNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext) { throw null; } public static string PageNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext, string page) { throw null; } @@ -450,9 +488,19 @@ public AccessDeniedModel() { } public void OnGet() { } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] + public abstract partial class ConfirmEmailChangeModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel + { + protected ConfirmEmailChangeModel() { } + [Microsoft.AspNetCore.Mvc.TempDataAttribute] + public string StatusMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual System.Threading.Tasks.Task OnGetAsync(string userId, string email, string code) { throw null; } + } + [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] public abstract partial class ConfirmEmailModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { protected ConfirmEmailModel() { } + [Microsoft.AspNetCore.Mvc.TempDataAttribute] + public string StatusMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public virtual System.Threading.Tasks.Task OnGetAsync(string userId, string code) { throw null; } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] @@ -578,6 +626,15 @@ public void OnGet() { } public virtual System.Threading.Tasks.Task OnPost(string returnUrl = null) { throw null; } } [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] + public partial class RegisterConfirmationModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel + { + public RegisterConfirmationModel() { } + public bool DisplayConfirmAccountLink { [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 { } } + public string EmailConfirmationUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual System.Threading.Tasks.Task OnGetAsync(string email) { throw null; } + } + [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] public abstract partial class RegisterModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { protected RegisterModel() { } @@ -697,6 +754,27 @@ protected DownloadPersonalDataModel() { } public virtual Microsoft.AspNetCore.Mvc.IActionResult OnGet() { throw null; } public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } } + public abstract partial class EmailModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel + { + protected EmailModel() { } + public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] + public Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal.EmailModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool IsEmailConfirmed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + [Microsoft.AspNetCore.Mvc.TempDataAttribute] + public string StatusMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual System.Threading.Tasks.Task OnGetAsync() { throw null; } + public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } + public virtual System.Threading.Tasks.Task OnPostSendVerificationEmailAsync() { throw null; } + public partial class InputModel + { + public InputModel() { } + [System.ComponentModel.DataAnnotations.DisplayAttribute(Name="New email")] + [System.ComponentModel.DataAnnotations.EmailAddressAttribute] + [System.ComponentModel.DataAnnotations.RequiredAttribute] + public string NewEmail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + } public partial class EnableAuthenticatorModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { public EnableAuthenticatorModel() { } @@ -748,19 +826,14 @@ public abstract partial class IndexModel : Microsoft.AspNetCore.Mvc.RazorPages.P protected IndexModel() { } [Microsoft.AspNetCore.Mvc.BindPropertyAttribute] public Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal.IndexModel.InputModel Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool IsEmailConfirmed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } [Microsoft.AspNetCore.Mvc.TempDataAttribute] public string StatusMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Username { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public virtual System.Threading.Tasks.Task OnGetAsync() { throw null; } public virtual System.Threading.Tasks.Task OnPostAsync() { throw null; } - public virtual System.Threading.Tasks.Task OnPostSendVerificationEmailAsync() { 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 { } } [System.ComponentModel.DataAnnotations.DisplayAttribute(Name="Phone number")] [System.ComponentModel.DataAnnotations.PhoneAttribute] public string PhoneNumber { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } @@ -771,6 +844,7 @@ public static partial class ManageNavPages public static string ChangePassword { get { throw null; } } public static string DeletePersonalData { get { throw null; } } public static string DownloadPersonalData { get { throw null; } } + public static string Email { get { throw null; } } public static string ExternalLogins { get { throw null; } } public static string Index { get { throw null; } } public static string PersonalData { get { throw null; } } @@ -778,6 +852,7 @@ public static partial class ManageNavPages public static string ChangePasswordNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext) { throw null; } public static string DeletePersonalDataNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext) { throw null; } public static string DownloadPersonalDataNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext) { throw null; } + public static string EmailNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext) { throw null; } public static string ExternalLoginsNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext) { throw null; } public static string IndexNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext) { throw null; } public static string PageNavClass(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext, string page) { throw null; } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmail.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmail.cshtml index 903896477cb7..b257bd30be8e 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmail.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmail.cshtml @@ -5,8 +5,4 @@ }

@ViewData["Title"]

-
-

- Thank you for confirming your email. -

-
+ diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmail.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmail.cshtml.cs index 2579c5a17b53..4477c39fa476 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmail.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmail.cshtml.cs @@ -8,7 +8,6 @@ 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 @@ -18,6 +17,13 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal [IdentityDefaultUI(typeof(ConfirmEmailModel<>))] public abstract class ConfirmEmailModel : 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. + /// + [TempData] + public string StatusMessage { 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. @@ -48,11 +54,7 @@ public override async Task OnGetAsync(string userId, string code) } var result = await _userManager.ConfirmEmailAsync(user, code); - if (!result.Succeeded) - { - throw new InvalidOperationException($"Error confirming email for user with ID '{userId}':"); - } - + StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email."; return Page(); } } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmailChange.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmailChange.cshtml new file mode 100644 index 000000000000..83b1df7254cc --- /dev/null +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmailChange.cshtml @@ -0,0 +1,8 @@ +@page +@model ConfirmEmailChangeModel +@{ + ViewData["Title"] = "Confirm email change"; +} + +

@ViewData["Title"]

+ diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmailChange.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmailChange.cshtml.cs new file mode 100644 index 000000000000..ee24b30fd13b --- /dev/null +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ConfirmEmailChange.cshtml.cs @@ -0,0 +1,80 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +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(ConfirmEmailChangeModel<>))] + public abstract class ConfirmEmailChangeModel : 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. + /// + [TempData] + public string StatusMessage { 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 Task OnGetAsync(string userId, string email, string code) => throw new NotImplementedException(); + } + + internal class ConfirmEmailChangeModel : ConfirmEmailChangeModel where TUser : class + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + + public ConfirmEmailChangeModel(UserManager userManager, SignInManager signInManager) + { + _userManager = userManager; + _signInManager = signInManager; + } + + public override async Task OnGetAsync(string userId, string email, string code) + { + if (userId == null || email == null || code == null) + { + return RedirectToPage("/Index"); + } + + var user = await _userManager.FindByIdAsync(userId); + if (user == null) + { + return NotFound($"Unable to load user with ID '{userId}'."); + } + + var result = await _userManager.ChangeEmailAsync(user, email, code); + if (!result.Succeeded) + { + StatusMessage = "Error changing email."; + return Page(); + } + + // In our UI email and user name are one and the same, so when we update the email + // we need to update the user name. + var setUserNameResult = await _userManager.SetUserNameAsync(user, email); + if (!setUserNameResult.Succeeded) + { + StatusMessage = "Error changing user name."; + return Page(); + } + + await _signInManager.RefreshSignInAsync(user); + StatusMessage = "Thank you for confirming your email change."; + return Page(); + } + } +} 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 875fe3ee7f36..a58edae8a87a 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 @@ -32,7 +32,7 @@
- +

@@ -41,6 +41,9 @@

Register as a new user

+

+ +

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 9f5dc83c5e61..74ec7bb1cd89 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 @@ -5,9 +5,11 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; @@ -90,16 +92,28 @@ public class InputModel /// directly from your code. This API may change or be removed in future releases. ///
public virtual Task OnPostAsync(string returnUrl = null) => 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 OnPostSendVerificationEmailAsync() => throw new NotImplementedException(); } internal class LoginModel : LoginModel where TUser : class { + private readonly UserManager _userManager; private readonly SignInManager _signInManager; private readonly ILogger _logger; + private readonly IEmailSender _emailSender; - public LoginModel(SignInManager signInManager, ILogger logger) + public LoginModel(SignInManager signInManager, ILogger logger, + UserManager userManager, + IEmailSender emailSender) { + _userManager = userManager; _signInManager = signInManager; + _emailSender = emailSender; _logger = logger; } @@ -155,5 +169,34 @@ 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/Manage/Email.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Email.cshtml new file mode 100644 index 000000000000..e291749562fc --- /dev/null +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Email.cshtml @@ -0,0 +1,41 @@ +@page +@model EmailModel +@{ + ViewData["Title"] = "Manage Email"; + ViewData["ActivePage"] = ManageNavPages.Email; +} + +

@ViewData["Title"]

+ +
+
+
+
+
+ + @if (Model.IsEmailConfirmed) + { +
+ + +
+ } + else + { + + + } +
+
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Email.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Email.cshtml.cs new file mode 100644 index 000000000000..0ff0ca11bf0f --- /dev/null +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Email.cshtml.cs @@ -0,0 +1,185 @@ +// 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.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.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. + /// + [IdentityDefaultUI(typeof(EmailModel<>))] + public abstract class EmailModel : 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. + /// + 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 bool IsEmailConfirmed { 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. + /// + [TempData] + public string StatusMessage { 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. + /// + [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] + [Display(Name = "New email")] + public string NewEmail { 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 Task OnGetAsync() => 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(); + + /// + /// 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 OnPostSendVerificationEmailAsync() => throw new NotImplementedException(); + } + + internal class EmailModel : EmailModel where TUser : class + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; + + public EmailModel( + UserManager userManager, + SignInManager signInManager, + IEmailSender emailSender) + { + _userManager = userManager; + _signInManager = signInManager; + _emailSender = emailSender; + } + + public override async Task OnGetAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + var email = await _userManager.GetEmailAsync(user); + + Email = email; + + Input = new InputModel + { + NewEmail = email, + }; + + IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user); + + return Page(); + } + + public override async Task OnPostAsync() + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var email = await _userManager.GetEmailAsync(user); + if (Input.NewEmail != email) + { + var userId = await _userManager.GetUserIdAsync(user); + var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail); + var callbackUrl = Url.Page( + "/Account/ConfirmEmailChange", + pageHandler: null, + values: new { userId = userId, email = Input.NewEmail, code = code }, + protocol: Request.Scheme); + await _emailSender.SendEmailAsync( + Input.NewEmail, + "Confirm your email", + $"Please confirm your account by clicking here."); + + StatusMessage = "Confirmation link to change email sent. Please check your email."; + return RedirectToPage(); + } + + StatusMessage = "Your email is unchanged."; + return RedirectToPage(); + } + + public override async Task OnPostSendVerificationEmailAsync() + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var userId = await _userManager.GetUserIdAsync(user); + var email = await _userManager.GetEmailAsync(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( + email, + "Confirm your email", + $"Please confirm your account by clicking here."); + + StatusMessage = "Verification email sent. Please check your email."; + return RedirectToPage(); + } + } +} diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml index 13b1cf8e547f..19ac1a296e23 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml @@ -15,22 +15,6 @@ -
- - @if (Model.IsEmailConfirmed) - { -
- - -
- } - else - { - - - } - -
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml.cs index d536d587e15b..a95594f70234 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml.cs @@ -3,9 +3,7 @@ using System; using System.ComponentModel.DataAnnotations; -using System.Text.Encodings.Web; using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; @@ -24,12 +22,6 @@ public abstract class IndexModel : PageModel ///
public string Username { 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 bool IsEmailConfirmed { 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. @@ -50,14 +42,6 @@ public abstract class IndexModel : PageModel /// 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. @@ -78,28 +62,19 @@ public class InputModel /// directly from your code. This API may change or be removed in future releases. /// public virtual Task OnPostAsync() => 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 OnPostSendVerificationEmailAsync() => throw new NotImplementedException(); } internal class IndexModel : IndexModel where TUser : class { private readonly UserManager _userManager; private readonly SignInManager _signInManager; - private readonly IEmailSender _emailSender; public IndexModel( UserManager userManager, - SignInManager signInManager, - IEmailSender emailSender) + SignInManager signInManager) { _userManager = userManager; _signInManager = signInManager; - _emailSender = emailSender; } public override async Task OnGetAsync() @@ -110,19 +85,15 @@ public override async Task OnGetAsync() return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } var userName = await _userManager.GetUserNameAsync(user); - var email = await _userManager.GetEmailAsync(user); var phoneNumber = await _userManager.GetPhoneNumberAsync(user); Username = userName; Input = new InputModel { - Email = email, PhoneNumber = phoneNumber }; - IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user); - return Page(); } @@ -139,26 +110,6 @@ public override async Task OnPostAsync() return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } - var email = await _userManager.GetEmailAsync(user); - if (Input.Email != email) - { - var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email); - if (!setEmailResult.Succeeded) - { - var userId = await _userManager.GetUserIdAsync(user); - throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'."); - } - - // In our UI email and user name are one and the same, so when we update the email - // we need to update the user name. - var setUserNameResult = await _userManager.SetUserNameAsync(user, Input.Email); - if (!setUserNameResult.Succeeded) - { - var userId = await _userManager.GetUserIdAsync(user); - throw new InvalidOperationException($"Unexpected error occurred setting name for user with ID '{userId}'."); - } - } - var phoneNumber = await _userManager.GetPhoneNumberAsync(user); if (Input.PhoneNumber != phoneNumber) { @@ -174,36 +125,5 @@ public override async Task OnPostAsync() StatusMessage = "Your profile has been updated"; return RedirectToPage(); } - - public override async Task OnPostSendVerificationEmailAsync() - { - if (!ModelState.IsValid) - { - return Page(); - } - - var user = await _userManager.GetUserAsync(User); - if (user == null) - { - return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); - } - - - var userId = await _userManager.GetUserIdAsync(user); - var email = await _userManager.GetEmailAsync(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( - email, - "Confirm your email", - $"Please confirm your account by clicking here."); - - StatusMessage = "Verification email sent. Please check your email."; - return RedirectToPage(); - } } } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ManageNavPages.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ManageNavPages.cs index e449a0221c4a..289e0ebf33df 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ManageNavPages.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ManageNavPages.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -18,6 +18,12 @@ public static class ManageNavPages /// public static string Index => "Index"; + /// + /// 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 static string Email => "Email"; + /// /// 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. @@ -60,6 +66,12 @@ public static class ManageNavPages /// public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); + /// + /// 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 static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email); + /// /// 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. diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/_ManageNav.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/_ManageNav.cshtml index 69fcc8f7b8e6..cfd16bb22b5d 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/_ManageNav.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/_ManageNav.cshtml @@ -1,5 +1,6 @@ - public static string Index => "Index"; + /// + /// 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 static string Email => "Email"; + /// /// 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. @@ -60,6 +66,12 @@ public static class ManageNavPages /// public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); + /// + /// 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 static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email); + /// /// 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. diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/_ManageNav.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/_ManageNav.cshtml index de285c8b7447..eb79b4001f18 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/_ManageNav.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/_ManageNav.cshtml @@ -1,5 +1,6 @@ -