+
diff --git a/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs b/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs
new file mode 100644
index 0000000..9eca491
--- /dev/null
+++ b/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs
@@ -0,0 +1,23 @@
+// 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.Mvc.RazorPages;
+
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class AccessDeniedModel : PageModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public void OnGet()
+ {
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/ConfirmEmail.cshtml b/Areas/Identity/Pages/Account/ConfirmEmail.cshtml
new file mode 100644
index 0000000..5be0410
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ConfirmEmail.cshtml
@@ -0,0 +1,8 @@
+@page
+@model ConfirmEmailModel
+@{
+ ViewData["Title"] = "Confirm email";
+}
+
+
@ViewData["Title"]
+
diff --git a/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs b/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs
new file mode 100644
index 0000000..7d99417
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs
@@ -0,0 +1,51 @@
+// 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.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ public class ConfirmEmailModel : PageModel
+ {
+ private readonly UserManager _userManager;
+
+ public ConfirmEmailModel(UserManager userManager)
+ {
+ _userManager = userManager;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+ public async Task OnGetAsync(string userId, string code)
+ {
+ if (userId == null || code == null)
+ {
+ return RedirectToPage("/Index");
+ }
+
+ var user = await _userManager.FindByIdAsync(userId);
+ if (user == null)
+ {
+ return NotFound($"Unable to load user with ID '{userId}'.");
+ }
+
+ code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
+ var result = await _userManager.ConfirmEmailAsync(user, code);
+ StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
+ return Page();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml b/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml
new file mode 100644
index 0000000..190601c
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml
@@ -0,0 +1,8 @@
+@page
+@model ConfirmEmailChangeModel
+@{
+ ViewData["Title"] = "Confirm email change";
+}
+
+
@ViewData["Title"]
+
diff --git a/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs b/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs
new file mode 100644
index 0000000..bf69f5c
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs
@@ -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.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ public class ConfirmEmailChangeModel : PageModel
+ {
+ private readonly UserManager _userManager;
+ private readonly SignInManager _signInManager;
+
+ public ConfirmEmailChangeModel(UserManager userManager, SignInManager signInManager)
+ {
+ _userManager = userManager;
+ _signInManager = signInManager;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+
+ public async Task OnGetAsync(string userId, string email, string code)
+ {
+ if (userId == null || email == null || code == null)
+ {
+ return RedirectToPage("/Index");
+ }
+
+ var user = await _userManager.FindByIdAsync(userId);
+ if (user == null)
+ {
+ return NotFound($"Unable to load user with ID '{userId}'.");
+ }
+
+ code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
+ var result = await _userManager.ChangeEmailAsync(user, email, code);
+ if (!result.Succeeded)
+ {
+ StatusMessage = "Error changing email.";
+ return Page();
+ }
+
+ // In our UI email and user name are one and the same, so when we update the email
+ // we need to update the user name.
+ var setUserNameResult = await _userManager.SetUserNameAsync(user, email);
+ if (!setUserNameResult.Succeeded)
+ {
+ StatusMessage = "Error changing user name.";
+ return Page();
+ }
+
+ await _signInManager.RefreshSignInAsync(user);
+ StatusMessage = "Thank you for confirming your email change.";
+ return Page();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/ExternalLogin.cshtml b/Areas/Identity/Pages/Account/ExternalLogin.cshtml
new file mode 100644
index 0000000..4e38218
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ExternalLogin.cshtml
@@ -0,0 +1,33 @@
+@page
+@model ExternalLoginModel
+@{
+ ViewData["Title"] = "Register";
+}
+
+
@ViewData["Title"]
+
Associate your @Model.ProviderDisplayName account.
+
+
+
+ You've successfully authenticated with @Model.ProviderDisplayName.
+ Please enter an email address for this site below and click the Register button to finish
+ logging in.
+
+
+
+
+
+
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs b/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs
new file mode 100644
index 0000000..5837b12
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs
@@ -0,0 +1,223 @@
+// 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.Security.Claims;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.Extensions.Options;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.UI.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Logging;
+
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ [AllowAnonymous]
+ public class ExternalLoginModel : PageModel
+ {
+ private readonly SignInManager _signInManager;
+ private readonly UserManager _userManager;
+ private readonly IUserStore _userStore;
+ private readonly IUserEmailStore _emailStore;
+ private readonly IEmailSender _emailSender;
+ private readonly ILogger _logger;
+
+ public ExternalLoginModel(
+ SignInManager signInManager,
+ UserManager userManager,
+ IUserStore userStore,
+ ILogger logger,
+ IEmailSender emailSender)
+ {
+ _signInManager = signInManager;
+ _userManager = userManager;
+ _userStore = userStore;
+ _emailStore = GetEmailStore();
+ _logger = logger;
+ _emailSender = emailSender;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public string ProviderDisplayName { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public string ReturnUrl { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string ErrorMessage { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [EmailAddress]
+ public string Email { get; set; }
+ }
+
+ public IActionResult OnGet() => RedirectToPage("./Login");
+
+ public IActionResult OnPost(string provider, string returnUrl = null)
+ {
+ // Request a redirect to the external login provider.
+ var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
+ var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
+ return new ChallengeResult(provider, properties);
+ }
+
+ public async Task OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
+ {
+ returnUrl = returnUrl ?? Url.Content("~/");
+ if (remoteError != null)
+ {
+ ErrorMessage = $"Error from external provider: {remoteError}";
+ return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
+ }
+ var info = await _signInManager.GetExternalLoginInfoAsync();
+ if (info == null)
+ {
+ ErrorMessage = "Error loading external login information.";
+ return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
+ }
+
+ // Sign in the user with this external login provider if the user already has a login.
+ var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
+ if (result.Succeeded)
+ {
+ _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
+ return LocalRedirect(returnUrl);
+ }
+ if (result.IsLockedOut)
+ {
+ return RedirectToPage("./Lockout");
+ }
+ else
+ {
+ // If the user does not have an account, then ask the user to create an account.
+ ReturnUrl = returnUrl;
+ ProviderDisplayName = info.ProviderDisplayName;
+ if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
+ {
+ Input = new InputModel
+ {
+ Email = info.Principal.FindFirstValue(ClaimTypes.Email)
+ };
+ }
+ return Page();
+ }
+ }
+
+ public async Task OnPostConfirmationAsync(string returnUrl = null)
+ {
+ returnUrl = returnUrl ?? Url.Content("~/");
+ // Get the information about the user from the external login provider
+ var info = await _signInManager.GetExternalLoginInfoAsync();
+ if (info == null)
+ {
+ ErrorMessage = "Error loading external login information during confirmation.";
+ return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
+ }
+
+ if (ModelState.IsValid)
+ {
+ var user = CreateUser();
+
+ await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
+ await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
+
+ var result = await _userManager.CreateAsync(user);
+ if (result.Succeeded)
+ {
+ result = await _userManager.AddLoginAsync(user, info);
+ if (result.Succeeded)
+ {
+ _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
+
+ var userId = await _userManager.GetUserIdAsync(user);
+ var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
+ code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+ var callbackUrl = Url.Page(
+ "/Account/ConfirmEmail",
+ pageHandler: null,
+ values: new { area = "Identity", userId = userId, code = code },
+ protocol: Request.Scheme);
+
+ await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
+ $"Please confirm your account by clicking here.");
+
+ // If account confirmation is required, we need to show the link if we don't have a real email sender
+ if (_userManager.Options.SignIn.RequireConfirmedAccount)
+ {
+ return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
+ }
+
+ await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
+ return LocalRedirect(returnUrl);
+ }
+ }
+ foreach (var error in result.Errors)
+ {
+ ModelState.AddModelError(string.Empty, error.Description);
+ }
+ }
+
+ ProviderDisplayName = info.ProviderDisplayName;
+ ReturnUrl = returnUrl;
+ return Page();
+ }
+
+ private IdentityUser CreateUser()
+ {
+ try
+ {
+ return Activator.CreateInstance();
+ }
+ catch
+ {
+ throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " +
+ $"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor, or alternatively " +
+ $"override the external login page in /Areas/Identity/Pages/Account/ExternalLogin.cshtml");
+ }
+ }
+
+ private IUserEmailStore GetEmailStore()
+ {
+ if (!_userManager.SupportsUserEmail)
+ {
+ throw new NotSupportedException("The default UI requires a user store with email support.");
+ }
+ return (IUserEmailStore)_userStore;
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/ForgotPassword.cshtml b/Areas/Identity/Pages/Account/ForgotPassword.cshtml
new file mode 100644
index 0000000..5fba798
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ForgotPassword.cshtml
@@ -0,0 +1,26 @@
+@page
+@model ForgotPasswordModel
+@{
+ ViewData["Title"] = "Forgot your password?";
+}
+
+
@ViewData["Title"]
+
Enter your email.
+
+
+
+
+
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs b/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs
new file mode 100644
index 0000000..e676abf
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs
@@ -0,0 +1,84 @@
+// 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.Authorization;
+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
+{
+ public class ForgotPasswordModel : PageModel
+ {
+ private readonly UserManager _userManager;
+ private readonly IEmailSender _emailSender;
+
+ public ForgotPasswordModel(UserManager userManager, IEmailSender emailSender)
+ {
+ _userManager = userManager;
+ _emailSender = emailSender;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [EmailAddress]
+ public string Email { get; set; }
+ }
+
+ public async Task OnPostAsync()
+ {
+ if (ModelState.IsValid)
+ {
+ var user = await _userManager.FindByEmailAsync(Input.Email);
+ if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
+ {
+ // Don't reveal that the user does not exist or is not confirmed
+ return RedirectToPage("./ForgotPasswordConfirmation");
+ }
+
+ // For more information on how to enable account confirmation and password reset please
+ // visit https://go.microsoft.com/fwlink/?LinkID=532713
+ var code = await _userManager.GeneratePasswordResetTokenAsync(user);
+ code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+ var callbackUrl = Url.Page(
+ "/Account/ResetPassword",
+ pageHandler: null,
+ values: new { area = "Identity", code },
+ protocol: Request.Scheme);
+
+ await _emailSender.SendEmailAsync(
+ Input.Email,
+ "Reset Password",
+ $"Please reset your password by clicking here.");
+
+ return RedirectToPage("./ForgotPasswordConfirmation");
+ }
+
+ return Page();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml b/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml
new file mode 100644
index 0000000..a606993
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml
@@ -0,0 +1,10 @@
+@page
+@model ForgotPasswordConfirmation
+@{
+ ViewData["Title"] = "Forgot password confirmation";
+}
+
+
@ViewData["Title"]
+
+ Please check your email to reset your password.
+
diff --git a/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs b/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs
new file mode 100644
index 0000000..3606e6d
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs
@@ -0,0 +1,25 @@
+// 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.Authorization;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [AllowAnonymous]
+ public class ForgotPasswordConfirmation : PageModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public void OnGet()
+ {
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Lockout.cshtml b/Areas/Identity/Pages/Account/Lockout.cshtml
new file mode 100644
index 0000000..0fa0db0
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Lockout.cshtml
@@ -0,0 +1,10 @@
+@page
+@model LockoutModel
+@{
+ ViewData["Title"] = "Locked out";
+}
+
+
+
@ViewData["Title"]
+
This account has been locked out, please try again later.
+
diff --git a/Areas/Identity/Pages/Account/Lockout.cshtml.cs b/Areas/Identity/Pages/Account/Lockout.cshtml.cs
new file mode 100644
index 0000000..f3c05e2
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Lockout.cshtml.cs
@@ -0,0 +1,25 @@
+// 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.Authorization;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [AllowAnonymous]
+ public class LockoutModel : PageModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public void OnGet()
+ {
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Login.cshtml b/Areas/Identity/Pages/Account/Login.cshtml
new file mode 100644
index 0000000..5bbd418
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Login.cshtml
@@ -0,0 +1,83 @@
+@page
+@model LoginModel
+
+@{
+ ViewData["Title"] = "Log in";
+}
+
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/Login.cshtml.cs b/Areas/Identity/Pages/Account/Login.cshtml.cs
new file mode 100644
index 0000000..f72ade8
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Login.cshtml.cs
@@ -0,0 +1,140 @@
+// 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.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.UI.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ public class LoginModel : PageModel
+ {
+ private readonly SignInManager _signInManager;
+ private readonly ILogger _logger;
+
+ public LoginModel(SignInManager signInManager, ILogger logger)
+ {
+ _signInManager = signInManager;
+ _logger = logger;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public IList ExternalLogins { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public string ReturnUrl { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string ErrorMessage { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [EmailAddress]
+ public string Email { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [DataType(DataType.Password)]
+ public string Password { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Display(Name = "Remember me?")]
+ public bool RememberMe { get; set; }
+ }
+
+ public async Task OnGetAsync(string returnUrl = null)
+ {
+ if (!string.IsNullOrEmpty(ErrorMessage))
+ {
+ ModelState.AddModelError(string.Empty, ErrorMessage);
+ }
+
+ returnUrl ??= Url.Content("~/");
+
+ // Clear the existing external cookie to ensure a clean login process
+ await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
+
+ ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
+
+ ReturnUrl = returnUrl;
+ }
+
+ public async Task OnPostAsync(string returnUrl = null)
+ {
+ returnUrl ??= Url.Content("~/");
+
+ ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
+
+ if (ModelState.IsValid)
+ {
+ // This doesn't count login failures towards account lockout
+ // To enable password failures to trigger account lockout, set lockoutOnFailure: true
+ var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
+ if (result.Succeeded)
+ {
+ _logger.LogInformation("User logged in.");
+ return LocalRedirect(returnUrl);
+ }
+ if (result.RequiresTwoFactor)
+ {
+ return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
+ }
+ if (result.IsLockedOut)
+ {
+ _logger.LogWarning("User account locked out.");
+ return RedirectToPage("./Lockout");
+ }
+ else
+ {
+ ModelState.AddModelError(string.Empty, "Invalid login attempt.");
+ return Page();
+ }
+ }
+
+ // If we got this far, something failed, redisplay form
+ return Page();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/LoginWith2fa.cshtml b/Areas/Identity/Pages/Account/LoginWith2fa.cshtml
new file mode 100644
index 0000000..fc6e25c
--- /dev/null
+++ b/Areas/Identity/Pages/Account/LoginWith2fa.cshtml
@@ -0,0 +1,39 @@
+@page
+@model LoginWith2faModel
+@{
+ ViewData["Title"] = "Two-factor authentication";
+}
+
+
@ViewData["Title"]
+
+
Your login is protected with an authenticator app. Enter your authenticator code below.
+
+@section Scripts {
+
+}
\ No newline at end of file
diff --git a/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs b/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs
new file mode 100644
index 0000000..3c6c15e
--- /dev/null
+++ b/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs
@@ -0,0 +1,131 @@
+// 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.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.Extensions.Logging;
+
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ public class LoginWith2faModel : PageModel
+ {
+ private readonly SignInManager _signInManager;
+ private readonly UserManager _userManager;
+ private readonly ILogger _logger;
+
+ public LoginWith2faModel(
+ SignInManager signInManager,
+ UserManager userManager,
+ ILogger logger)
+ {
+ _signInManager = signInManager;
+ _userManager = userManager;
+ _logger = logger;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public bool RememberMe { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public string ReturnUrl { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+ [DataType(DataType.Text)]
+ [Display(Name = "Authenticator code")]
+ public string TwoFactorCode { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Display(Name = "Remember this machine")]
+ public bool RememberMachine { get; set; }
+ }
+
+ public async Task OnGetAsync(bool rememberMe, string returnUrl = null)
+ {
+ // Ensure the user has gone through the username & password screen first
+ var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
+
+ if (user == null)
+ {
+ throw new InvalidOperationException($"Unable to load two-factor authentication user.");
+ }
+
+ ReturnUrl = returnUrl;
+ RememberMe = rememberMe;
+
+ return Page();
+ }
+
+ public async Task OnPostAsync(bool rememberMe, string returnUrl = null)
+ {
+ if (!ModelState.IsValid)
+ {
+ return Page();
+ }
+
+ returnUrl = returnUrl ?? Url.Content("~/");
+
+ var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
+ if (user == null)
+ {
+ throw new InvalidOperationException($"Unable to load two-factor authentication user.");
+ }
+
+ var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
+
+ var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine);
+
+ var userId = await _userManager.GetUserIdAsync(user);
+
+ if (result.Succeeded)
+ {
+ _logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id);
+ return LocalRedirect(returnUrl);
+ }
+ else if (result.IsLockedOut)
+ {
+ _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
+ return RedirectToPage("./Lockout");
+ }
+ else
+ {
+ _logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id);
+ ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
+ return Page();
+ }
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml b/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml
new file mode 100644
index 0000000..b59c337
--- /dev/null
+++ b/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml
@@ -0,0 +1,29 @@
+@page
+@model LoginWithRecoveryCodeModel
+@{
+ ViewData["Title"] = "Recovery code verification";
+}
+
+
@ViewData["Title"]
+
+
+ You have requested to log in with a recovery code. This login will not be remembered until you provide
+ an authenticator app code at log in or disable 2FA and log in again.
+
+
+
+
+
+
+
+@section Scripts {
+
+}
\ No newline at end of file
diff --git a/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs b/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs
new file mode 100644
index 0000000..0a6fcbf
--- /dev/null
+++ b/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs
@@ -0,0 +1,112 @@
+// 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.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ public class LoginWithRecoveryCodeModel : PageModel
+ {
+ private readonly SignInManager _signInManager;
+ private readonly UserManager _userManager;
+ private readonly ILogger _logger;
+
+ public LoginWithRecoveryCodeModel(
+ SignInManager signInManager,
+ UserManager userManager,
+ ILogger logger)
+ {
+ _signInManager = signInManager;
+ _userManager = userManager;
+ _logger = logger;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public string ReturnUrl { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ [Required]
+ [DataType(DataType.Text)]
+ [Display(Name = "Recovery Code")]
+ public string RecoveryCode { get; set; }
+ }
+
+ public async Task OnGetAsync(string returnUrl = null)
+ {
+ // Ensure the user has gone through the username & password screen first
+ var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
+ if (user == null)
+ {
+ throw new InvalidOperationException($"Unable to load two-factor authentication user.");
+ }
+
+ ReturnUrl = returnUrl;
+
+ return Page();
+ }
+
+ public async Task OnPostAsync(string returnUrl = null)
+ {
+ if (!ModelState.IsValid)
+ {
+ return Page();
+ }
+
+ var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
+ if (user == null)
+ {
+ throw new InvalidOperationException($"Unable to load two-factor authentication user.");
+ }
+
+ var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
+
+ var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
+
+ var userId = await _userManager.GetUserIdAsync(user);
+
+ if (result.Succeeded)
+ {
+ _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id);
+ return LocalRedirect(returnUrl ?? Url.Content("~/"));
+ }
+ if (result.IsLockedOut)
+ {
+ _logger.LogWarning("User account locked out.");
+ return RedirectToPage("./Lockout");
+ }
+ else
+ {
+ _logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id);
+ ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
+ return Page();
+ }
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Logout.cshtml b/Areas/Identity/Pages/Account/Logout.cshtml
new file mode 100644
index 0000000..ad0161c
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Logout.cshtml
@@ -0,0 +1,21 @@
+@page
+@model LogoutModel
+@{
+ ViewData["Title"] = "Log out";
+}
+
+
+
You have successfully logged out of the application.
+ }
+ }
+
diff --git a/Areas/Identity/Pages/Account/Logout.cshtml.cs b/Areas/Identity/Pages/Account/Logout.cshtml.cs
new file mode 100644
index 0000000..f083544
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Logout.cshtml.cs
@@ -0,0 +1,42 @@
+// 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.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ public class LogoutModel : PageModel
+ {
+ private readonly SignInManager _signInManager;
+ private readonly ILogger _logger;
+
+ public LogoutModel(SignInManager signInManager, ILogger logger)
+ {
+ _signInManager = signInManager;
+ _logger = logger;
+ }
+
+ public async Task OnPost(string returnUrl = null)
+ {
+ await _signInManager.SignOutAsync();
+ _logger.LogInformation("User logged out.");
+ if (returnUrl != null)
+ {
+ return LocalRedirect(returnUrl);
+ }
+ else
+ {
+ // This needs to be a redirect so that the browser performs a new
+ // request and the identity for the user gets updated.
+ return RedirectToPage();
+ }
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml b/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml
new file mode 100644
index 0000000..1c02d1a
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml
@@ -0,0 +1,36 @@
+@page
+@model ChangePasswordModel
+@{
+ ViewData["Title"] = "Change password";
+ ViewData["ActivePage"] = ManageNavPages.ChangePassword;
+}
+
+
@ViewData["Title"]
+
+
+
+
+
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs b/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs
new file mode 100644
index 0000000..27dfde1
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs
@@ -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 _userManager;
+ private readonly SignInManager _signInManager;
+ private readonly ILogger _logger;
+
+ public ChangePasswordModel(
+ UserManager userManager,
+ SignInManager signInManager,
+ ILogger logger)
+ {
+ _userManager = userManager;
+ _signInManager = signInManager;
+ _logger = logger;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [DataType(DataType.Password)]
+ [Display(Name = "Current password")]
+ public string OldPassword { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [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; }
+
+ ///
+ /// 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.
+ ///
+ [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 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 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();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml b/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml
new file mode 100644
index 0000000..3334140
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml
@@ -0,0 +1,33 @@
+@page
+@model DeletePersonalDataModel
+@{
+ ViewData["Title"] = "Delete Personal Data";
+ ViewData["ActivePage"] = ManageNavPages.PersonalData;
+}
+
+
@ViewData["Title"]
+
+
+
+ Deleting this data will permanently remove your account, and this cannot be recovered.
+
+
+
+
+
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs b/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs
new file mode 100644
index 0000000..9fb652c
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs
@@ -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 _userManager;
+ private readonly SignInManager _signInManager;
+ private readonly ILogger _logger;
+
+ public DeletePersonalDataModel(
+ UserManager userManager,
+ SignInManager signInManager,
+ ILogger logger)
+ {
+ _userManager = userManager;
+ _signInManager = signInManager;
+ _logger = logger;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [DataType(DataType.Password)]
+ public string Password { get; set; }
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public bool RequirePassword { get; set; }
+
+ public async Task 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 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("~/");
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml b/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml
new file mode 100644
index 0000000..0084ed4
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml
@@ -0,0 +1,25 @@
+@page
+@model Disable2faModel
+@{
+ ViewData["Title"] = "Disable two-factor authentication (2FA)";
+ ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
+}
+
+
+
@ViewData["Title"]
+
+
+
+ This action only disables 2FA.
+
+
+ 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 reset your authenticator keys.
+
+
+
+
+
+
diff --git a/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs b/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs
new file mode 100644
index 0000000..5aa69d0
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs
@@ -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 _userManager;
+ private readonly ILogger _logger;
+
+ public Disable2faModel(
+ UserManager userManager,
+ ILogger logger)
+ {
+ _userManager = userManager;
+ _logger = logger;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+
+ public async Task 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 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");
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml b/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml
new file mode 100644
index 0000000..eb8b3ba
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml
@@ -0,0 +1,12 @@
+@page
+@model DownloadPersonalDataModel
+@{
+ ViewData["Title"] = "Download Your Data";
+ ViewData["ActivePage"] = ManageNavPages.PersonalData;
+}
+
+
@ViewData["Title"]
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs b/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs
new file mode 100644
index 0000000..1816832
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs
@@ -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 _userManager;
+ private readonly ILogger _logger;
+
+ public DownloadPersonalDataModel(
+ UserManager userManager,
+ ILogger logger)
+ {
+ _userManager = userManager;
+ _logger = logger;
+ }
+
+ public IActionResult OnGet()
+ {
+ return NotFound();
+ }
+
+ public async Task 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();
+ 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");
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/Email.cshtml b/Areas/Identity/Pages/Account/Manage/Email.cshtml
new file mode 100644
index 0000000..a08a960
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/Email.cshtml
@@ -0,0 +1,44 @@
+@page
+@model EmailModel
+@{
+ ViewData["Title"] = "Manage Email";
+ ViewData["ActivePage"] = ManageNavPages.Email;
+}
+
+
@ViewData["Title"]
+
+
+
+
+
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs b/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs
new file mode 100644
index 0000000..fcdb65e
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs
@@ -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 _userManager;
+ private readonly SignInManager _signInManager;
+ private readonly IEmailSender _emailSender;
+
+ public EmailModel(
+ UserManager userManager,
+ SignInManager signInManager,
+ IEmailSender emailSender)
+ {
+ _userManager = userManager;
+ _signInManager = signInManager;
+ _emailSender = emailSender;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public string Email { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public bool IsEmailConfirmed { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [EmailAddress]
+ [Display(Name = "New email")]
+ public string NewEmail { get; set; }
+ }
+
+ 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 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 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 clicking here.");
+
+ StatusMessage = "Confirmation link to change email sent. Please check your email.";
+ return RedirectToPage();
+ }
+
+ StatusMessage = "Your email is unchanged.";
+ return RedirectToPage();
+ }
+
+ public async Task 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 clicking here.");
+
+ StatusMessage = "Verification email sent. Please check your email.";
+ return RedirectToPage();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml b/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml
new file mode 100644
index 0000000..f6c7120
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml
@@ -0,0 +1,53 @@
+@page
+@model EnableAuthenticatorModel
+@{
+ ViewData["Title"] = "Configure authenticator app";
+ ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
+}
+
+
+
@ViewData["Title"]
+
+
To use an authenticator app go through the following steps:
+
+
+
+ Download a two-factor authenticator app like Microsoft Authenticator for
+ Android and
+ iOS or
+ Google Authenticator for
+ Android and
+ iOS.
+
+
+
+
Scan the QR Code or enter this key @Model.SharedKey into your two factor authenticator app. Spaces and casing do not matter.
+ 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.
+
+
+
+
+
+
+
+
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs b/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs
new file mode 100644
index 0000000..27755f7
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs
@@ -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 _userManager;
+ private readonly ILogger _logger;
+ private readonly UrlEncoder _urlEncoder;
+
+ private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
+
+ public EnableAuthenticatorModel(
+ UserManager userManager,
+ ILogger logger,
+ UrlEncoder urlEncoder)
+ {
+ _userManager = userManager;
+ _logger = logger;
+ _urlEncoder = urlEncoder;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public string SharedKey { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public string AuthenticatorUri { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string[] RecoveryCodes { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [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 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 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);
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml b/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml
new file mode 100644
index 0000000..f6171f8
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml
@@ -0,0 +1,53 @@
+@page
+@model ExternalLoginsModel
+@{
+ ViewData["Title"] = "Manage your external logins";
+ ViewData["ActivePage"] = ManageNavPages.ExternalLogins;
+}
+
+
+@if (Model.CurrentLogins?.Count > 0)
+{
+
Registered Logins
+
+
+ @foreach (var login in Model.CurrentLogins)
+ {
+
+
+
+}
diff --git a/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs b/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs
new file mode 100644
index 0000000..2c1338d
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs
@@ -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 _userManager;
+ private readonly SignInManager _signInManager;
+ private readonly IUserStore _userStore;
+
+ public ExternalLoginsModel(
+ UserManager userManager,
+ SignInManager signInManager,
+ IUserStore userStore)
+ {
+ _userManager = userManager;
+ _signInManager = signInManager;
+ _userStore = userStore;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public IList CurrentLogins { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public IList OtherLogins { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public bool ShowRemoveButton { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+
+ public async Task 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 userPasswordStore)
+ {
+ passwordHash = await userPasswordStore.GetPasswordHashAsync(user, HttpContext.RequestAborted);
+ }
+
+ ShowRemoveButton = passwordHash != null || CurrentLogins.Count > 1;
+ return Page();
+ }
+
+ public async Task 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 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 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();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml b/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml
new file mode 100644
index 0000000..1b3870d
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml
@@ -0,0 +1,27 @@
+@page
+@model GenerateRecoveryCodesModel
+@{
+ ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
+ ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
+}
+
+
+
@ViewData["Title"]
+
+
+
+ Put these codes in a safe place.
+
+
+ If you lose your device and don't have the recovery codes you will lose access to your account.
+
+
+ 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 reset your authenticator keys.
+
+
+
+
+
diff --git a/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs b/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs
new file mode 100644
index 0000000..a814ca1
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs
@@ -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 _userManager;
+ private readonly ILogger _logger;
+
+ public GenerateRecoveryCodesModel(
+ UserManager userManager,
+ ILogger logger)
+ {
+ _userManager = userManager;
+ _logger = logger;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string[] RecoveryCodes { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+
+ public async Task 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 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");
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/Index.cshtml b/Areas/Identity/Pages/Account/Manage/Index.cshtml
new file mode 100644
index 0000000..5c91475
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/Index.cshtml
@@ -0,0 +1,30 @@
+@page
+@model IndexModel
+@{
+ ViewData["Title"] = "Profile";
+ ViewData["ActivePage"] = ManageNavPages.Index;
+}
+
+
@ViewData["Title"]
+
+
+
+
+
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs b/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs
new file mode 100644
index 0000000..fb90b76
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs
@@ -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 _userManager;
+ private readonly SignInManager _signInManager;
+
+ public IndexModel(
+ UserManager userManager,
+ SignInManager signInManager)
+ {
+ _userManager = userManager;
+ _signInManager = signInManager;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public string Username { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [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 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 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();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs b/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs
new file mode 100644
index 0000000..fe6e9c1
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs
@@ -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
+{
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static class ManageNavPages
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string Index => "Index";
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string Email => "Email";
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string ChangePassword => "ChangePassword";
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string DownloadPersonalData => "DownloadPersonalData";
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string DeletePersonalData => "DeletePersonalData";
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string ExternalLogins => "ExternalLogins";
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string PersonalData => "PersonalData";
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string TwoFactorAuthentication => "TwoFactorAuthentication";
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email);
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData);
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData);
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string 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;
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml b/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml
new file mode 100644
index 0000000..a29c514
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml
@@ -0,0 +1,27 @@
+@page
+@model PersonalDataModel
+@{
+ ViewData["Title"] = "Personal Data";
+ ViewData["ActivePage"] = ManageNavPages.PersonalData;
+}
+
+
@ViewData["Title"]
+
+
+
+
Your account contains personal data that you have given us. This page allows you to download or delete that data.
+
+ Deleting this data will permanently remove your account, and this cannot be recovered.
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs b/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs
new file mode 100644
index 0000000..f02b2ac
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs
@@ -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 _userManager;
+ private readonly ILogger _logger;
+
+ public PersonalDataModel(
+ UserManager userManager,
+ ILogger logger)
+ {
+ _userManager = userManager;
+ _logger = logger;
+ }
+
+ public async Task OnGet()
+ {
+ var user = await _userManager.GetUserAsync(User);
+ if (user == null)
+ {
+ return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+ }
+
+ return Page();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml b/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml
new file mode 100644
index 0000000..97139e2
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml
@@ -0,0 +1,24 @@
+@page
+@model ResetAuthenticatorModel
+@{
+ ViewData["Title"] = "Reset authenticator key";
+ ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
+}
+
+
+
@ViewData["Title"]
+
+
+
+ If you reset your authenticator key your authenticator app will not work until you reconfigure it.
+
+
+ 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.
+
+
+
+
+
diff --git a/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs b/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs
new file mode 100644
index 0000000..68a61cc
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs
@@ -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 _userManager;
+ private readonly SignInManager _signInManager;
+ private readonly ILogger _logger;
+
+ public ResetAuthenticatorModel(
+ UserManager userManager,
+ SignInManager signInManager,
+ ILogger logger)
+ {
+ _userManager = userManager;
+ _signInManager = signInManager;
+ _logger = logger;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+
+ public async Task 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 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");
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml b/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml
new file mode 100644
index 0000000..5a7aca6
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml
@@ -0,0 +1,35 @@
+@page
+@model SetPasswordModel
+@{
+ ViewData["Title"] = "Set password";
+ ViewData["ActivePage"] = ManageNavPages.ChangePassword;
+}
+
+
Set your password
+
+
+ You do not have a local username/password for this site. Add a local
+ account so you can log in without an external login.
+
+
+
+
+
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs b/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs
new file mode 100644
index 0000000..1f2872c
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs
@@ -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 _userManager;
+ private readonly SignInManager _signInManager;
+
+ public SetPasswordModel(
+ UserManager userManager,
+ SignInManager signInManager)
+ {
+ _userManager = userManager;
+ _signInManager = signInManager;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [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; }
+
+ ///
+ /// 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.
+ ///
+ [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 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 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();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml b/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml
new file mode 100644
index 0000000..b61feb8
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml
@@ -0,0 +1,25 @@
+@page
+@model ShowRecoveryCodesModel
+@{
+ ViewData["Title"] = "Recovery codes";
+ ViewData["ActivePage"] = "TwoFactorAuthentication";
+}
+
+
+
@ViewData["Title"]
+
+
+ Put these codes in a safe place.
+
+
+ If you lose your device and don't have the recovery codes you will lose access to your account.
+
diff --git a/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs b/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs
new file mode 100644
index 0000000..1d2f013
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs
@@ -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
+{
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class ShowRecoveryCodesModel : PageModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string[] RecoveryCodes { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public IActionResult OnGet()
+ {
+ if (RecoveryCodes == null || RecoveryCodes.Length == 0)
+ {
+ return RedirectToPage("./TwoFactorAuthentication");
+ }
+
+ return Page();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml b/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml
new file mode 100644
index 0000000..37d22b7
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml
@@ -0,0 +1,71 @@
+@page
+@using Microsoft.AspNetCore.Http.Features
+@model TwoFactorAuthenticationModel
+@{
+ ViewData["Title"] = "Two-factor authentication (2FA)";
+ ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
+}
+
+
+
+ Privacy and cookie policy have not been accepted.
+
You must accept the policy before you can enable two factor authentication.
+
+ }
+}
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs b/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs
new file mode 100644
index 0000000..fe85eb8
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs
@@ -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 _userManager;
+ private readonly SignInManager _signInManager;
+ private readonly ILogger _logger;
+
+ public TwoFactorAuthenticationModel(
+ UserManager userManager, SignInManager signInManager, ILogger logger)
+ {
+ _userManager = userManager;
+ _signInManager = signInManager;
+ _logger = logger;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public bool HasAuthenticator { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public int RecoveryCodesLeft { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public bool Is2faEnabled { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public bool IsMachineRemembered { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [TempData]
+ public string StatusMessage { get; set; }
+
+ public async Task 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 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();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/Manage/_Layout.cshtml b/Areas/Identity/Pages/Account/Manage/_Layout.cshtml
new file mode 100644
index 0000000..d97cd4e
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Manage/_Layout.cshtml
@@ -0,0 +1,29 @@
+@{
+ if (ViewData.TryGetValue("ParentLayout", out var parentLayout) && parentLayout != null)
+ {
+ Layout = parentLayout.ToString();
+ }
+ else
+ {
+ Layout = "/Areas/Identity/Pages/_Layout.cshtml";
+ }
+}
+
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/Register.cshtml.cs b/Areas/Identity/Pages/Account/Register.cshtml.cs
new file mode 100644
index 0000000..b38434a
--- /dev/null
+++ b/Areas/Identity/Pages/Account/Register.cshtml.cs
@@ -0,0 +1,180 @@
+// 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.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.UI.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Logging;
+
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ public class RegisterModel : PageModel
+ {
+ private readonly SignInManager _signInManager;
+ private readonly UserManager _userManager;
+ private readonly IUserStore _userStore;
+ private readonly IUserEmailStore _emailStore;
+ private readonly ILogger _logger;
+ private readonly IEmailSender _emailSender;
+
+ public RegisterModel(
+ UserManager userManager,
+ IUserStore userStore,
+ SignInManager signInManager,
+ ILogger logger,
+ IEmailSender emailSender)
+ {
+ _userManager = userManager;
+ _userStore = userStore;
+ _emailStore = GetEmailStore();
+ _signInManager = signInManager;
+ _logger = logger;
+ _emailSender = emailSender;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public string ReturnUrl { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public IList ExternalLogins { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [EmailAddress]
+ [Display(Name = "Email")]
+ public string Email { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+ [DataType(DataType.Password)]
+ [Display(Name = "Password")]
+ public string Password { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [DataType(DataType.Password)]
+ [Display(Name = "Confirm password")]
+ [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
+ public string ConfirmPassword { get; set; }
+ }
+
+
+ public async Task OnGetAsync(string returnUrl = null)
+ {
+ ReturnUrl = returnUrl;
+ ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
+ }
+
+ public async Task OnPostAsync(string returnUrl = null)
+ {
+ returnUrl ??= Url.Content("~/");
+ ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
+ if (ModelState.IsValid)
+ {
+ var user = CreateUser();
+
+ await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
+ await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
+ var result = await _userManager.CreateAsync(user, Input.Password);
+
+ if (result.Succeeded)
+ {
+ _logger.LogInformation("User created a new account with password.");
+
+ var userId = await _userManager.GetUserIdAsync(user);
+ var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
+ code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+ var callbackUrl = Url.Page(
+ "/Account/ConfirmEmail",
+ pageHandler: null,
+ values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
+ protocol: Request.Scheme);
+
+ await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
+ $"Please confirm your account by clicking here.");
+
+ if (_userManager.Options.SignIn.RequireConfirmedAccount)
+ {
+ return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
+ }
+ else
+ {
+ await _signInManager.SignInAsync(user, isPersistent: false);
+ return LocalRedirect(returnUrl);
+ }
+ }
+ foreach (var error in result.Errors)
+ {
+ ModelState.AddModelError(string.Empty, error.Description);
+ }
+ }
+
+ // If we got this far, something failed, redisplay form
+ return Page();
+ }
+
+ private IdentityUser CreateUser()
+ {
+ try
+ {
+ return Activator.CreateInstance();
+ }
+ catch
+ {
+ throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " +
+ $"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor, or alternatively " +
+ $"override the register page in /Areas/Identity/Pages/Account/Register.cshtml");
+ }
+ }
+
+ private IUserEmailStore GetEmailStore()
+ {
+ if (!_userManager.SupportsUserEmail)
+ {
+ throw new NotSupportedException("The default UI requires a user store with email support.");
+ }
+ return (IUserEmailStore)_userStore;
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml b/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml
new file mode 100644
index 0000000..ce64a5a
--- /dev/null
+++ b/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml
@@ -0,0 +1,23 @@
+@page
+@model RegisterConfirmationModel
+@{
+ ViewData["Title"] = "Register confirmation";
+}
+
+
@ViewData["Title"]
+@{
+ if (@Model.DisplayConfirmAccountLink)
+ {
+
+ This app does not currently have a real email sender registered, see these docs for how to configure a real email sender.
+ Normally this would be emailed: Click here to confirm your account
+
+ }
+ else
+ {
+
+ Please check your email to confirm your account.
+
+ }
+}
+
diff --git a/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs b/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs
new file mode 100644
index 0000000..fef5df2
--- /dev/null
+++ b/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs
@@ -0,0 +1,79 @@
+// 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.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+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
+{
+ [AllowAnonymous]
+ public class RegisterConfirmationModel : PageModel
+ {
+ private readonly UserManager _userManager;
+ private readonly IEmailSender _sender;
+
+ public RegisterConfirmationModel(UserManager userManager, IEmailSender sender)
+ {
+ _userManager = userManager;
+ _sender = sender;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public string Email { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public bool DisplayConfirmAccountLink { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public string EmailConfirmationUrl { get; set; }
+
+ public async Task OnGetAsync(string email, string returnUrl = null)
+ {
+ if (email == null)
+ {
+ return RedirectToPage("/Index");
+ }
+ returnUrl = returnUrl ?? Url.Content("~/");
+
+ var user = await _userManager.FindByEmailAsync(email);
+ if (user == null)
+ {
+ return NotFound($"Unable to load user with email '{email}'.");
+ }
+
+ Email = email;
+ // Once you add a real email sender, you should remove this code that lets you confirm the account
+ DisplayConfirmAccountLink = true;
+ if (DisplayConfirmAccountLink)
+ {
+ var userId = await _userManager.GetUserIdAsync(user);
+ var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
+ code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+ EmailConfirmationUrl = Url.Page(
+ "/Account/ConfirmEmail",
+ pageHandler: null,
+ values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
+ protocol: Request.Scheme);
+ }
+
+ return Page();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml b/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml
new file mode 100644
index 0000000..021fffa
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml
@@ -0,0 +1,26 @@
+@page
+@model ResendEmailConfirmationModel
+@{
+ ViewData["Title"] = "Resend email confirmation";
+}
+
+
@ViewData["Title"]
+
Enter your email.
+
+
+
+
+
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml.cs b/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml.cs
new file mode 100644
index 0000000..cd5742a
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml.cs
@@ -0,0 +1,88 @@
+// 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.Authorization;
+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
+{
+ [AllowAnonymous]
+ public class ResendEmailConfirmationModel : PageModel
+ {
+ private readonly UserManager _userManager;
+ private readonly IEmailSender _emailSender;
+
+ public ResendEmailConfirmationModel(UserManager userManager, IEmailSender emailSender)
+ {
+ _userManager = userManager;
+ _emailSender = emailSender;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [EmailAddress]
+ public string Email { get; set; }
+ }
+
+ public void OnGet()
+ {
+ }
+
+ public async Task OnPostAsync()
+ {
+ if (!ModelState.IsValid)
+ {
+ return Page();
+ }
+
+ var user = await _userManager.FindByEmailAsync(Input.Email);
+ if (user == null)
+ {
+ ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email.");
+ return Page();
+ }
+
+ var userId = await _userManager.GetUserIdAsync(user);
+ var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
+ code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+ var callbackUrl = Url.Page(
+ "/Account/ConfirmEmail",
+ pageHandler: null,
+ values: new { userId = userId, code = code },
+ protocol: Request.Scheme);
+ await _emailSender.SendEmailAsync(
+ Input.Email,
+ "Confirm your email",
+ $"Please confirm your account by clicking here.");
+
+ ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email.");
+ return Page();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/ResetPassword.cshtml b/Areas/Identity/Pages/Account/ResetPassword.cshtml
new file mode 100644
index 0000000..3d4da7d
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ResetPassword.cshtml
@@ -0,0 +1,37 @@
+@page
+@model ResetPasswordModel
+@{
+ ViewData["Title"] = "Reset password";
+}
+
+
@ViewData["Title"]
+
Reset your password.
+
+
+
+
+
+
+
+@section Scripts {
+
+}
diff --git a/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs b/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs
new file mode 100644
index 0000000..51946a2
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs
@@ -0,0 +1,117 @@
+// 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.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ public class ResetPasswordModel : PageModel
+ {
+ private readonly UserManager _userManager;
+
+ public ResetPasswordModel(UserManager userManager)
+ {
+ _userManager = userManager;
+ }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [EmailAddress]
+ public string Email { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+ [DataType(DataType.Password)]
+ public string Password { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [DataType(DataType.Password)]
+ [Display(Name = "Confirm password")]
+ [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
+ public string ConfirmPassword { get; set; }
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ public string Code { get; set; }
+
+ }
+
+ public IActionResult OnGet(string code = null)
+ {
+ if (code == null)
+ {
+ return BadRequest("A code must be supplied for password reset.");
+ }
+ else
+ {
+ Input = new InputModel
+ {
+ Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code))
+ };
+ return Page();
+ }
+ }
+
+ public async Task OnPostAsync()
+ {
+ if (!ModelState.IsValid)
+ {
+ return Page();
+ }
+
+ var user = await _userManager.FindByEmailAsync(Input.Email);
+ if (user == null)
+ {
+ // Don't reveal that the user does not exist
+ return RedirectToPage("./ResetPasswordConfirmation");
+ }
+
+ var result = await _userManager.ResetPasswordAsync(user, Input.Code, Input.Password);
+ if (result.Succeeded)
+ {
+ return RedirectToPage("./ResetPasswordConfirmation");
+ }
+
+ foreach (var error in result.Errors)
+ {
+ ModelState.AddModelError(string.Empty, error.Description);
+ }
+ return Page();
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml b/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml
new file mode 100644
index 0000000..394c8db
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml
@@ -0,0 +1,10 @@
+@page
+@model ResetPasswordConfirmationModel
+@{
+ ViewData["Title"] = "Reset password confirmation";
+}
+
+
diff --git a/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml.cs b/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml.cs
new file mode 100644
index 0000000..be98533
--- /dev/null
+++ b/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml.cs
@@ -0,0 +1,25 @@
+// 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.Authorization;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace turf_tasker.Areas.Identity.Pages.Account
+{
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [AllowAnonymous]
+ public class ResetPasswordConfirmationModel : PageModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public void OnGet()
+ {
+ }
+ }
+}
diff --git a/Areas/Identity/Pages/Account/_StatusMessage.cshtml b/Areas/Identity/Pages/Account/_StatusMessage.cshtml
new file mode 100644
index 0000000..5051306
--- /dev/null
+++ b/Areas/Identity/Pages/Account/_StatusMessage.cshtml
@@ -0,0 +1,10 @@
+@model string
+
+@if (!String.IsNullOrEmpty(Model))
+{
+ var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
+