feat: Integrate ASP.NET Core Identity for user authentication
This commit integrates ASP.NET Core Identity into the application to enable user registration, login, and management. This lays the groundwork for securing data per user.
**Key Changes:**
*   **DbContext Configuration:**
    *   Modified `ApplicationDbContext.cs` to inherit from `IdentityDbContext<IdentityUser>`.
    *   Removed an unnecessary `using` statement from `ApplicationDbContext.cs`.
*   **Program.cs Setup:**
    *   Configured `AddDefaultIdentity<IdentityUser>` with `AddEntityFrameworkStores<ApplicationDbContext>()` to register Identity services.
    *   Ensured correct ordering of `UseAuthentication()` and `UseAuthorization()` middleware.
    *   Added `app.MapRazorPages()` to enable the Identity UI pages.
    *   Verified core package versions in `turf_tasker.csproj` for consistency across EF Core and Identity components (`8.0.6`).
*   **Identity UI:**
    *   Scaffolded ASP.NET Core Identity pages (Login, Register, Manage, etc.) to provide the user interface for authentication.
    *   Added a `_LoginPartial.cshtml` partial view to the `Views/Shared` folder.
    *   Rendered `_LoginPartial` in `Views/Shared/_Layout.cshtml` to display login/register/logout links in the navigation bar.
*   **Migrations:**
    *   Created and applied a new migration (`AddIdentitySchema`) to create the necessary ASP.NET Core Identity database tables (e.g., `AspNetUsers`, `AspNetRoles`).
			
			
This commit is contained in:
		
							parent
							
								
									60567d7969
								
							
						
					
					
						commit
						b24beb3154
					
				
					 79 changed files with 5246 additions and 12 deletions
				
			
		
							
								
								
									
										36
									
								
								Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
@page
 | 
			
		||||
@model ChangePasswordModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Change password";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.ChangePassword;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<h3>@ViewData["Title"]</h3>
 | 
			
		||||
<partial name="_StatusMessage" for="StatusMessage" />
 | 
			
		||||
<div class="row">
 | 
			
		||||
    <div class="col-md-6">
 | 
			
		||||
        <form id="change-password-form" method="post">
 | 
			
		||||
            <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
 | 
			
		||||
            <div class="form-floating mb-3">
 | 
			
		||||
                <input asp-for="Input.OldPassword" class="form-control" autocomplete="current-password" aria-required="true" placeholder="Please enter your old password." />
 | 
			
		||||
                <label asp-for="Input.OldPassword" class="form-label"></label>
 | 
			
		||||
                <span asp-validation-for="Input.OldPassword" class="text-danger"></span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-floating mb-3">
 | 
			
		||||
                <input asp-for="Input.NewPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please enter your new password." />
 | 
			
		||||
                <label asp-for="Input.NewPassword" class="form-label"></label>
 | 
			
		||||
                <span asp-validation-for="Input.NewPassword" class="text-danger"></span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-floating mb-3">
 | 
			
		||||
                <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please confirm your new password."/>
 | 
			
		||||
                <label asp-for="Input.ConfirmPassword" class="form-label"></label>
 | 
			
		||||
                <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <button type="submit" class="w-100 btn btn-lg btn-primary">Update password</button>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@section Scripts {
 | 
			
		||||
    <partial name="_ValidationScriptsPartial" />
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										127
									
								
								Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class ChangePasswordModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly SignInManager<IdentityUser> _signInManager;
 | 
			
		||||
        private readonly ILogger<ChangePasswordModel> _logger;
 | 
			
		||||
 | 
			
		||||
        public ChangePasswordModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager,
 | 
			
		||||
            SignInManager<IdentityUser> signInManager,
 | 
			
		||||
            ILogger<ChangePasswordModel> logger)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _signInManager = signInManager;
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [BindProperty]
 | 
			
		||||
        public InputModel Input { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string StatusMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public class InputModel
 | 
			
		||||
        {
 | 
			
		||||
            /// <summary>
 | 
			
		||||
            ///     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.
 | 
			
		||||
            /// </summary>
 | 
			
		||||
            [Required]
 | 
			
		||||
            [DataType(DataType.Password)]
 | 
			
		||||
            [Display(Name = "Current password")]
 | 
			
		||||
            public string OldPassword { get; set; }
 | 
			
		||||
 | 
			
		||||
            /// <summary>
 | 
			
		||||
            ///     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.
 | 
			
		||||
            /// </summary>
 | 
			
		||||
            [Required]
 | 
			
		||||
            [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
 | 
			
		||||
            [DataType(DataType.Password)]
 | 
			
		||||
            [Display(Name = "New password")]
 | 
			
		||||
            public string NewPassword { get; set; }
 | 
			
		||||
 | 
			
		||||
            /// <summary>
 | 
			
		||||
            ///     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.
 | 
			
		||||
            /// </summary>
 | 
			
		||||
            [DataType(DataType.Password)]
 | 
			
		||||
            [Display(Name = "Confirm new password")]
 | 
			
		||||
            [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
 | 
			
		||||
            public string ConfirmPassword { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGetAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var hasPassword = await _userManager.HasPasswordAsync(user);
 | 
			
		||||
            if (!hasPassword)
 | 
			
		||||
            {
 | 
			
		||||
                return RedirectToPage("./SetPassword");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> 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 changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
 | 
			
		||||
            if (!changePasswordResult.Succeeded)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var error in changePasswordResult.Errors)
 | 
			
		||||
                {
 | 
			
		||||
                    ModelState.AddModelError(string.Empty, error.Description);
 | 
			
		||||
                }
 | 
			
		||||
                return Page();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await _signInManager.RefreshSignInAsync(user);
 | 
			
		||||
            _logger.LogInformation("User changed their password successfully.");
 | 
			
		||||
            StatusMessage = "Your password has been changed.";
 | 
			
		||||
 | 
			
		||||
            return RedirectToPage();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
@page
 | 
			
		||||
@model DeletePersonalDataModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Delete Personal Data";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.PersonalData;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<h3>@ViewData["Title"]</h3>
 | 
			
		||||
 | 
			
		||||
<div class="alert alert-warning" role="alert">
 | 
			
		||||
    <p>
 | 
			
		||||
        <strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
 | 
			
		||||
    </p>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div>
 | 
			
		||||
    <form id="delete-user" method="post">
 | 
			
		||||
        <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
 | 
			
		||||
        @if (Model.RequirePassword)
 | 
			
		||||
        {
 | 
			
		||||
            <div class="form-floating mb-3">
 | 
			
		||||
                <input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="Please enter your password." />
 | 
			
		||||
                <label asp-for="Input.Password" class="form-label"></label>
 | 
			
		||||
                <span asp-validation-for="Input.Password" class="text-danger"></span>
 | 
			
		||||
            </div>
 | 
			
		||||
        }
 | 
			
		||||
        <button class="w-100 btn btn-lg btn-danger" type="submit">Delete data and close my account</button>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@section Scripts {
 | 
			
		||||
    <partial name="_ValidationScriptsPartial" />
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										103
									
								
								Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,103 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class DeletePersonalDataModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly SignInManager<IdentityUser> _signInManager;
 | 
			
		||||
        private readonly ILogger<DeletePersonalDataModel> _logger;
 | 
			
		||||
 | 
			
		||||
        public DeletePersonalDataModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager,
 | 
			
		||||
            SignInManager<IdentityUser> signInManager,
 | 
			
		||||
            ILogger<DeletePersonalDataModel> logger)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _signInManager = signInManager;
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [BindProperty]
 | 
			
		||||
        public InputModel Input { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public class InputModel
 | 
			
		||||
        {
 | 
			
		||||
            /// <summary>
 | 
			
		||||
            ///     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.
 | 
			
		||||
            /// </summary>
 | 
			
		||||
            [Required]
 | 
			
		||||
            [DataType(DataType.Password)]
 | 
			
		||||
            public string Password { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool RequirePassword { get; set; }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGet()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            RequirePassword = await _userManager.HasPasswordAsync(user);
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnPostAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            RequirePassword = await _userManager.HasPasswordAsync(user);
 | 
			
		||||
            if (RequirePassword)
 | 
			
		||||
            {
 | 
			
		||||
                if (!await _userManager.CheckPasswordAsync(user, Input.Password))
 | 
			
		||||
                {
 | 
			
		||||
                    ModelState.AddModelError(string.Empty, "Incorrect password.");
 | 
			
		||||
                    return Page();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var result = await _userManager.DeleteAsync(user);
 | 
			
		||||
            var userId = await _userManager.GetUserIdAsync(user);
 | 
			
		||||
            if (!result.Succeeded)
 | 
			
		||||
            {
 | 
			
		||||
                throw new InvalidOperationException($"Unexpected error occurred deleting user.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await _signInManager.SignOutAsync();
 | 
			
		||||
 | 
			
		||||
            _logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
 | 
			
		||||
 | 
			
		||||
            return Redirect("~/");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
@page
 | 
			
		||||
@model Disable2faModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Disable two-factor authentication (2FA)";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<partial name="_StatusMessage" for="StatusMessage" />
 | 
			
		||||
<h3>@ViewData["Title"]</h3>
 | 
			
		||||
 | 
			
		||||
<div class="alert alert-warning" role="alert">
 | 
			
		||||
    <p>
 | 
			
		||||
        <strong>This action only disables 2FA.</strong>
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
 | 
			
		||||
        used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a>
 | 
			
		||||
    </p>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div>
 | 
			
		||||
    <form method="post">
 | 
			
		||||
        <button class="btn btn-danger" type="submit">Disable 2FA</button>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										69
									
								
								Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,69 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class Disable2faModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly ILogger<Disable2faModel> _logger;
 | 
			
		||||
 | 
			
		||||
        public Disable2faModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager,
 | 
			
		||||
            ILogger<Disable2faModel> logger)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string StatusMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGet()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!await _userManager.GetTwoFactorEnabledAsync(user))
 | 
			
		||||
            {
 | 
			
		||||
                throw new InvalidOperationException($"Cannot disable 2FA for user as it's not currently enabled.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnPostAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
 | 
			
		||||
            if (!disable2faResult.Succeeded)
 | 
			
		||||
            {
 | 
			
		||||
                throw new InvalidOperationException($"Unexpected error occurred disabling 2FA.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User));
 | 
			
		||||
            StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app";
 | 
			
		||||
            return RedirectToPage("./TwoFactorAuthentication");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
@page
 | 
			
		||||
@model DownloadPersonalDataModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Download Your Data";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.PersonalData;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<h3>@ViewData["Title"]</h3>
 | 
			
		||||
 | 
			
		||||
@section Scripts {
 | 
			
		||||
    <partial name="_ValidationScriptsPartial" />
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class DownloadPersonalDataModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly ILogger<DownloadPersonalDataModel> _logger;
 | 
			
		||||
 | 
			
		||||
        public DownloadPersonalDataModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager,
 | 
			
		||||
            ILogger<DownloadPersonalDataModel> logger)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IActionResult OnGet()
 | 
			
		||||
        {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnPostAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User));
 | 
			
		||||
 | 
			
		||||
            // Only include personal data for download
 | 
			
		||||
            var personalData = new Dictionary<string, string>();
 | 
			
		||||
            var personalDataProps = typeof(IdentityUser).GetProperties().Where(
 | 
			
		||||
                            prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
 | 
			
		||||
            foreach (var p in personalDataProps)
 | 
			
		||||
            {
 | 
			
		||||
                personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var logins = await _userManager.GetLoginsAsync(user);
 | 
			
		||||
            foreach (var l in logins)
 | 
			
		||||
            {
 | 
			
		||||
                personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            personalData.Add($"Authenticator Key", await _userManager.GetAuthenticatorKeyAsync(user));
 | 
			
		||||
 | 
			
		||||
            Response.Headers.TryAdd("Content-Disposition", "attachment; filename=PersonalData.json");
 | 
			
		||||
            return new FileContentResult(JsonSerializer.SerializeToUtf8Bytes(personalData), "application/json");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								Areas/Identity/Pages/Account/Manage/Email.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								Areas/Identity/Pages/Account/Manage/Email.cshtml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
@page
 | 
			
		||||
@model EmailModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Manage Email";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.Email;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<h3>@ViewData["Title"]</h3>
 | 
			
		||||
<partial name="_StatusMessage" for="StatusMessage" />
 | 
			
		||||
<div class="row">
 | 
			
		||||
    <div class="col-md-6">
 | 
			
		||||
        <form id="email-form" method="post">
 | 
			
		||||
            <div asp-validation-summary="All" class="text-danger" role="alert"></div>
 | 
			
		||||
            @if (Model.IsEmailConfirmed)
 | 
			
		||||
            {
 | 
			
		||||
                <div class="form-floating mb-3 input-group">
 | 
			
		||||
                    <input asp-for="Email" class="form-control" placeholder="Please enter your email." disabled />
 | 
			
		||||
                        <div class="input-group-append">
 | 
			
		||||
                            <span class="h-100 input-group-text text-success font-weight-bold">✓</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    <label asp-for="Email" class="form-label"></label>
 | 
			
		||||
                </div>
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                <div class="form-floating mb-3">
 | 
			
		||||
                    <input asp-for="Email" class="form-control" placeholder="Please enter your email." disabled />
 | 
			
		||||
                    <label asp-for="Email" class="form-label"></label>
 | 
			
		||||
                    <button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
 | 
			
		||||
                </div>
 | 
			
		||||
            }
 | 
			
		||||
            <div class="form-floating mb-3">
 | 
			
		||||
                <input asp-for="Input.NewEmail" class="form-control" autocomplete="email" aria-required="true" placeholder="Please enter new email." />
 | 
			
		||||
                <label asp-for="Input.NewEmail" class="form-label"></label>
 | 
			
		||||
                <span asp-validation-for="Input.NewEmail" class="text-danger"></span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <button id="change-email-button" type="submit" asp-page-handler="ChangeEmail" class="w-100 btn btn-lg btn-primary">Change email</button>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@section Scripts {
 | 
			
		||||
    <partial name="_ValidationScriptsPartial" />
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										171
									
								
								Areas/Identity/Pages/Account/Manage/Email.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								Areas/Identity/Pages/Account/Manage/Email.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,171 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.Encodings.Web;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Identity.UI.Services;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using Microsoft.AspNetCore.WebUtilities;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class EmailModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly SignInManager<IdentityUser> _signInManager;
 | 
			
		||||
        private readonly IEmailSender _emailSender;
 | 
			
		||||
 | 
			
		||||
        public EmailModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager,
 | 
			
		||||
            SignInManager<IdentityUser> signInManager,
 | 
			
		||||
            IEmailSender emailSender)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _signInManager = signInManager;
 | 
			
		||||
            _emailSender = emailSender;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string Email { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool IsEmailConfirmed { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string StatusMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [BindProperty]
 | 
			
		||||
        public InputModel Input { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public class InputModel
 | 
			
		||||
        {
 | 
			
		||||
            /// <summary>
 | 
			
		||||
            ///     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.
 | 
			
		||||
            /// </summary>
 | 
			
		||||
            [Required]
 | 
			
		||||
            [EmailAddress]
 | 
			
		||||
            [Display(Name = "New email")]
 | 
			
		||||
            public string NewEmail { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task LoadAsync(IdentityUser user)
 | 
			
		||||
        {
 | 
			
		||||
            var email = await _userManager.GetEmailAsync(user);
 | 
			
		||||
            Email = email;
 | 
			
		||||
 | 
			
		||||
            Input = new InputModel
 | 
			
		||||
            {
 | 
			
		||||
                NewEmail = email,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGetAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await LoadAsync(user);
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnPostChangeEmailAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!ModelState.IsValid)
 | 
			
		||||
            {
 | 
			
		||||
                await LoadAsync(user);
 | 
			
		||||
                return Page();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var email = await _userManager.GetEmailAsync(user);
 | 
			
		||||
            if (Input.NewEmail != email)
 | 
			
		||||
            {
 | 
			
		||||
                var userId = await _userManager.GetUserIdAsync(user);
 | 
			
		||||
                var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail);
 | 
			
		||||
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
 | 
			
		||||
                var callbackUrl = Url.Page(
 | 
			
		||||
                    "/Account/ConfirmEmailChange",
 | 
			
		||||
                    pageHandler: null,
 | 
			
		||||
                    values: new { area = "Identity", userId = userId, email = Input.NewEmail, code = code },
 | 
			
		||||
                    protocol: Request.Scheme);
 | 
			
		||||
                await _emailSender.SendEmailAsync(
 | 
			
		||||
                    Input.NewEmail,
 | 
			
		||||
                    "Confirm your email",
 | 
			
		||||
                    $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
 | 
			
		||||
 | 
			
		||||
                StatusMessage = "Confirmation link to change email sent. Please check your email.";
 | 
			
		||||
                return RedirectToPage();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            StatusMessage = "Your email is unchanged.";
 | 
			
		||||
            return RedirectToPage();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnPostSendVerificationEmailAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!ModelState.IsValid)
 | 
			
		||||
            {
 | 
			
		||||
                await LoadAsync(user);
 | 
			
		||||
                return Page();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var userId = await _userManager.GetUserIdAsync(user);
 | 
			
		||||
            var email = await _userManager.GetEmailAsync(user);
 | 
			
		||||
            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
 | 
			
		||||
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
 | 
			
		||||
            var callbackUrl = Url.Page(
 | 
			
		||||
                "/Account/ConfirmEmail",
 | 
			
		||||
                pageHandler: null,
 | 
			
		||||
                values: new { area = "Identity", userId = userId, code = code },
 | 
			
		||||
                protocol: Request.Scheme);
 | 
			
		||||
            await _emailSender.SendEmailAsync(
 | 
			
		||||
                email,
 | 
			
		||||
                "Confirm your email",
 | 
			
		||||
                $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
 | 
			
		||||
 | 
			
		||||
            StatusMessage = "Verification email sent. Please check your email.";
 | 
			
		||||
            return RedirectToPage();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
@page
 | 
			
		||||
@model EnableAuthenticatorModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Configure authenticator app";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<partial name="_StatusMessage" for="StatusMessage" />
 | 
			
		||||
<h3>@ViewData["Title"]</h3>
 | 
			
		||||
<div>
 | 
			
		||||
    <p>To use an authenticator app go through the following steps:</p>
 | 
			
		||||
    <ol class="list">
 | 
			
		||||
        <li>
 | 
			
		||||
            <p>
 | 
			
		||||
                Download a two-factor authenticator app like Microsoft Authenticator for
 | 
			
		||||
                <a href="https://go.microsoft.com/fwlink/?Linkid=825072">Android</a> and
 | 
			
		||||
                <a href="https://go.microsoft.com/fwlink/?Linkid=825073">iOS</a> or
 | 
			
		||||
                Google Authenticator for
 | 
			
		||||
                <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en">Android</a> and
 | 
			
		||||
                <a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">iOS</a>.
 | 
			
		||||
            </p>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
            <p>Scan the QR Code or enter this key <kbd>@Model.SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
 | 
			
		||||
            <div class="alert alert-info">Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable QR code generation</a>.</div>
 | 
			
		||||
            <div id="qrCode"></div>
 | 
			
		||||
            <div id="qrCodeData" data-url="@Model.AuthenticatorUri"></div>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
            <p>
 | 
			
		||||
                Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
 | 
			
		||||
                with a unique code. Enter the code in the confirmation box below.
 | 
			
		||||
            </p>
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-md-6">
 | 
			
		||||
                    <form id="send-code" method="post">
 | 
			
		||||
                        <div class="form-floating mb-3">
 | 
			
		||||
                            <input asp-for="Input.Code" class="form-control" autocomplete="off" placeholder="Please enter the code."/>
 | 
			
		||||
                            <label asp-for="Input.Code" class="control-label form-label">Verification Code</label>
 | 
			
		||||
                            <span asp-validation-for="Input.Code" class="text-danger"></span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <button type="submit" class="w-100 btn btn-lg btn-primary">Verify</button>
 | 
			
		||||
                        <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </li>
 | 
			
		||||
    </ol>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@section Scripts {
 | 
			
		||||
    <partial name="_ValidationScriptsPartial" />
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,188 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.Encodings.Web;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class EnableAuthenticatorModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly ILogger<EnableAuthenticatorModel> _logger;
 | 
			
		||||
        private readonly UrlEncoder _urlEncoder;
 | 
			
		||||
 | 
			
		||||
        private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
 | 
			
		||||
 | 
			
		||||
        public EnableAuthenticatorModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager,
 | 
			
		||||
            ILogger<EnableAuthenticatorModel> logger,
 | 
			
		||||
            UrlEncoder urlEncoder)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
            _urlEncoder = urlEncoder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string SharedKey { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string AuthenticatorUri { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string[] RecoveryCodes { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string StatusMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [BindProperty]
 | 
			
		||||
        public InputModel Input { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public class InputModel
 | 
			
		||||
        {
 | 
			
		||||
            /// <summary>
 | 
			
		||||
            ///     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.
 | 
			
		||||
            /// </summary>
 | 
			
		||||
            [Required]
 | 
			
		||||
            [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
 | 
			
		||||
            [DataType(DataType.Text)]
 | 
			
		||||
            [Display(Name = "Verification Code")]
 | 
			
		||||
            public string Code { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGetAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await LoadSharedKeyAndQrCodeUriAsync(user);
 | 
			
		||||
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnPostAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!ModelState.IsValid)
 | 
			
		||||
            {
 | 
			
		||||
                await LoadSharedKeyAndQrCodeUriAsync(user);
 | 
			
		||||
                return Page();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Strip spaces and hyphens
 | 
			
		||||
            var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
 | 
			
		||||
 | 
			
		||||
            var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
 | 
			
		||||
                user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
 | 
			
		||||
 | 
			
		||||
            if (!is2faTokenValid)
 | 
			
		||||
            {
 | 
			
		||||
                ModelState.AddModelError("Input.Code", "Verification code is invalid.");
 | 
			
		||||
                await LoadSharedKeyAndQrCodeUriAsync(user);
 | 
			
		||||
                return Page();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await _userManager.SetTwoFactorEnabledAsync(user, true);
 | 
			
		||||
            var userId = await _userManager.GetUserIdAsync(user);
 | 
			
		||||
            _logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);
 | 
			
		||||
 | 
			
		||||
            StatusMessage = "Your authenticator app has been verified.";
 | 
			
		||||
 | 
			
		||||
            if (await _userManager.CountRecoveryCodesAsync(user) == 0)
 | 
			
		||||
            {
 | 
			
		||||
                var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
 | 
			
		||||
                RecoveryCodes = recoveryCodes.ToArray();
 | 
			
		||||
                return RedirectToPage("./ShowRecoveryCodes");
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return RedirectToPage("./TwoFactorAuthentication");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task LoadSharedKeyAndQrCodeUriAsync(IdentityUser user)
 | 
			
		||||
        {
 | 
			
		||||
            // Load the authenticator key & QR code URI to display on the form
 | 
			
		||||
            var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
 | 
			
		||||
            if (string.IsNullOrEmpty(unformattedKey))
 | 
			
		||||
            {
 | 
			
		||||
                await _userManager.ResetAuthenticatorKeyAsync(user);
 | 
			
		||||
                unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SharedKey = FormatKey(unformattedKey);
 | 
			
		||||
 | 
			
		||||
            var email = await _userManager.GetEmailAsync(user);
 | 
			
		||||
            AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string FormatKey(string unformattedKey)
 | 
			
		||||
        {
 | 
			
		||||
            var result = new StringBuilder();
 | 
			
		||||
            int currentPosition = 0;
 | 
			
		||||
            while (currentPosition + 4 < unformattedKey.Length)
 | 
			
		||||
            {
 | 
			
		||||
                result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' ');
 | 
			
		||||
                currentPosition += 4;
 | 
			
		||||
            }
 | 
			
		||||
            if (currentPosition < unformattedKey.Length)
 | 
			
		||||
            {
 | 
			
		||||
                result.Append(unformattedKey.AsSpan(currentPosition));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return result.ToString().ToLowerInvariant();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string GenerateQrCodeUri(string email, string unformattedKey)
 | 
			
		||||
        {
 | 
			
		||||
            return string.Format(
 | 
			
		||||
                CultureInfo.InvariantCulture,
 | 
			
		||||
                AuthenticatorUriFormat,
 | 
			
		||||
                _urlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"),
 | 
			
		||||
                _urlEncoder.Encode(email),
 | 
			
		||||
                unformattedKey);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
@page
 | 
			
		||||
@model ExternalLoginsModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Manage your external logins";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.ExternalLogins;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<partial name="_StatusMessage" for="StatusMessage" />
 | 
			
		||||
@if (Model.CurrentLogins?.Count > 0)
 | 
			
		||||
{
 | 
			
		||||
    <h3>Registered Logins</h3>
 | 
			
		||||
    <table class="table">
 | 
			
		||||
        <tbody>
 | 
			
		||||
            @foreach (var login in Model.CurrentLogins)
 | 
			
		||||
            {
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td id="@($"login-provider-{login.LoginProvider}")">@login.ProviderDisplayName</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        @if (Model.ShowRemoveButton)
 | 
			
		||||
                        {
 | 
			
		||||
                            <form id="@($"remove-login-{login.LoginProvider}")" asp-page-handler="RemoveLogin" method="post">
 | 
			
		||||
                                <div>
 | 
			
		||||
                                    <input asp-for="@login.LoginProvider" name="LoginProvider" type="hidden" />
 | 
			
		||||
                                    <input asp-for="@login.ProviderKey" name="ProviderKey" type="hidden" />
 | 
			
		||||
                                    <button type="submit" class="btn btn-primary" title="Remove this @login.ProviderDisplayName login from your account">Remove</button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </form>
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            @:  
 | 
			
		||||
                        }
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            }
 | 
			
		||||
        </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
}
 | 
			
		||||
@if (Model.OtherLogins?.Count > 0)
 | 
			
		||||
{
 | 
			
		||||
    <h4>Add another service to log in.</h4>
 | 
			
		||||
    <hr />
 | 
			
		||||
    <form id="link-login-form" asp-page-handler="LinkLogin" method="post" class="form-horizontal">
 | 
			
		||||
        <div id="socialLoginList">
 | 
			
		||||
            <p>
 | 
			
		||||
                @foreach (var provider in Model.OtherLogins)
 | 
			
		||||
                {
 | 
			
		||||
                    <button id="@($"link-login-button-{provider.Name}")" type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
 | 
			
		||||
                }
 | 
			
		||||
            </p>
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										141
									
								
								Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,141 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Authentication;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class ExternalLoginsModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly SignInManager<IdentityUser> _signInManager;
 | 
			
		||||
        private readonly IUserStore<IdentityUser> _userStore;
 | 
			
		||||
 | 
			
		||||
        public ExternalLoginsModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager,
 | 
			
		||||
            SignInManager<IdentityUser> signInManager,
 | 
			
		||||
            IUserStore<IdentityUser> userStore)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _signInManager = signInManager;
 | 
			
		||||
            _userStore = userStore;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public IList<UserLoginInfo> CurrentLogins { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public IList<AuthenticationScheme> OtherLogins { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool ShowRemoveButton { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string StatusMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGetAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            CurrentLogins = await _userManager.GetLoginsAsync(user);
 | 
			
		||||
            OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
 | 
			
		||||
                .Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
 | 
			
		||||
                .ToList();
 | 
			
		||||
 | 
			
		||||
            string passwordHash = null;
 | 
			
		||||
            if (_userStore is IUserPasswordStore<IdentityUser> userPasswordStore)
 | 
			
		||||
            {
 | 
			
		||||
                passwordHash = await userPasswordStore.GetPasswordHashAsync(user, HttpContext.RequestAborted);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ShowRemoveButton = passwordHash != null || CurrentLogins.Count > 1;
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnPostRemoveLoginAsync(string loginProvider, string providerKey)
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var result = await _userManager.RemoveLoginAsync(user, loginProvider, providerKey);
 | 
			
		||||
            if (!result.Succeeded)
 | 
			
		||||
            {
 | 
			
		||||
                StatusMessage = "The external login was not removed.";
 | 
			
		||||
                return RedirectToPage();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await _signInManager.RefreshSignInAsync(user);
 | 
			
		||||
            StatusMessage = "The external login was removed.";
 | 
			
		||||
            return RedirectToPage();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnPostLinkLoginAsync(string provider)
 | 
			
		||||
        {
 | 
			
		||||
            // Clear the existing external cookie to ensure a clean login process
 | 
			
		||||
            await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
 | 
			
		||||
 | 
			
		||||
            // Request a redirect to the external login provider to link a login for the current user
 | 
			
		||||
            var redirectUrl = Url.Page("./ExternalLogins", pageHandler: "LinkLoginCallback");
 | 
			
		||||
            var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
 | 
			
		||||
            return new ChallengeResult(provider, properties);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGetLinkLoginCallbackAsync()
 | 
			
		||||
        {
 | 
			
		||||
            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 info = await _signInManager.GetExternalLoginInfoAsync(userId);
 | 
			
		||||
            if (info == null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new InvalidOperationException($"Unexpected error occurred loading external login info.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var result = await _userManager.AddLoginAsync(user, info);
 | 
			
		||||
            if (!result.Succeeded)
 | 
			
		||||
            {
 | 
			
		||||
                StatusMessage = "The external login was not added. External logins can only be associated with one account.";
 | 
			
		||||
                return RedirectToPage();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Clear the existing external cookie to ensure a clean login process
 | 
			
		||||
            await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
 | 
			
		||||
 | 
			
		||||
            StatusMessage = "The external login was added.";
 | 
			
		||||
            return RedirectToPage();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
@page
 | 
			
		||||
@model GenerateRecoveryCodesModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<partial name="_StatusMessage" for="StatusMessage" />
 | 
			
		||||
<h3>@ViewData["Title"]</h3>
 | 
			
		||||
<div class="alert alert-warning" role="alert">
 | 
			
		||||
    <p>
 | 
			
		||||
        <span class="glyphicon glyphicon-warning-sign"></span>
 | 
			
		||||
        <strong>Put these codes in a safe place.</strong>
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        If you lose your device and don't have the recovery codes you will lose access to your account.
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
 | 
			
		||||
        used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a>
 | 
			
		||||
    </p>
 | 
			
		||||
</div>
 | 
			
		||||
<div>
 | 
			
		||||
    <form method="post">
 | 
			
		||||
        <button class="btn btn-danger" type="submit">Generate Recovery Codes</button>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,82 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class GenerateRecoveryCodesModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly ILogger<GenerateRecoveryCodesModel> _logger;
 | 
			
		||||
 | 
			
		||||
        public GenerateRecoveryCodesModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager,
 | 
			
		||||
            ILogger<GenerateRecoveryCodesModel> logger)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string[] RecoveryCodes { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string StatusMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGetAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
 | 
			
		||||
            if (!isTwoFactorEnabled)
 | 
			
		||||
            {
 | 
			
		||||
                throw new InvalidOperationException($"Cannot generate recovery codes for user because they do not have 2FA enabled.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnPostAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
 | 
			
		||||
            var userId = await _userManager.GetUserIdAsync(user);
 | 
			
		||||
            if (!isTwoFactorEnabled)
 | 
			
		||||
            {
 | 
			
		||||
                throw new InvalidOperationException($"Cannot generate recovery codes for user as they do not have 2FA enabled.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
 | 
			
		||||
            RecoveryCodes = recoveryCodes.ToArray();
 | 
			
		||||
 | 
			
		||||
            _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId);
 | 
			
		||||
            StatusMessage = "You have generated new recovery codes.";
 | 
			
		||||
            return RedirectToPage("./ShowRecoveryCodes");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								Areas/Identity/Pages/Account/Manage/Index.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Areas/Identity/Pages/Account/Manage/Index.cshtml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
@page
 | 
			
		||||
@model IndexModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Profile";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.Index;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<h3>@ViewData["Title"]</h3>
 | 
			
		||||
<partial name="_StatusMessage" for="StatusMessage" />
 | 
			
		||||
<div class="row">
 | 
			
		||||
    <div class="col-md-6">
 | 
			
		||||
        <form id="profile-form" method="post">
 | 
			
		||||
            <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
 | 
			
		||||
            <div class="form-floating mb-3">
 | 
			
		||||
                <input asp-for="Username" class="form-control" placeholder="Please choose your username." disabled />
 | 
			
		||||
                <label asp-for="Username" class="form-label"></label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-floating mb-3">
 | 
			
		||||
                <input asp-for="Input.PhoneNumber" class="form-control" placeholder="Please enter your phone number."/>
 | 
			
		||||
                <label asp-for="Input.PhoneNumber" class="form-label"></label>
 | 
			
		||||
                <span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <button id="update-profile-button" type="submit" class="w-100 btn btn-lg btn-primary">Save</button>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@section Scripts {
 | 
			
		||||
    <partial name="_ValidationScriptsPartial" />
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										118
									
								
								Areas/Identity/Pages/Account/Manage/Index.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								Areas/Identity/Pages/Account/Manage/Index.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,118 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
using System.Text.Encodings.Web;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class IndexModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly SignInManager<IdentityUser> _signInManager;
 | 
			
		||||
 | 
			
		||||
        public IndexModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager,
 | 
			
		||||
            SignInManager<IdentityUser> signInManager)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _signInManager = signInManager;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string Username { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string StatusMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [BindProperty]
 | 
			
		||||
        public InputModel Input { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public class InputModel
 | 
			
		||||
        {
 | 
			
		||||
            /// <summary>
 | 
			
		||||
            ///     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.
 | 
			
		||||
            /// </summary>
 | 
			
		||||
            [Phone]
 | 
			
		||||
            [Display(Name = "Phone number")]
 | 
			
		||||
            public string PhoneNumber { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task LoadAsync(IdentityUser user)
 | 
			
		||||
        {
 | 
			
		||||
            var userName = await _userManager.GetUserNameAsync(user);
 | 
			
		||||
            var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
 | 
			
		||||
 | 
			
		||||
            Username = userName;
 | 
			
		||||
 | 
			
		||||
            Input = new InputModel
 | 
			
		||||
            {
 | 
			
		||||
                PhoneNumber = phoneNumber
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGetAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await LoadAsync(user);
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnPostAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!ModelState.IsValid)
 | 
			
		||||
            {
 | 
			
		||||
                await LoadAsync(user);
 | 
			
		||||
                return Page();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
 | 
			
		||||
            if (Input.PhoneNumber != phoneNumber)
 | 
			
		||||
            {
 | 
			
		||||
                var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
 | 
			
		||||
                if (!setPhoneResult.Succeeded)
 | 
			
		||||
                {
 | 
			
		||||
                    StatusMessage = "Unexpected error when trying to set phone number.";
 | 
			
		||||
                    return RedirectToPage();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await _signInManager.RefreshSignInAsync(user);
 | 
			
		||||
            StatusMessage = "Your profile has been updated";
 | 
			
		||||
            return RedirectToPage();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										123
									
								
								Areas/Identity/Pages/Account/Manage/ManageNavPages.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								Areas/Identity/Pages/Account/Manage/ManageNavPages.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,123 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.Rendering;
 | 
			
		||||
 | 
			
		||||
namespace  turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     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.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static class ManageNavPages
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string Index => "Index";
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string Email => "Email";
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string ChangePassword => "ChangePassword";
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string DownloadPersonalData => "DownloadPersonalData";
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string DeletePersonalData => "DeletePersonalData";
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string ExternalLogins => "ExternalLogins";
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string PersonalData => "PersonalData";
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string TwoFactorAuthentication => "TwoFactorAuthentication";
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string PageNavClass(ViewContext viewContext, string page)
 | 
			
		||||
        {
 | 
			
		||||
            var activePage = viewContext.ViewData["ActivePage"] as string
 | 
			
		||||
                ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
 | 
			
		||||
            return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								Areas/Identity/Pages/Account/Manage/PersonalData.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Areas/Identity/Pages/Account/Manage/PersonalData.cshtml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
@page
 | 
			
		||||
@model PersonalDataModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Personal Data";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.PersonalData;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<h3>@ViewData["Title"]</h3>
 | 
			
		||||
 | 
			
		||||
<div class="row">
 | 
			
		||||
    <div class="col-md-6">
 | 
			
		||||
        <p>Your account contains personal data that you have given us. This page allows you to download or delete that data.</p>
 | 
			
		||||
        <p>
 | 
			
		||||
            <strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
 | 
			
		||||
        </p>
 | 
			
		||||
        <form id="download-data" asp-page="DownloadPersonalData" method="post">
 | 
			
		||||
            <button class="btn btn-primary" type="submit">Download</button>
 | 
			
		||||
        </form>
 | 
			
		||||
        <p>
 | 
			
		||||
            <a id="delete" asp-page="DeletePersonalData" class="btn btn-danger">Delete</a>
 | 
			
		||||
        </p>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@section Scripts {
 | 
			
		||||
    <partial name="_ValidationScriptsPartial" />
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class PersonalDataModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly ILogger<PersonalDataModel> _logger;
 | 
			
		||||
 | 
			
		||||
        public PersonalDataModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager,
 | 
			
		||||
            ILogger<PersonalDataModel> logger)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGet()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
@page
 | 
			
		||||
@model ResetAuthenticatorModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Reset authenticator key";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<partial name="_StatusMessage" for="StatusMessage" />
 | 
			
		||||
<h3>@ViewData["Title"]</h3>
 | 
			
		||||
<div class="alert alert-warning" role="alert">
 | 
			
		||||
    <p>
 | 
			
		||||
        <span class="glyphicon glyphicon-warning-sign"></span>
 | 
			
		||||
        <strong>If you reset your authenticator key your authenticator app will not work until you reconfigure it.</strong>
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        This process disables 2FA until you verify your authenticator app.
 | 
			
		||||
        If you do not complete your authenticator app configuration you may lose access to your account.
 | 
			
		||||
    </p>
 | 
			
		||||
</div>
 | 
			
		||||
<div>
 | 
			
		||||
    <form id="reset-authenticator-form" method="post">
 | 
			
		||||
        <button id="reset-authenticator-button" class="btn btn-danger" type="submit">Reset authenticator key</button>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class ResetAuthenticatorModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly SignInManager<IdentityUser> _signInManager;
 | 
			
		||||
        private readonly ILogger<ResetAuthenticatorModel> _logger;
 | 
			
		||||
 | 
			
		||||
        public ResetAuthenticatorModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager,
 | 
			
		||||
            SignInManager<IdentityUser> signInManager,
 | 
			
		||||
            ILogger<ResetAuthenticatorModel> logger)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _signInManager = signInManager;
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string StatusMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGet()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnPostAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await _userManager.SetTwoFactorEnabledAsync(user, false);
 | 
			
		||||
            await _userManager.ResetAuthenticatorKeyAsync(user);
 | 
			
		||||
            var userId = await _userManager.GetUserIdAsync(user);
 | 
			
		||||
            _logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id);
 | 
			
		||||
 | 
			
		||||
            await _signInManager.RefreshSignInAsync(user);
 | 
			
		||||
            StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.";
 | 
			
		||||
 | 
			
		||||
            return RedirectToPage("./EnableAuthenticator");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								Areas/Identity/Pages/Account/Manage/SetPassword.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Areas/Identity/Pages/Account/Manage/SetPassword.cshtml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
@page
 | 
			
		||||
@model SetPasswordModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Set password";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.ChangePassword;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<h3>Set your password</h3>
 | 
			
		||||
<partial name="_StatusMessage" for="StatusMessage" />
 | 
			
		||||
<p class="text-info">
 | 
			
		||||
    You do not have a local username/password for this site. Add a local
 | 
			
		||||
    account so you can log in without an external login.
 | 
			
		||||
</p>
 | 
			
		||||
<div class="row">
 | 
			
		||||
    <div class="col-md-6">
 | 
			
		||||
        <form id="set-password-form" method="post">
 | 
			
		||||
            <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
 | 
			
		||||
            <div class="form-floating mb-3">
 | 
			
		||||
                <input asp-for="Input.NewPassword" class="form-control" autocomplete="new-password" placeholder="Please enter your new password."/>
 | 
			
		||||
                <label asp-for="Input.NewPassword" class="form-label"></label>
 | 
			
		||||
                <span asp-validation-for="Input.NewPassword" class="text-danger"></span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-floating mb-3">
 | 
			
		||||
                <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" placeholder="Please confirm your new password."/>
 | 
			
		||||
                <label asp-for="Input.ConfirmPassword" class="form-label"></label>
 | 
			
		||||
                <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <button type="submit" class="w-100 btn btn-lg btn-primary">Set password</button>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@section Scripts {
 | 
			
		||||
    <partial name="_ValidationScriptsPartial" />
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,114 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class SetPasswordModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly SignInManager<IdentityUser> _signInManager;
 | 
			
		||||
 | 
			
		||||
        public SetPasswordModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager,
 | 
			
		||||
            SignInManager<IdentityUser> signInManager)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _signInManager = signInManager;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [BindProperty]
 | 
			
		||||
        public InputModel Input { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string StatusMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public class InputModel
 | 
			
		||||
        {
 | 
			
		||||
            /// <summary>
 | 
			
		||||
            ///     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.
 | 
			
		||||
            /// </summary>
 | 
			
		||||
            [Required]
 | 
			
		||||
            [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
 | 
			
		||||
            [DataType(DataType.Password)]
 | 
			
		||||
            [Display(Name = "New password")]
 | 
			
		||||
            public string NewPassword { get; set; }
 | 
			
		||||
 | 
			
		||||
            /// <summary>
 | 
			
		||||
            ///     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.
 | 
			
		||||
            /// </summary>
 | 
			
		||||
            [DataType(DataType.Password)]
 | 
			
		||||
            [Display(Name = "Confirm new password")]
 | 
			
		||||
            [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
 | 
			
		||||
            public string ConfirmPassword { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGetAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var hasPassword = await _userManager.HasPasswordAsync(user);
 | 
			
		||||
 | 
			
		||||
            if (hasPassword)
 | 
			
		||||
            {
 | 
			
		||||
                return RedirectToPage("./ChangePassword");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> 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 addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword);
 | 
			
		||||
            if (!addPasswordResult.Succeeded)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var error in addPasswordResult.Errors)
 | 
			
		||||
                {
 | 
			
		||||
                    ModelState.AddModelError(string.Empty, error.Description);
 | 
			
		||||
                }
 | 
			
		||||
                return Page();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await _signInManager.RefreshSignInAsync(user);
 | 
			
		||||
            StatusMessage = "Your password has been set.";
 | 
			
		||||
 | 
			
		||||
            return RedirectToPage();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
@page
 | 
			
		||||
@model ShowRecoveryCodesModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Recovery codes";
 | 
			
		||||
    ViewData["ActivePage"] = "TwoFactorAuthentication";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<partial name="_StatusMessage" for="StatusMessage" />
 | 
			
		||||
<h3>@ViewData["Title"]</h3>
 | 
			
		||||
<div class="alert alert-warning" role="alert">
 | 
			
		||||
    <p>
 | 
			
		||||
        <strong>Put these codes in a safe place.</strong>
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        If you lose your device and don't have the recovery codes you will lose access to your account.
 | 
			
		||||
    </p>
 | 
			
		||||
</div>
 | 
			
		||||
<div class="row">
 | 
			
		||||
    <div class="col-md-12">
 | 
			
		||||
        @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)
 | 
			
		||||
        {
 | 
			
		||||
            <code class="recovery-code">@Model.RecoveryCodes[row]</code><text> </text><code class="recovery-code">@Model.RecoveryCodes[row + 1]</code><br />
 | 
			
		||||
        }
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     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.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class ShowRecoveryCodesModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string[] RecoveryCodes { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string StatusMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public IActionResult OnGet()
 | 
			
		||||
        {
 | 
			
		||||
            if (RecoveryCodes == null || RecoveryCodes.Length == 0)
 | 
			
		||||
            {
 | 
			
		||||
                return RedirectToPage("./TwoFactorAuthentication");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
@page
 | 
			
		||||
@using Microsoft.AspNetCore.Http.Features
 | 
			
		||||
@model TwoFactorAuthenticationModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "Two-factor authentication (2FA)";
 | 
			
		||||
    ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<partial name="_StatusMessage" for="StatusMessage" />
 | 
			
		||||
<h3>@ViewData["Title"]</h3>
 | 
			
		||||
@{
 | 
			
		||||
    var consentFeature = HttpContext.Features.Get<ITrackingConsentFeature>();
 | 
			
		||||
    @if (consentFeature?.CanTrack ?? true)
 | 
			
		||||
    {
 | 
			
		||||
        @if (Model.Is2faEnabled)
 | 
			
		||||
        {
 | 
			
		||||
            if (Model.RecoveryCodesLeft == 0)
 | 
			
		||||
            {
 | 
			
		||||
                <div class="alert alert-danger">
 | 
			
		||||
                    <strong>You have no recovery codes left.</strong>
 | 
			
		||||
                    <p>You must <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a> before you can log in with a recovery code.</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            }
 | 
			
		||||
            else if (Model.RecoveryCodesLeft == 1)
 | 
			
		||||
            {
 | 
			
		||||
                <div class="alert alert-danger">
 | 
			
		||||
                    <strong>You have 1 recovery code left.</strong>
 | 
			
		||||
                    <p>You can <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            }
 | 
			
		||||
            else if (Model.RecoveryCodesLeft <= 3)
 | 
			
		||||
            {
 | 
			
		||||
                <div class="alert alert-warning">
 | 
			
		||||
                    <strong>You have @Model.RecoveryCodesLeft recovery codes left.</strong>
 | 
			
		||||
                    <p>You should <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (Model.IsMachineRemembered)
 | 
			
		||||
            {
 | 
			
		||||
                <form method="post" style="display: inline-block">
 | 
			
		||||
                    <button type="submit" class="btn btn-primary">Forget this browser</button>
 | 
			
		||||
                </form>
 | 
			
		||||
            }
 | 
			
		||||
            <a asp-page="./Disable2fa" class="btn btn-primary">Disable 2FA</a>
 | 
			
		||||
            <a asp-page="./GenerateRecoveryCodes" class="btn btn-primary">Reset recovery codes</a>
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        <h4>Authenticator app</h4>
 | 
			
		||||
        @if (!Model.HasAuthenticator)
 | 
			
		||||
        {
 | 
			
		||||
            <a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Add authenticator app</a>
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            <a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Set up authenticator app</a>
 | 
			
		||||
            <a id="reset-authenticator" asp-page="./ResetAuthenticator" class="btn btn-primary">Reset authenticator app</a>
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        <div class="alert alert-danger">
 | 
			
		||||
            <strong>Privacy and cookie policy have not been accepted.</strong>
 | 
			
		||||
            <p>You must accept the policy before you can enable two factor authentication.</p>
 | 
			
		||||
        </div>
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@section Scripts {
 | 
			
		||||
    <partial name="_ValidationScriptsPartial" />
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
// Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
// The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Identity;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
{
 | 
			
		||||
    public class TwoFactorAuthenticationModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        private readonly UserManager<IdentityUser> _userManager;
 | 
			
		||||
        private readonly SignInManager<IdentityUser> _signInManager;
 | 
			
		||||
        private readonly ILogger<TwoFactorAuthenticationModel> _logger;
 | 
			
		||||
 | 
			
		||||
        public TwoFactorAuthenticationModel(
 | 
			
		||||
            UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, ILogger<TwoFactorAuthenticationModel> logger)
 | 
			
		||||
        {
 | 
			
		||||
            _userManager = userManager;
 | 
			
		||||
            _signInManager = signInManager;
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool HasAuthenticator { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int RecoveryCodesLeft { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [BindProperty]
 | 
			
		||||
        public bool Is2faEnabled { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool IsMachineRemembered { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        ///     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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [TempData]
 | 
			
		||||
        public string StatusMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnGetAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null;
 | 
			
		||||
            Is2faEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
 | 
			
		||||
            IsMachineRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user);
 | 
			
		||||
            RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user);
 | 
			
		||||
 | 
			
		||||
            return Page();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> OnPostAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var user = await _userManager.GetUserAsync(User);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            {
 | 
			
		||||
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await _signInManager.ForgetTwoFactorClientAsync();
 | 
			
		||||
            StatusMessage = "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code.";
 | 
			
		||||
            return RedirectToPage();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								Areas/Identity/Pages/Account/Manage/_Layout.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								Areas/Identity/Pages/Account/Manage/_Layout.cshtml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
@{
 | 
			
		||||
    if (ViewData.TryGetValue("ParentLayout", out var parentLayout) && parentLayout !=  null)
 | 
			
		||||
    {
 | 
			
		||||
        Layout = parentLayout.ToString();
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        Layout = "/Areas/Identity/Pages/_Layout.cshtml";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<h1>Manage your account</h1>
 | 
			
		||||
 | 
			
		||||
<div>
 | 
			
		||||
    <h2>Change your account settings</h2>
 | 
			
		||||
    <hr />
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-md-3">
 | 
			
		||||
            <partial name="_ManageNav" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-9">
 | 
			
		||||
            @RenderBody()
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@section Scripts {
 | 
			
		||||
    @RenderSection("Scripts", required: false)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
@inject SignInManager<IdentityUser> SignInManager
 | 
			
		||||
@{
 | 
			
		||||
    var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
 | 
			
		||||
}
 | 
			
		||||
<ul class="nav nav-pills flex-column">
 | 
			
		||||
    <li class="nav-item"><a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">Profile</a></li>
 | 
			
		||||
    <li class="nav-item"><a class="nav-link @ManageNavPages.EmailNavClass(ViewContext)" id="email" asp-page="./Email">Email</a></li>
 | 
			
		||||
    <li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">Password</a></li>
 | 
			
		||||
    @if (hasExternalLogins)
 | 
			
		||||
    {
 | 
			
		||||
        <li id="external-logins" class="nav-item"><a id="external-login" class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-page="./ExternalLogins">External logins</a></li>
 | 
			
		||||
    }
 | 
			
		||||
    <li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>
 | 
			
		||||
    <li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
							
								
								
									
										10
									
								
								Areas/Identity/Pages/Account/Manage/_StatusMessage.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								Areas/Identity/Pages/Account/Manage/_StatusMessage.cshtml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
@model string
 | 
			
		||||
 | 
			
		||||
@if (!String.IsNullOrEmpty(Model))
 | 
			
		||||
{
 | 
			
		||||
    var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
 | 
			
		||||
    <div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
 | 
			
		||||
        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 | 
			
		||||
        @Model
 | 
			
		||||
    </div>
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
@using turf_tasker.Areas.Identity.Pages.Account.Manage
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue