Compare commits
	
		
			2 commits
		
	
	
		
			main
			...
			feat/add-a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 2274abd3fb | ||
|   | b24beb3154 | 
					 78 changed files with 4829 additions and 12 deletions
				
			
		
							
								
								
									
										10
									
								
								Areas/Identity/Pages/Account/AccessDenied.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								Areas/Identity/Pages/Account/AccessDenied.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | @page | ||||||
|  | @model AccessDeniedModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Access denied"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <header> | ||||||
|  |     <h1 class="text-danger">@ViewData["Title"]</h1> | ||||||
|  |     <p class="text-danger">You do not have access to this resource.</p> | ||||||
|  | </header> | ||||||
							
								
								
									
										23
									
								
								Areas/Identity/Pages/Account/AccessDenied.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Areas/Identity/Pages/Account/AccessDenied.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |     ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |     /// </summary> | ||||||
|  |     public class AccessDeniedModel : PageModel | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public void OnGet() | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								Areas/Identity/Pages/Account/ConfirmEmail.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Areas/Identity/Pages/Account/ConfirmEmail.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | @page | ||||||
|  | @model ConfirmEmailModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Confirm email"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | <partial name="_StatusMessage" model="Model.StatusMessage" /> | ||||||
							
								
								
									
										51
									
								
								Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<IdentityUser> _userManager; | ||||||
|  | 
 | ||||||
|  |         public ConfirmEmailModel(UserManager<IdentityUser> userManager) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  |         public async Task<IActionResult> OnGetAsync(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(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | @page | ||||||
|  | @model ConfirmEmailChangeModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Confirm email change"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | <partial name="_StatusMessage" model="Model.StatusMessage" /> | ||||||
							
								
								
									
										69
									
								
								Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.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<IdentityUser> _userManager; | ||||||
|  |         private readonly SignInManager<IdentityUser> _signInManager; | ||||||
|  | 
 | ||||||
|  |         public ConfirmEmailChangeModel(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> 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(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								Areas/Identity/Pages/Account/ExternalLogin.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Areas/Identity/Pages/Account/ExternalLogin.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | ||||||
|  | @page | ||||||
|  | @model ExternalLoginModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Register"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | <h2 id="external-login-title">Associate your @Model.ProviderDisplayName account.</h2> | ||||||
|  | <hr /> | ||||||
|  | 
 | ||||||
|  | <p id="external-login-description" class="text-info"> | ||||||
|  |     You've successfully authenticated with <strong>@Model.ProviderDisplayName</strong>. | ||||||
|  |     Please enter an email address for this site below and click the Register button to finish | ||||||
|  |     logging in. | ||||||
|  | </p> | ||||||
|  | 
 | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-4"> | ||||||
|  |         <form asp-page-handler="Confirmation" asp-route-returnUrl="@Model.ReturnUrl" method="post"> | ||||||
|  |             <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.Email" class="form-control" autocomplete="email" placeholder="Please enter your email."/> | ||||||
|  |                 <label asp-for="Input.Email" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.Email" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <button type="submit" class="w-100 btn btn-lg btn-primary">Register</button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										223
									
								
								Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<IdentityUser> _signInManager; | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly IUserStore<IdentityUser> _userStore; | ||||||
|  |         private readonly IUserEmailStore<IdentityUser> _emailStore; | ||||||
|  |         private readonly IEmailSender _emailSender; | ||||||
|  |         private readonly ILogger<ExternalLoginModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public ExternalLoginModel( | ||||||
|  |             SignInManager<IdentityUser> signInManager, | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             IUserStore<IdentityUser> userStore, | ||||||
|  |             ILogger<ExternalLoginModel> logger, | ||||||
|  |             IEmailSender emailSender) | ||||||
|  |         { | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _userStore = userStore; | ||||||
|  |             _emailStore = GetEmailStore(); | ||||||
|  |             _logger = logger; | ||||||
|  |             _emailSender = emailSender; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public string ProviderDisplayName { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public string ReturnUrl { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string ErrorMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [EmailAddress] | ||||||
|  |             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<IActionResult> 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<IActionResult> 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 <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>."); | ||||||
|  | 
 | ||||||
|  |                         // 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<IdentityUser>(); | ||||||
|  |             } | ||||||
|  |             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<IdentityUser> GetEmailStore() | ||||||
|  |         { | ||||||
|  |             if (!_userManager.SupportsUserEmail) | ||||||
|  |             { | ||||||
|  |                 throw new NotSupportedException("The default UI requires a user store with email support."); | ||||||
|  |             } | ||||||
|  |             return (IUserEmailStore<IdentityUser>)_userStore; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								Areas/Identity/Pages/Account/ForgotPassword.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Areas/Identity/Pages/Account/ForgotPassword.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | ||||||
|  | @page | ||||||
|  | @model ForgotPasswordModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Forgot your password?"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | <h2>Enter your email.</h2> | ||||||
|  | <hr /> | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-4"> | ||||||
|  |         <form method="post"> | ||||||
|  |             <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" /> | ||||||
|  |                 <label asp-for="Input.Email" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.Email" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <button type="submit" class="w-100 btn btn-lg btn-primary">Reset Password</button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										84
									
								
								Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<IdentityUser> _userManager; | ||||||
|  |         private readonly IEmailSender _emailSender; | ||||||
|  | 
 | ||||||
|  |         public ForgotPasswordModel(UserManager<IdentityUser> userManager, IEmailSender emailSender) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _emailSender = emailSender; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [EmailAddress] | ||||||
|  |             public string Email { get; set; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> 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 <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>."); | ||||||
|  | 
 | ||||||
|  |                 return RedirectToPage("./ForgotPasswordConfirmation"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,10 @@ | ||||||
|  | @page | ||||||
|  | @model ForgotPasswordConfirmation | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Forgot password confirmation"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | <p> | ||||||
|  |     Please check your email to reset your password. | ||||||
|  | </p> | ||||||
|  | @ -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 | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |     ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |     /// </summary> | ||||||
|  |     [AllowAnonymous] | ||||||
|  |     public class ForgotPasswordConfirmation : PageModel | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public void OnGet() | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								Areas/Identity/Pages/Account/Lockout.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								Areas/Identity/Pages/Account/Lockout.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | @page | ||||||
|  | @model LockoutModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Locked out"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <header> | ||||||
|  |     <h1 class="text-danger">@ViewData["Title"]</h1> | ||||||
|  |     <p class="text-danger">This account has been locked out, please try again later.</p> | ||||||
|  | </header> | ||||||
							
								
								
									
										25
									
								
								Areas/Identity/Pages/Account/Lockout.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Areas/Identity/Pages/Account/Lockout.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |     ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |     /// </summary> | ||||||
|  |     [AllowAnonymous] | ||||||
|  |     public class LockoutModel : PageModel | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public void OnGet() | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										83
									
								
								Areas/Identity/Pages/Account/Login.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								Areas/Identity/Pages/Account/Login.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,83 @@ | ||||||
|  | @page | ||||||
|  | @model LoginModel | ||||||
|  | 
 | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Log in"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-4"> | ||||||
|  |         <section> | ||||||
|  |             <form id="account" method="post"> | ||||||
|  |                 <h2>Use a local account to log in.</h2> | ||||||
|  |                 <hr /> | ||||||
|  |                 <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> | ||||||
|  |                 <div class="form-floating mb-3"> | ||||||
|  |                     <input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" /> | ||||||
|  |                     <label asp-for="Input.Email" class="form-label">Email</label> | ||||||
|  |                     <span asp-validation-for="Input.Email" class="text-danger"></span> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="form-floating mb-3"> | ||||||
|  |                     <input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="password" /> | ||||||
|  |                     <label asp-for="Input.Password" class="form-label">Password</label> | ||||||
|  |                     <span asp-validation-for="Input.Password" class="text-danger"></span> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="checkbox mb-3"> | ||||||
|  |                     <label asp-for="Input.RememberMe" class="form-label"> | ||||||
|  |                         <input class="form-check-input" asp-for="Input.RememberMe" /> | ||||||
|  |                         @Html.DisplayNameFor(m => m.Input.RememberMe) | ||||||
|  |                     </label> | ||||||
|  |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <button id="login-submit" type="submit" class="w-100 btn btn-lg btn-primary">Log in</button> | ||||||
|  |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <p> | ||||||
|  |                         <a id="forgot-password" asp-page="./ForgotPassword">Forgot your password?</a> | ||||||
|  |                     </p> | ||||||
|  |                     <p> | ||||||
|  |                         <a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a> | ||||||
|  |                     </p> | ||||||
|  |                     <p> | ||||||
|  |                         <a id="resend-confirmation" asp-page="./ResendEmailConfirmation">Resend email confirmation</a> | ||||||
|  |                     </p> | ||||||
|  |                 </div> | ||||||
|  |             </form> | ||||||
|  |         </section> | ||||||
|  |     </div> | ||||||
|  |     <div class="col-md-6 col-md-offset-2"> | ||||||
|  |         <section> | ||||||
|  |             <h3>Use another service to log in.</h3> | ||||||
|  |             <hr /> | ||||||
|  |             @{ | ||||||
|  |                 if ((Model.ExternalLogins?.Count ?? 0) == 0) | ||||||
|  |                 { | ||||||
|  |                     <div> | ||||||
|  |                         <p> | ||||||
|  |                             There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article | ||||||
|  |                             about setting up this ASP.NET application to support logging in via external services</a>. | ||||||
|  |                         </p> | ||||||
|  |                     </div> | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal"> | ||||||
|  |                         <div> | ||||||
|  |                             <p> | ||||||
|  |                                 @foreach (var provider in Model.ExternalLogins!) | ||||||
|  |                                 { | ||||||
|  |                                     <button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> | ||||||
|  |                                 } | ||||||
|  |                             </p> | ||||||
|  |                         </div> | ||||||
|  |                     </form> | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         </section> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										140
									
								
								Areas/Identity/Pages/Account/Login.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								Areas/Identity/Pages/Account/Login.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<IdentityUser> _signInManager; | ||||||
|  |         private readonly ILogger<LoginModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public LoginModel(SignInManager<IdentityUser> signInManager, ILogger<LoginModel> logger) | ||||||
|  |         { | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public IList<AuthenticationScheme> ExternalLogins { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public string ReturnUrl { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string ErrorMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [EmailAddress] | ||||||
|  |             public string Email { get; set; } | ||||||
|  | 
 | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [DataType(DataType.Password)] | ||||||
|  |             public string Password { get; set; } | ||||||
|  | 
 | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [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<IActionResult> 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(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								Areas/Identity/Pages/Account/LoginWith2fa.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								Areas/Identity/Pages/Account/LoginWith2fa.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | ||||||
|  | @page | ||||||
|  | @model LoginWith2faModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Two-factor authentication"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | <hr /> | ||||||
|  | <p>Your login is protected with an authenticator app. Enter your authenticator code below.</p> | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-4"> | ||||||
|  |         <form method="post" asp-route-returnUrl="@Model.ReturnUrl"> | ||||||
|  |             <input asp-for="RememberMe" type="hidden" /> | ||||||
|  |             <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.TwoFactorCode" class="form-control" autocomplete="off" /> | ||||||
|  |                 <label asp-for="Input.TwoFactorCode" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.TwoFactorCode" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="checkbox mb-3"> | ||||||
|  |                 <label asp-for="Input.RememberMachine" class="form-label"> | ||||||
|  |                     <input asp-for="Input.RememberMachine" /> | ||||||
|  |                     @Html.DisplayNameFor(m => m.Input.RememberMachine) | ||||||
|  |                 </label> | ||||||
|  |             </div> | ||||||
|  |             <div> | ||||||
|  |                 <button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button> | ||||||
|  |             </div> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | <p> | ||||||
|  |     Don't have access to your authenticator device? You can | ||||||
|  |     <a id="recovery-code-login" asp-page="./LoginWithRecoveryCode" asp-route-returnUrl="@Model.ReturnUrl">log in with a recovery code</a>. | ||||||
|  | </p> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										131
									
								
								Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<IdentityUser> _signInManager; | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly ILogger<LoginWith2faModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public LoginWith2faModel( | ||||||
|  |             SignInManager<IdentityUser> signInManager, | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             ILogger<LoginWith2faModel> logger) | ||||||
|  |         { | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool RememberMe { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public string ReturnUrl { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] | ||||||
|  |             [DataType(DataType.Text)] | ||||||
|  |             [Display(Name = "Authenticator code")] | ||||||
|  |             public string TwoFactorCode { get; set; } | ||||||
|  | 
 | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Display(Name = "Remember this machine")] | ||||||
|  |             public bool RememberMachine { get; set; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> 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<IActionResult> 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(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | @page | ||||||
|  | @model LoginWithRecoveryCodeModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Recovery code verification"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | <hr /> | ||||||
|  | <p> | ||||||
|  |     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. | ||||||
|  | </p> | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-4"> | ||||||
|  |         <form method="post"> | ||||||
|  |             <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.RecoveryCode" class="form-control" autocomplete="off" placeholder="RecoveryCode" /> | ||||||
|  |                 <label asp-for="Input.RecoveryCode" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.RecoveryCode" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										112
									
								
								Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<IdentityUser> _signInManager; | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly ILogger<LoginWithRecoveryCodeModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public LoginWithRecoveryCodeModel( | ||||||
|  |             SignInManager<IdentityUser> signInManager, | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             ILogger<LoginWithRecoveryCodeModel> logger) | ||||||
|  |         { | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public string ReturnUrl { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [BindProperty] | ||||||
|  |             [Required] | ||||||
|  |             [DataType(DataType.Text)] | ||||||
|  |             [Display(Name = "Recovery Code")] | ||||||
|  |             public string RecoveryCode { get; set; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> 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<IActionResult> 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(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								Areas/Identity/Pages/Account/Logout.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Areas/Identity/Pages/Account/Logout.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | @page | ||||||
|  | @model LogoutModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Log out"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <header> | ||||||
|  |     <h1>@ViewData["Title"]</h1> | ||||||
|  |     @{ | ||||||
|  |         if (User.Identity?.IsAuthenticated ?? false) | ||||||
|  |         { | ||||||
|  |             <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/", new { area = "" })" method="post"> | ||||||
|  |                 <button type="submit" class="nav-link btn btn-link text-dark">Click here to Logout</button> | ||||||
|  |             </form> | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             <p>You have successfully logged out of the application.</p> | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | </header> | ||||||
							
								
								
									
										42
									
								
								Areas/Identity/Pages/Account/Logout.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Areas/Identity/Pages/Account/Logout.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<IdentityUser> _signInManager; | ||||||
|  |         private readonly ILogger<LogoutModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger) | ||||||
|  |         { | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> 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(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | @page | ||||||
|  | @model ChangePasswordModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Change password"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.ChangePassword; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h3>@ViewData["Title"]</h3> | ||||||
|  | <partial name="_StatusMessage" for="StatusMessage" /> | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-6"> | ||||||
|  |         <form id="change-password-form" method="post"> | ||||||
|  |             <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.OldPassword" class="form-control" autocomplete="current-password" aria-required="true" placeholder="Please enter your old password." /> | ||||||
|  |                 <label asp-for="Input.OldPassword" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.OldPassword" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.NewPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please enter your new password." /> | ||||||
|  |                 <label asp-for="Input.NewPassword" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.NewPassword" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please confirm your new password."/> | ||||||
|  |                 <label asp-for="Input.ConfirmPassword" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <button type="submit" class="w-100 btn btn-lg btn-primary">Update password</button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										127
									
								
								Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,127 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class ChangePasswordModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly SignInManager<IdentityUser> _signInManager; | ||||||
|  |         private readonly ILogger<ChangePasswordModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public ChangePasswordModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             SignInManager<IdentityUser> signInManager, | ||||||
|  |             ILogger<ChangePasswordModel> logger) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [DataType(DataType.Password)] | ||||||
|  |             [Display(Name = "Current password")] | ||||||
|  |             public string OldPassword { get; set; } | ||||||
|  | 
 | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] | ||||||
|  |             [DataType(DataType.Password)] | ||||||
|  |             [Display(Name = "New password")] | ||||||
|  |             public string NewPassword { get; set; } | ||||||
|  | 
 | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [DataType(DataType.Password)] | ||||||
|  |             [Display(Name = "Confirm new password")] | ||||||
|  |             [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] | ||||||
|  |             public string ConfirmPassword { get; set; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGetAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var hasPassword = await _userManager.HasPasswordAsync(user); | ||||||
|  |             if (!hasPassword) | ||||||
|  |             { | ||||||
|  |                 return RedirectToPage("./SetPassword"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostAsync() | ||||||
|  |         { | ||||||
|  |             if (!ModelState.IsValid) | ||||||
|  |             { | ||||||
|  |                 return Page(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword); | ||||||
|  |             if (!changePasswordResult.Succeeded) | ||||||
|  |             { | ||||||
|  |                 foreach (var error in changePasswordResult.Errors) | ||||||
|  |                 { | ||||||
|  |                     ModelState.AddModelError(string.Empty, error.Description); | ||||||
|  |                 } | ||||||
|  |                 return Page(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             await _signInManager.RefreshSignInAsync(user); | ||||||
|  |             _logger.LogInformation("User changed their password successfully."); | ||||||
|  |             StatusMessage = "Your password has been changed."; | ||||||
|  | 
 | ||||||
|  |             return RedirectToPage(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,33 @@ | ||||||
|  | @page | ||||||
|  | @model DeletePersonalDataModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Delete Personal Data"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.PersonalData; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h3>@ViewData["Title"]</h3> | ||||||
|  | 
 | ||||||
|  | <div class="alert alert-warning" role="alert"> | ||||||
|  |     <p> | ||||||
|  |         <strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong> | ||||||
|  |     </p> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <div> | ||||||
|  |     <form id="delete-user" method="post"> | ||||||
|  |         <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> | ||||||
|  |         @if (Model.RequirePassword) | ||||||
|  |         { | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="Please enter your password." /> | ||||||
|  |                 <label asp-for="Input.Password" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.Password" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |         } | ||||||
|  |         <button class="w-100 btn btn-lg btn-danger" type="submit">Delete data and close my account</button> | ||||||
|  |     </form> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										103
									
								
								Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,103 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class DeletePersonalDataModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly SignInManager<IdentityUser> _signInManager; | ||||||
|  |         private readonly ILogger<DeletePersonalDataModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public DeletePersonalDataModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             SignInManager<IdentityUser> signInManager, | ||||||
|  |             ILogger<DeletePersonalDataModel> logger) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [DataType(DataType.Password)] | ||||||
|  |             public string Password { get; set; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool RequirePassword { get; set; } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGet() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             RequirePassword = await _userManager.HasPasswordAsync(user); | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             RequirePassword = await _userManager.HasPasswordAsync(user); | ||||||
|  |             if (RequirePassword) | ||||||
|  |             { | ||||||
|  |                 if (!await _userManager.CheckPasswordAsync(user, Input.Password)) | ||||||
|  |                 { | ||||||
|  |                     ModelState.AddModelError(string.Empty, "Incorrect password."); | ||||||
|  |                     return Page(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var result = await _userManager.DeleteAsync(user); | ||||||
|  |             var userId = await _userManager.GetUserIdAsync(user); | ||||||
|  |             if (!result.Succeeded) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException($"Unexpected error occurred deleting user."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             await _signInManager.SignOutAsync(); | ||||||
|  | 
 | ||||||
|  |             _logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId); | ||||||
|  | 
 | ||||||
|  |             return Redirect("~/"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | ||||||
|  | @page | ||||||
|  | @model Disable2faModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Disable two-factor authentication (2FA)"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <partial name="_StatusMessage" for="StatusMessage" /> | ||||||
|  | <h3>@ViewData["Title"]</h3> | ||||||
|  | 
 | ||||||
|  | <div class="alert alert-warning" role="alert"> | ||||||
|  |     <p> | ||||||
|  |         <strong>This action only disables 2FA.</strong> | ||||||
|  |     </p> | ||||||
|  |     <p> | ||||||
|  |         Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key | ||||||
|  |         used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a> | ||||||
|  |     </p> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <div> | ||||||
|  |     <form method="post"> | ||||||
|  |         <button class="btn btn-danger" type="submit">Disable 2FA</button> | ||||||
|  |     </form> | ||||||
|  | </div> | ||||||
							
								
								
									
										69
									
								
								Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class Disable2faModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly ILogger<Disable2faModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public Disable2faModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             ILogger<Disable2faModel> logger) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGet() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!await _userManager.GetTwoFactorEnabledAsync(user)) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException($"Cannot disable 2FA for user as it's not currently enabled."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false); | ||||||
|  |             if (!disable2faResult.Succeeded) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException($"Unexpected error occurred disabling 2FA."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             _logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User)); | ||||||
|  |             StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app"; | ||||||
|  |             return RedirectToPage("./TwoFactorAuthentication"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | @page | ||||||
|  | @model DownloadPersonalDataModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Download Your Data"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.PersonalData; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h3>@ViewData["Title"]</h3> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
|  | @ -0,0 +1,67 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Text.Json; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class DownloadPersonalDataModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly ILogger<DownloadPersonalDataModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public DownloadPersonalDataModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             ILogger<DownloadPersonalDataModel> logger) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public IActionResult OnGet() | ||||||
|  |         { | ||||||
|  |             return NotFound(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             _logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User)); | ||||||
|  | 
 | ||||||
|  |             // Only include personal data for download | ||||||
|  |             var personalData = new Dictionary<string, string>(); | ||||||
|  |             var personalDataProps = typeof(IdentityUser).GetProperties().Where( | ||||||
|  |                             prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute))); | ||||||
|  |             foreach (var p in personalDataProps) | ||||||
|  |             { | ||||||
|  |                 personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var logins = await _userManager.GetLoginsAsync(user); | ||||||
|  |             foreach (var l in logins) | ||||||
|  |             { | ||||||
|  |                 personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             personalData.Add($"Authenticator Key", await _userManager.GetAuthenticatorKeyAsync(user)); | ||||||
|  | 
 | ||||||
|  |             Response.Headers.TryAdd("Content-Disposition", "attachment; filename=PersonalData.json"); | ||||||
|  |             return new FileContentResult(JsonSerializer.SerializeToUtf8Bytes(personalData), "application/json"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								Areas/Identity/Pages/Account/Manage/Email.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								Areas/Identity/Pages/Account/Manage/Email.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | ||||||
|  | @page | ||||||
|  | @model EmailModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Manage Email"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.Email; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h3>@ViewData["Title"]</h3> | ||||||
|  | <partial name="_StatusMessage" for="StatusMessage" /> | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-6"> | ||||||
|  |         <form id="email-form" method="post"> | ||||||
|  |             <div asp-validation-summary="All" class="text-danger" role="alert"></div> | ||||||
|  |             @if (Model.IsEmailConfirmed) | ||||||
|  |             { | ||||||
|  |                 <div class="form-floating mb-3 input-group"> | ||||||
|  |                     <input asp-for="Email" class="form-control" placeholder="Please enter your email." disabled /> | ||||||
|  |                         <div class="input-group-append"> | ||||||
|  |                             <span class="h-100 input-group-text text-success font-weight-bold">✓</span> | ||||||
|  |                         </div> | ||||||
|  |                     <label asp-for="Email" class="form-label"></label> | ||||||
|  |                 </div> | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 <div class="form-floating mb-3"> | ||||||
|  |                     <input asp-for="Email" class="form-control" placeholder="Please enter your email." disabled /> | ||||||
|  |                     <label asp-for="Email" class="form-label"></label> | ||||||
|  |                     <button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button> | ||||||
|  |                 </div> | ||||||
|  |             } | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.NewEmail" class="form-control" autocomplete="email" aria-required="true" placeholder="Please enter new email." /> | ||||||
|  |                 <label asp-for="Input.NewEmail" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.NewEmail" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <button id="change-email-button" type="submit" asp-page-handler="ChangeEmail" class="w-100 btn btn-lg btn-primary">Change email</button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										171
									
								
								Areas/Identity/Pages/Account/Manage/Email.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								Areas/Identity/Pages/Account/Manage/Email.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,171 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  | using System.Text; | ||||||
|  | using System.Text.Encodings.Web; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Identity.UI.Services; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | using Microsoft.AspNetCore.WebUtilities; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class EmailModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly SignInManager<IdentityUser> _signInManager; | ||||||
|  |         private readonly IEmailSender _emailSender; | ||||||
|  | 
 | ||||||
|  |         public EmailModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             SignInManager<IdentityUser> signInManager, | ||||||
|  |             IEmailSender emailSender) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |             _emailSender = emailSender; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public string Email { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool IsEmailConfirmed { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [EmailAddress] | ||||||
|  |             [Display(Name = "New email")] | ||||||
|  |             public string NewEmail { get; set; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private async Task LoadAsync(IdentityUser user) | ||||||
|  |         { | ||||||
|  |             var email = await _userManager.GetEmailAsync(user); | ||||||
|  |             Email = email; | ||||||
|  | 
 | ||||||
|  |             Input = new InputModel | ||||||
|  |             { | ||||||
|  |                 NewEmail = email, | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGetAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             await LoadAsync(user); | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostChangeEmailAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!ModelState.IsValid) | ||||||
|  |             { | ||||||
|  |                 await LoadAsync(user); | ||||||
|  |                 return Page(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var email = await _userManager.GetEmailAsync(user); | ||||||
|  |             if (Input.NewEmail != email) | ||||||
|  |             { | ||||||
|  |                 var userId = await _userManager.GetUserIdAsync(user); | ||||||
|  |                 var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail); | ||||||
|  |                 code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); | ||||||
|  |                 var callbackUrl = Url.Page( | ||||||
|  |                     "/Account/ConfirmEmailChange", | ||||||
|  |                     pageHandler: null, | ||||||
|  |                     values: new { area = "Identity", userId = userId, email = Input.NewEmail, code = code }, | ||||||
|  |                     protocol: Request.Scheme); | ||||||
|  |                 await _emailSender.SendEmailAsync( | ||||||
|  |                     Input.NewEmail, | ||||||
|  |                     "Confirm your email", | ||||||
|  |                     $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>."); | ||||||
|  | 
 | ||||||
|  |                 StatusMessage = "Confirmation link to change email sent. Please check your email."; | ||||||
|  |                 return RedirectToPage(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             StatusMessage = "Your email is unchanged."; | ||||||
|  |             return RedirectToPage(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostSendVerificationEmailAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!ModelState.IsValid) | ||||||
|  |             { | ||||||
|  |                 await LoadAsync(user); | ||||||
|  |                 return Page(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var userId = await _userManager.GetUserIdAsync(user); | ||||||
|  |             var email = await _userManager.GetEmailAsync(user); | ||||||
|  |             var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); | ||||||
|  |             code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); | ||||||
|  |             var callbackUrl = Url.Page( | ||||||
|  |                 "/Account/ConfirmEmail", | ||||||
|  |                 pageHandler: null, | ||||||
|  |                 values: new { area = "Identity", userId = userId, code = code }, | ||||||
|  |                 protocol: Request.Scheme); | ||||||
|  |             await _emailSender.SendEmailAsync( | ||||||
|  |                 email, | ||||||
|  |                 "Confirm your email", | ||||||
|  |                 $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>."); | ||||||
|  | 
 | ||||||
|  |             StatusMessage = "Verification email sent. Please check your email."; | ||||||
|  |             return RedirectToPage(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,53 @@ | ||||||
|  | @page | ||||||
|  | @model EnableAuthenticatorModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Configure authenticator app"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <partial name="_StatusMessage" for="StatusMessage" /> | ||||||
|  | <h3>@ViewData["Title"]</h3> | ||||||
|  | <div> | ||||||
|  |     <p>To use an authenticator app go through the following steps:</p> | ||||||
|  |     <ol class="list"> | ||||||
|  |         <li> | ||||||
|  |             <p> | ||||||
|  |                 Download a two-factor authenticator app like Microsoft Authenticator for | ||||||
|  |                 <a href="https://go.microsoft.com/fwlink/?Linkid=825072">Android</a> and | ||||||
|  |                 <a href="https://go.microsoft.com/fwlink/?Linkid=825073">iOS</a> or | ||||||
|  |                 Google Authenticator for | ||||||
|  |                 <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en">Android</a> and | ||||||
|  |                 <a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">iOS</a>. | ||||||
|  |             </p> | ||||||
|  |         </li> | ||||||
|  |         <li> | ||||||
|  |             <p>Scan the QR Code or enter this key <kbd>@Model.SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p> | ||||||
|  |             <div class="alert alert-info">Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable QR code generation</a>.</div> | ||||||
|  |             <div id="qrCode"></div> | ||||||
|  |             <div id="qrCodeData" data-url="@Model.AuthenticatorUri"></div> | ||||||
|  |         </li> | ||||||
|  |         <li> | ||||||
|  |             <p> | ||||||
|  |                 Once you have scanned the QR code or input the key above, your two factor authentication app will provide you | ||||||
|  |                 with a unique code. Enter the code in the confirmation box below. | ||||||
|  |             </p> | ||||||
|  |             <div class="row"> | ||||||
|  |                 <div class="col-md-6"> | ||||||
|  |                     <form id="send-code" method="post"> | ||||||
|  |                         <div class="form-floating mb-3"> | ||||||
|  |                             <input asp-for="Input.Code" class="form-control" autocomplete="off" placeholder="Please enter the code."/> | ||||||
|  |                             <label asp-for="Input.Code" class="control-label form-label">Verification Code</label> | ||||||
|  |                             <span asp-validation-for="Input.Code" class="text-danger"></span> | ||||||
|  |                         </div> | ||||||
|  |                         <button type="submit" class="w-100 btn btn-lg btn-primary">Verify</button> | ||||||
|  |                         <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> | ||||||
|  |                     </form> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </li> | ||||||
|  |     </ol> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
|  | @ -0,0 +1,188 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  | using System.Globalization; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Text.Encodings.Web; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class EnableAuthenticatorModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly ILogger<EnableAuthenticatorModel> _logger; | ||||||
|  |         private readonly UrlEncoder _urlEncoder; | ||||||
|  | 
 | ||||||
|  |         private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; | ||||||
|  | 
 | ||||||
|  |         public EnableAuthenticatorModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             ILogger<EnableAuthenticatorModel> logger, | ||||||
|  |             UrlEncoder urlEncoder) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _logger = logger; | ||||||
|  |             _urlEncoder = urlEncoder; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public string SharedKey { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public string AuthenticatorUri { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string[] RecoveryCodes { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] | ||||||
|  |             [DataType(DataType.Text)] | ||||||
|  |             [Display(Name = "Verification Code")] | ||||||
|  |             public string Code { get; set; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGetAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             await LoadSharedKeyAndQrCodeUriAsync(user); | ||||||
|  | 
 | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!ModelState.IsValid) | ||||||
|  |             { | ||||||
|  |                 await LoadSharedKeyAndQrCodeUriAsync(user); | ||||||
|  |                 return Page(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Strip spaces and hyphens | ||||||
|  |             var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty); | ||||||
|  | 
 | ||||||
|  |             var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync( | ||||||
|  |                 user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); | ||||||
|  | 
 | ||||||
|  |             if (!is2faTokenValid) | ||||||
|  |             { | ||||||
|  |                 ModelState.AddModelError("Input.Code", "Verification code is invalid."); | ||||||
|  |                 await LoadSharedKeyAndQrCodeUriAsync(user); | ||||||
|  |                 return Page(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             await _userManager.SetTwoFactorEnabledAsync(user, true); | ||||||
|  |             var userId = await _userManager.GetUserIdAsync(user); | ||||||
|  |             _logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId); | ||||||
|  | 
 | ||||||
|  |             StatusMessage = "Your authenticator app has been verified."; | ||||||
|  | 
 | ||||||
|  |             if (await _userManager.CountRecoveryCodesAsync(user) == 0) | ||||||
|  |             { | ||||||
|  |                 var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); | ||||||
|  |                 RecoveryCodes = recoveryCodes.ToArray(); | ||||||
|  |                 return RedirectToPage("./ShowRecoveryCodes"); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 return RedirectToPage("./TwoFactorAuthentication"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private async Task LoadSharedKeyAndQrCodeUriAsync(IdentityUser user) | ||||||
|  |         { | ||||||
|  |             // Load the authenticator key & QR code URI to display on the form | ||||||
|  |             var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); | ||||||
|  |             if (string.IsNullOrEmpty(unformattedKey)) | ||||||
|  |             { | ||||||
|  |                 await _userManager.ResetAuthenticatorKeyAsync(user); | ||||||
|  |                 unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             SharedKey = FormatKey(unformattedKey); | ||||||
|  | 
 | ||||||
|  |             var email = await _userManager.GetEmailAsync(user); | ||||||
|  |             AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private string FormatKey(string unformattedKey) | ||||||
|  |         { | ||||||
|  |             var result = new StringBuilder(); | ||||||
|  |             int currentPosition = 0; | ||||||
|  |             while (currentPosition + 4 < unformattedKey.Length) | ||||||
|  |             { | ||||||
|  |                 result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' '); | ||||||
|  |                 currentPosition += 4; | ||||||
|  |             } | ||||||
|  |             if (currentPosition < unformattedKey.Length) | ||||||
|  |             { | ||||||
|  |                 result.Append(unformattedKey.AsSpan(currentPosition)); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return result.ToString().ToLowerInvariant(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private string GenerateQrCodeUri(string email, string unformattedKey) | ||||||
|  |         { | ||||||
|  |             return string.Format( | ||||||
|  |                 CultureInfo.InvariantCulture, | ||||||
|  |                 AuthenticatorUriFormat, | ||||||
|  |                 _urlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"), | ||||||
|  |                 _urlEncoder.Encode(email), | ||||||
|  |                 unformattedKey); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | ||||||
|  | @page | ||||||
|  | @model ExternalLoginsModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Manage your external logins"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.ExternalLogins; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <partial name="_StatusMessage" for="StatusMessage" /> | ||||||
|  | @if (Model.CurrentLogins?.Count > 0) | ||||||
|  | { | ||||||
|  |     <h3>Registered Logins</h3> | ||||||
|  |     <table class="table"> | ||||||
|  |         <tbody> | ||||||
|  |             @foreach (var login in Model.CurrentLogins) | ||||||
|  |             { | ||||||
|  |                 <tr> | ||||||
|  |                     <td id="@($"login-provider-{login.LoginProvider}")">@login.ProviderDisplayName</td> | ||||||
|  |                     <td> | ||||||
|  |                         @if (Model.ShowRemoveButton) | ||||||
|  |                         { | ||||||
|  |                             <form id="@($"remove-login-{login.LoginProvider}")" asp-page-handler="RemoveLogin" method="post"> | ||||||
|  |                                 <div> | ||||||
|  |                                     <input asp-for="@login.LoginProvider" name="LoginProvider" type="hidden" /> | ||||||
|  |                                     <input asp-for="@login.ProviderKey" name="ProviderKey" type="hidden" /> | ||||||
|  |                                     <button type="submit" class="btn btn-primary" title="Remove this @login.ProviderDisplayName login from your account">Remove</button> | ||||||
|  |                                 </div> | ||||||
|  |                             </form> | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             @:   | ||||||
|  |                         } | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |             } | ||||||
|  |         </tbody> | ||||||
|  |     </table> | ||||||
|  | } | ||||||
|  | @if (Model.OtherLogins?.Count > 0) | ||||||
|  | { | ||||||
|  |     <h4>Add another service to log in.</h4> | ||||||
|  |     <hr /> | ||||||
|  |     <form id="link-login-form" asp-page-handler="LinkLogin" method="post" class="form-horizontal"> | ||||||
|  |         <div id="socialLoginList"> | ||||||
|  |             <p> | ||||||
|  |                 @foreach (var provider in Model.OtherLogins) | ||||||
|  |                 { | ||||||
|  |                     <button id="@($"link-login-button-{provider.Name}")" type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> | ||||||
|  |                 } | ||||||
|  |             </p> | ||||||
|  |         </div> | ||||||
|  |     </form> | ||||||
|  | } | ||||||
							
								
								
									
										141
									
								
								Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,141 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Threading; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Authentication; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class ExternalLoginsModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly SignInManager<IdentityUser> _signInManager; | ||||||
|  |         private readonly IUserStore<IdentityUser> _userStore; | ||||||
|  | 
 | ||||||
|  |         public ExternalLoginsModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             SignInManager<IdentityUser> signInManager, | ||||||
|  |             IUserStore<IdentityUser> userStore) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |             _userStore = userStore; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public IList<UserLoginInfo> CurrentLogins { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public IList<AuthenticationScheme> OtherLogins { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool ShowRemoveButton { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGetAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             CurrentLogins = await _userManager.GetLoginsAsync(user); | ||||||
|  |             OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()) | ||||||
|  |                 .Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider)) | ||||||
|  |                 .ToList(); | ||||||
|  | 
 | ||||||
|  |             string passwordHash = null; | ||||||
|  |             if (_userStore is IUserPasswordStore<IdentityUser> userPasswordStore) | ||||||
|  |             { | ||||||
|  |                 passwordHash = await userPasswordStore.GetPasswordHashAsync(user, HttpContext.RequestAborted); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             ShowRemoveButton = passwordHash != null || CurrentLogins.Count > 1; | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostRemoveLoginAsync(string loginProvider, string providerKey) | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var result = await _userManager.RemoveLoginAsync(user, loginProvider, providerKey); | ||||||
|  |             if (!result.Succeeded) | ||||||
|  |             { | ||||||
|  |                 StatusMessage = "The external login was not removed."; | ||||||
|  |                 return RedirectToPage(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             await _signInManager.RefreshSignInAsync(user); | ||||||
|  |             StatusMessage = "The external login was removed."; | ||||||
|  |             return RedirectToPage(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostLinkLoginAsync(string provider) | ||||||
|  |         { | ||||||
|  |             // Clear the existing external cookie to ensure a clean login process | ||||||
|  |             await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); | ||||||
|  | 
 | ||||||
|  |             // Request a redirect to the external login provider to link a login for the current user | ||||||
|  |             var redirectUrl = Url.Page("./ExternalLogins", pageHandler: "LinkLoginCallback"); | ||||||
|  |             var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User)); | ||||||
|  |             return new ChallengeResult(provider, properties); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGetLinkLoginCallbackAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var userId = await _userManager.GetUserIdAsync(user); | ||||||
|  |             var info = await _signInManager.GetExternalLoginInfoAsync(userId); | ||||||
|  |             if (info == null) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException($"Unexpected error occurred loading external login info."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var result = await _userManager.AddLoginAsync(user, info); | ||||||
|  |             if (!result.Succeeded) | ||||||
|  |             { | ||||||
|  |                 StatusMessage = "The external login was not added. External logins can only be associated with one account."; | ||||||
|  |                 return RedirectToPage(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Clear the existing external cookie to ensure a clean login process | ||||||
|  |             await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); | ||||||
|  | 
 | ||||||
|  |             StatusMessage = "The external login was added."; | ||||||
|  |             return RedirectToPage(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,27 @@ | ||||||
|  | @page | ||||||
|  | @model GenerateRecoveryCodesModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <partial name="_StatusMessage" for="StatusMessage" /> | ||||||
|  | <h3>@ViewData["Title"]</h3> | ||||||
|  | <div class="alert alert-warning" role="alert"> | ||||||
|  |     <p> | ||||||
|  |         <span class="glyphicon glyphicon-warning-sign"></span> | ||||||
|  |         <strong>Put these codes in a safe place.</strong> | ||||||
|  |     </p> | ||||||
|  |     <p> | ||||||
|  |         If you lose your device and don't have the recovery codes you will lose access to your account. | ||||||
|  |     </p> | ||||||
|  |     <p> | ||||||
|  |         Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key | ||||||
|  |         used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a> | ||||||
|  |     </p> | ||||||
|  | </div> | ||||||
|  | <div> | ||||||
|  |     <form method="post"> | ||||||
|  |         <button class="btn btn-danger" type="submit">Generate Recovery Codes</button> | ||||||
|  |     </form> | ||||||
|  | </div> | ||||||
|  | @ -0,0 +1,82 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class GenerateRecoveryCodesModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly ILogger<GenerateRecoveryCodesModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public GenerateRecoveryCodesModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             ILogger<GenerateRecoveryCodesModel> logger) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string[] RecoveryCodes { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGetAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user); | ||||||
|  |             if (!isTwoFactorEnabled) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException($"Cannot generate recovery codes for user because they do not have 2FA enabled."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user); | ||||||
|  |             var userId = await _userManager.GetUserIdAsync(user); | ||||||
|  |             if (!isTwoFactorEnabled) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException($"Cannot generate recovery codes for user as they do not have 2FA enabled."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); | ||||||
|  |             RecoveryCodes = recoveryCodes.ToArray(); | ||||||
|  | 
 | ||||||
|  |             _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId); | ||||||
|  |             StatusMessage = "You have generated new recovery codes."; | ||||||
|  |             return RedirectToPage("./ShowRecoveryCodes"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								Areas/Identity/Pages/Account/Manage/Index.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Areas/Identity/Pages/Account/Manage/Index.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | ||||||
|  | @page | ||||||
|  | @model IndexModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Profile"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.Index; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h3>@ViewData["Title"]</h3> | ||||||
|  | <partial name="_StatusMessage" for="StatusMessage" /> | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-6"> | ||||||
|  |         <form id="profile-form" method="post"> | ||||||
|  |             <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Username" class="form-control" placeholder="Please choose your username." disabled /> | ||||||
|  |                 <label asp-for="Username" class="form-label"></label> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.PhoneNumber" class="form-control" placeholder="Please enter your phone number."/> | ||||||
|  |                 <label asp-for="Input.PhoneNumber" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.PhoneNumber" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <button id="update-profile-button" type="submit" class="w-100 btn btn-lg btn-primary">Save</button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										118
									
								
								Areas/Identity/Pages/Account/Manage/Index.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								Areas/Identity/Pages/Account/Manage/Index.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,118 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  | using System.Text.Encodings.Web; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class IndexModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly SignInManager<IdentityUser> _signInManager; | ||||||
|  | 
 | ||||||
|  |         public IndexModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             SignInManager<IdentityUser> signInManager) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public string Username { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Phone] | ||||||
|  |             [Display(Name = "Phone number")] | ||||||
|  |             public string PhoneNumber { get; set; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private async Task LoadAsync(IdentityUser user) | ||||||
|  |         { | ||||||
|  |             var userName = await _userManager.GetUserNameAsync(user); | ||||||
|  |             var phoneNumber = await _userManager.GetPhoneNumberAsync(user); | ||||||
|  | 
 | ||||||
|  |             Username = userName; | ||||||
|  | 
 | ||||||
|  |             Input = new InputModel | ||||||
|  |             { | ||||||
|  |                 PhoneNumber = phoneNumber | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGetAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             await LoadAsync(user); | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!ModelState.IsValid) | ||||||
|  |             { | ||||||
|  |                 await LoadAsync(user); | ||||||
|  |                 return Page(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var phoneNumber = await _userManager.GetPhoneNumberAsync(user); | ||||||
|  |             if (Input.PhoneNumber != phoneNumber) | ||||||
|  |             { | ||||||
|  |                 var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber); | ||||||
|  |                 if (!setPhoneResult.Succeeded) | ||||||
|  |                 { | ||||||
|  |                     StatusMessage = "Unexpected error when trying to set phone number."; | ||||||
|  |                     return RedirectToPage(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             await _signInManager.RefreshSignInAsync(user); | ||||||
|  |             StatusMessage = "Your profile has been updated"; | ||||||
|  |             return RedirectToPage(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										123
									
								
								Areas/Identity/Pages/Account/Manage/ManageNavPages.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								Areas/Identity/Pages/Account/Manage/ManageNavPages.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,123 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using Microsoft.AspNetCore.Mvc.Rendering; | ||||||
|  | 
 | ||||||
|  | namespace  turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |     ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |     /// </summary> | ||||||
|  |     public static class ManageNavPages | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string Index => "Index"; | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string Email => "Email"; | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string ChangePassword => "ChangePassword"; | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string DownloadPersonalData => "DownloadPersonalData"; | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string DeletePersonalData => "DeletePersonalData"; | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string ExternalLogins => "ExternalLogins"; | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string PersonalData => "PersonalData"; | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string TwoFactorAuthentication => "TwoFactorAuthentication"; | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email); | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword); | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData); | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData); | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins); | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData); | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication); | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string PageNavClass(ViewContext viewContext, string page) | ||||||
|  |         { | ||||||
|  |             var activePage = viewContext.ViewData["ActivePage"] as string | ||||||
|  |                 ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName); | ||||||
|  |             return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								Areas/Identity/Pages/Account/Manage/PersonalData.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Areas/Identity/Pages/Account/Manage/PersonalData.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | ||||||
|  | @page | ||||||
|  | @model PersonalDataModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Personal Data"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.PersonalData; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h3>@ViewData["Title"]</h3> | ||||||
|  | 
 | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-6"> | ||||||
|  |         <p>Your account contains personal data that you have given us. This page allows you to download or delete that data.</p> | ||||||
|  |         <p> | ||||||
|  |             <strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong> | ||||||
|  |         </p> | ||||||
|  |         <form id="download-data" asp-page="DownloadPersonalData" method="post"> | ||||||
|  |             <button class="btn btn-primary" type="submit">Download</button> | ||||||
|  |         </form> | ||||||
|  |         <p> | ||||||
|  |             <a id="delete" asp-page="DeletePersonalData" class="btn btn-danger">Delete</a> | ||||||
|  |         </p> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | using System; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class PersonalDataModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly ILogger<PersonalDataModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public PersonalDataModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             ILogger<PersonalDataModel> logger) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGet() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | @page | ||||||
|  | @model ResetAuthenticatorModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Reset authenticator key"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <partial name="_StatusMessage" for="StatusMessage" /> | ||||||
|  | <h3>@ViewData["Title"]</h3> | ||||||
|  | <div class="alert alert-warning" role="alert"> | ||||||
|  |     <p> | ||||||
|  |         <span class="glyphicon glyphicon-warning-sign"></span> | ||||||
|  |         <strong>If you reset your authenticator key your authenticator app will not work until you reconfigure it.</strong> | ||||||
|  |     </p> | ||||||
|  |     <p> | ||||||
|  |         This process disables 2FA until you verify your authenticator app. | ||||||
|  |         If you do not complete your authenticator app configuration you may lose access to your account. | ||||||
|  |     </p> | ||||||
|  | </div> | ||||||
|  | <div> | ||||||
|  |     <form id="reset-authenticator-form" method="post"> | ||||||
|  |         <button id="reset-authenticator-button" class="btn btn-danger" type="submit">Reset authenticator key</button> | ||||||
|  |     </form> | ||||||
|  | </div> | ||||||
|  | @ -0,0 +1,67 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class ResetAuthenticatorModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly SignInManager<IdentityUser> _signInManager; | ||||||
|  |         private readonly ILogger<ResetAuthenticatorModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public ResetAuthenticatorModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             SignInManager<IdentityUser> signInManager, | ||||||
|  |             ILogger<ResetAuthenticatorModel> logger) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGet() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             await _userManager.SetTwoFactorEnabledAsync(user, false); | ||||||
|  |             await _userManager.ResetAuthenticatorKeyAsync(user); | ||||||
|  |             var userId = await _userManager.GetUserIdAsync(user); | ||||||
|  |             _logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id); | ||||||
|  | 
 | ||||||
|  |             await _signInManager.RefreshSignInAsync(user); | ||||||
|  |             StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key."; | ||||||
|  | 
 | ||||||
|  |             return RedirectToPage("./EnableAuthenticator"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								Areas/Identity/Pages/Account/Manage/SetPassword.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Areas/Identity/Pages/Account/Manage/SetPassword.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | ||||||
|  | @page | ||||||
|  | @model SetPasswordModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Set password"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.ChangePassword; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h3>Set your password</h3> | ||||||
|  | <partial name="_StatusMessage" for="StatusMessage" /> | ||||||
|  | <p class="text-info"> | ||||||
|  |     You do not have a local username/password for this site. Add a local | ||||||
|  |     account so you can log in without an external login. | ||||||
|  | </p> | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-6"> | ||||||
|  |         <form id="set-password-form" method="post"> | ||||||
|  |             <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.NewPassword" class="form-control" autocomplete="new-password" placeholder="Please enter your new password."/> | ||||||
|  |                 <label asp-for="Input.NewPassword" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.NewPassword" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" placeholder="Please confirm your new password."/> | ||||||
|  |                 <label asp-for="Input.ConfirmPassword" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <button type="submit" class="w-100 btn btn-lg btn-primary">Set password</button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										114
									
								
								Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,114 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class SetPasswordModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly SignInManager<IdentityUser> _signInManager; | ||||||
|  | 
 | ||||||
|  |         public SetPasswordModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             SignInManager<IdentityUser> signInManager) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] | ||||||
|  |             [DataType(DataType.Password)] | ||||||
|  |             [Display(Name = "New password")] | ||||||
|  |             public string NewPassword { get; set; } | ||||||
|  | 
 | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [DataType(DataType.Password)] | ||||||
|  |             [Display(Name = "Confirm new password")] | ||||||
|  |             [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] | ||||||
|  |             public string ConfirmPassword { get; set; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGetAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var hasPassword = await _userManager.HasPasswordAsync(user); | ||||||
|  | 
 | ||||||
|  |             if (hasPassword) | ||||||
|  |             { | ||||||
|  |                 return RedirectToPage("./ChangePassword"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostAsync() | ||||||
|  |         { | ||||||
|  |             if (!ModelState.IsValid) | ||||||
|  |             { | ||||||
|  |                 return Page(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword); | ||||||
|  |             if (!addPasswordResult.Succeeded) | ||||||
|  |             { | ||||||
|  |                 foreach (var error in addPasswordResult.Errors) | ||||||
|  |                 { | ||||||
|  |                     ModelState.AddModelError(string.Empty, error.Description); | ||||||
|  |                 } | ||||||
|  |                 return Page(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             await _signInManager.RefreshSignInAsync(user); | ||||||
|  |             StatusMessage = "Your password has been set."; | ||||||
|  | 
 | ||||||
|  |             return RedirectToPage(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | ||||||
|  | @page | ||||||
|  | @model ShowRecoveryCodesModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Recovery codes"; | ||||||
|  |     ViewData["ActivePage"] = "TwoFactorAuthentication"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <partial name="_StatusMessage" for="StatusMessage" /> | ||||||
|  | <h3>@ViewData["Title"]</h3> | ||||||
|  | <div class="alert alert-warning" role="alert"> | ||||||
|  |     <p> | ||||||
|  |         <strong>Put these codes in a safe place.</strong> | ||||||
|  |     </p> | ||||||
|  |     <p> | ||||||
|  |         If you lose your device and don't have the recovery codes you will lose access to your account. | ||||||
|  |     </p> | ||||||
|  | </div> | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-12"> | ||||||
|  |         @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2) | ||||||
|  |         { | ||||||
|  |             <code class="recovery-code">@Model.RecoveryCodes[row]</code><text> </text><code class="recovery-code">@Model.RecoveryCodes[row + 1]</code><br /> | ||||||
|  |         } | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | @ -0,0 +1,46 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |     ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |     /// </summary> | ||||||
|  |     public class ShowRecoveryCodesModel : PageModel | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string[] RecoveryCodes { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public IActionResult OnGet() | ||||||
|  |         { | ||||||
|  |             if (RecoveryCodes == null || RecoveryCodes.Length == 0) | ||||||
|  |             { | ||||||
|  |                 return RedirectToPage("./TwoFactorAuthentication"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,71 @@ | ||||||
|  | @page | ||||||
|  | @using Microsoft.AspNetCore.Http.Features | ||||||
|  | @model TwoFactorAuthenticationModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Two-factor authentication (2FA)"; | ||||||
|  |     ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <partial name="_StatusMessage" for="StatusMessage" /> | ||||||
|  | <h3>@ViewData["Title"]</h3> | ||||||
|  | @{ | ||||||
|  |     var consentFeature = HttpContext.Features.Get<ITrackingConsentFeature>(); | ||||||
|  |     @if (consentFeature?.CanTrack ?? true) | ||||||
|  |     { | ||||||
|  |         @if (Model.Is2faEnabled) | ||||||
|  |         { | ||||||
|  |             if (Model.RecoveryCodesLeft == 0) | ||||||
|  |             { | ||||||
|  |                 <div class="alert alert-danger"> | ||||||
|  |                     <strong>You have no recovery codes left.</strong> | ||||||
|  |                     <p>You must <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a> before you can log in with a recovery code.</p> | ||||||
|  |                 </div> | ||||||
|  |             } | ||||||
|  |             else if (Model.RecoveryCodesLeft == 1) | ||||||
|  |             { | ||||||
|  |                 <div class="alert alert-danger"> | ||||||
|  |                     <strong>You have 1 recovery code left.</strong> | ||||||
|  |                     <p>You can <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p> | ||||||
|  |                 </div> | ||||||
|  |             } | ||||||
|  |             else if (Model.RecoveryCodesLeft <= 3) | ||||||
|  |             { | ||||||
|  |                 <div class="alert alert-warning"> | ||||||
|  |                     <strong>You have @Model.RecoveryCodesLeft recovery codes left.</strong> | ||||||
|  |                     <p>You should <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p> | ||||||
|  |                 </div> | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (Model.IsMachineRemembered) | ||||||
|  |             { | ||||||
|  |                 <form method="post" style="display: inline-block"> | ||||||
|  |                     <button type="submit" class="btn btn-primary">Forget this browser</button> | ||||||
|  |                 </form> | ||||||
|  |             } | ||||||
|  |             <a asp-page="./Disable2fa" class="btn btn-primary">Disable 2FA</a> | ||||||
|  |             <a asp-page="./GenerateRecoveryCodes" class="btn btn-primary">Reset recovery codes</a> | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         <h4>Authenticator app</h4> | ||||||
|  |         @if (!Model.HasAuthenticator) | ||||||
|  |         { | ||||||
|  |             <a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Add authenticator app</a> | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             <a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Set up authenticator app</a> | ||||||
|  |             <a id="reset-authenticator" asp-page="./ResetAuthenticator" class="btn btn-primary">Reset authenticator app</a> | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |         <div class="alert alert-danger"> | ||||||
|  |             <strong>Privacy and cookie policy have not been accepted.</strong> | ||||||
|  |             <p>You must accept the policy before you can enable two factor authentication.</p> | ||||||
|  |         </div> | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
|  | @ -0,0 +1,89 @@ | ||||||
|  | // Licensed to the .NET Foundation under one or more agreements. | ||||||
|  | // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.RazorPages; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
|  | { | ||||||
|  |     public class TwoFactorAuthenticationModel : PageModel | ||||||
|  |     { | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly SignInManager<IdentityUser> _signInManager; | ||||||
|  |         private readonly ILogger<TwoFactorAuthenticationModel> _logger; | ||||||
|  | 
 | ||||||
|  |         public TwoFactorAuthenticationModel( | ||||||
|  |             UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, ILogger<TwoFactorAuthenticationModel> logger) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool HasAuthenticator { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public int RecoveryCodesLeft { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public bool Is2faEnabled { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool IsMachineRemembered { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [TempData] | ||||||
|  |         public string StatusMessage { get; set; } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnGetAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null; | ||||||
|  |             Is2faEnabled = await _userManager.GetTwoFactorEnabledAsync(user); | ||||||
|  |             IsMachineRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user); | ||||||
|  |             RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user); | ||||||
|  | 
 | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> OnPostAsync() | ||||||
|  |         { | ||||||
|  |             var user = await _userManager.GetUserAsync(User); | ||||||
|  |             if (user == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             await _signInManager.ForgetTwoFactorClientAsync(); | ||||||
|  |             StatusMessage = "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code."; | ||||||
|  |             return RedirectToPage(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								Areas/Identity/Pages/Account/Manage/_Layout.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								Areas/Identity/Pages/Account/Manage/_Layout.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | @{ | ||||||
|  |     if (ViewData.TryGetValue("ParentLayout", out var parentLayout) && parentLayout !=  null) | ||||||
|  |     { | ||||||
|  |         Layout = parentLayout.ToString(); | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |         Layout = "/Areas/Identity/Pages/_Layout.cshtml"; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>Manage your account</h1> | ||||||
|  | 
 | ||||||
|  | <div> | ||||||
|  |     <h2>Change your account settings</h2> | ||||||
|  |     <hr /> | ||||||
|  |     <div class="row"> | ||||||
|  |         <div class="col-md-3"> | ||||||
|  |             <partial name="_ManageNav" /> | ||||||
|  |         </div> | ||||||
|  |         <div class="col-md-9"> | ||||||
|  |             @RenderBody() | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     @RenderSection("Scripts", required: false) | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | @inject SignInManager<IdentityUser> SignInManager | ||||||
|  | @{ | ||||||
|  |     var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); | ||||||
|  | } | ||||||
|  | <ul class="nav nav-pills flex-column"> | ||||||
|  |     <li class="nav-item"><a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">Profile</a></li> | ||||||
|  |     <li class="nav-item"><a class="nav-link @ManageNavPages.EmailNavClass(ViewContext)" id="email" asp-page="./Email">Email</a></li> | ||||||
|  |     <li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">Password</a></li> | ||||||
|  |     @if (hasExternalLogins) | ||||||
|  |     { | ||||||
|  |         <li id="external-logins" class="nav-item"><a id="external-login" class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-page="./ExternalLogins">External logins</a></li> | ||||||
|  |     } | ||||||
|  |     <li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li> | ||||||
|  |     <li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li> | ||||||
|  | </ul> | ||||||
							
								
								
									
										10
									
								
								Areas/Identity/Pages/Account/Manage/_StatusMessage.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								Areas/Identity/Pages/Account/Manage/_StatusMessage.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | @model string | ||||||
|  | 
 | ||||||
|  | @if (!String.IsNullOrEmpty(Model)) | ||||||
|  | { | ||||||
|  |     var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success"; | ||||||
|  |     <div class="alert alert-@statusMessageClass alert-dismissible" role="alert"> | ||||||
|  |         <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | ||||||
|  |         @Model | ||||||
|  |     </div> | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | @using turf_tasker.Areas.Identity.Pages.Account.Manage | ||||||
							
								
								
									
										67
									
								
								Areas/Identity/Pages/Account/Register.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								Areas/Identity/Pages/Account/Register.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | ||||||
|  | @page | ||||||
|  | @model RegisterModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Register"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | 
 | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-4"> | ||||||
|  |         <form id="registerForm" asp-route-returnUrl="@Model.ReturnUrl" method="post"> | ||||||
|  |             <h2>Create a new account.</h2> | ||||||
|  |             <hr /> | ||||||
|  |             <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" /> | ||||||
|  |                 <label asp-for="Input.Email">Email</label> | ||||||
|  |                 <span asp-validation-for="Input.Email" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" placeholder="password" /> | ||||||
|  |                 <label asp-for="Input.Password">Password</label> | ||||||
|  |                 <span asp-validation-for="Input.Password" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="password" /> | ||||||
|  |                 <label asp-for="Input.ConfirmPassword">Confirm Password</label> | ||||||
|  |                 <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <button id="registerSubmit" type="submit" class="w-100 btn btn-lg btn-primary">Register</button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  |     <div class="col-md-6 col-md-offset-2"> | ||||||
|  |         <section> | ||||||
|  |             <h3>Use another service to register.</h3> | ||||||
|  |             <hr /> | ||||||
|  |             @{ | ||||||
|  |                 if ((Model.ExternalLogins?.Count ?? 0) == 0) | ||||||
|  |                 { | ||||||
|  |                     <div> | ||||||
|  |                         <p> | ||||||
|  |                             There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article | ||||||
|  |                             about setting up this ASP.NET application to support logging in via external services</a>. | ||||||
|  |                         </p> | ||||||
|  |                     </div> | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal"> | ||||||
|  |                         <div> | ||||||
|  |                             <p> | ||||||
|  |                                 @foreach (var provider in Model.ExternalLogins!) | ||||||
|  |                                 { | ||||||
|  |                                     <button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> | ||||||
|  |                                 } | ||||||
|  |                             </p> | ||||||
|  |                         </div> | ||||||
|  |                     </form> | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         </section> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										180
									
								
								Areas/Identity/Pages/Account/Register.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								Areas/Identity/Pages/Account/Register.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<IdentityUser> _signInManager; | ||||||
|  |         private readonly UserManager<IdentityUser> _userManager; | ||||||
|  |         private readonly IUserStore<IdentityUser> _userStore; | ||||||
|  |         private readonly IUserEmailStore<IdentityUser> _emailStore; | ||||||
|  |         private readonly ILogger<RegisterModel> _logger; | ||||||
|  |         private readonly IEmailSender _emailSender; | ||||||
|  | 
 | ||||||
|  |         public RegisterModel( | ||||||
|  |             UserManager<IdentityUser> userManager, | ||||||
|  |             IUserStore<IdentityUser> userStore, | ||||||
|  |             SignInManager<IdentityUser> signInManager, | ||||||
|  |             ILogger<RegisterModel> logger, | ||||||
|  |             IEmailSender emailSender) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _userStore = userStore; | ||||||
|  |             _emailStore = GetEmailStore(); | ||||||
|  |             _signInManager = signInManager; | ||||||
|  |             _logger = logger; | ||||||
|  |             _emailSender = emailSender; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public string ReturnUrl { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public IList<AuthenticationScheme> ExternalLogins { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [EmailAddress] | ||||||
|  |             [Display(Name = "Email")] | ||||||
|  |             public string Email { get; set; } | ||||||
|  | 
 | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [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; } | ||||||
|  | 
 | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [DataType(DataType.Password)] | ||||||
|  |             [Display(Name = "Confirm 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<IActionResult> 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 <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>."); | ||||||
|  | 
 | ||||||
|  |                     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<IdentityUser>(); | ||||||
|  |             } | ||||||
|  |             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<IdentityUser> GetEmailStore() | ||||||
|  |         { | ||||||
|  |             if (!_userManager.SupportsUserEmail) | ||||||
|  |             { | ||||||
|  |                 throw new NotSupportedException("The default UI requires a user store with email support."); | ||||||
|  |             } | ||||||
|  |             return (IUserEmailStore<IdentityUser>)_userStore; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								Areas/Identity/Pages/Account/RegisterConfirmation.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Areas/Identity/Pages/Account/RegisterConfirmation.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | @page | ||||||
|  | @model RegisterConfirmationModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Register confirmation"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | @{ | ||||||
|  |     if (@Model.DisplayConfirmAccountLink) | ||||||
|  |     { | ||||||
|  | <p> | ||||||
|  |     This app does not currently have a real email sender registered, see <a href="https://aka.ms/aspaccountconf">these docs</a> for how to configure a real email sender. | ||||||
|  |     Normally this would be emailed: <a id="confirm-link" href="@Model.EmailConfirmationUrl">Click here to confirm your account</a> | ||||||
|  | </p> | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  | <p> | ||||||
|  |         Please check your email to confirm your account. | ||||||
|  | </p> | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
							
								
								
									
										79
									
								
								Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<IdentityUser> _userManager; | ||||||
|  |         private readonly IEmailSender _sender; | ||||||
|  | 
 | ||||||
|  |         public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _sender = sender; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public string Email { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool DisplayConfirmAccountLink { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public string EmailConfirmationUrl { get; set; } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> 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(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | ||||||
|  | @page | ||||||
|  | @model ResendEmailConfirmationModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Resend email confirmation"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | <h2>Enter your email.</h2> | ||||||
|  | <hr /> | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-4"> | ||||||
|  |         <form method="post"> | ||||||
|  |             <div asp-validation-summary="All" class="text-danger" role="alert"></div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.Email" class="form-control" aria-required="true" placeholder="name@example.com" /> | ||||||
|  |                 <label asp-for="Input.Email" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.Email" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <button type="submit" class="w-100 btn btn-lg btn-primary">Resend</button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
|  | @ -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<IdentityUser> _userManager; | ||||||
|  |         private readonly IEmailSender _emailSender; | ||||||
|  | 
 | ||||||
|  |         public ResendEmailConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender emailSender) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |             _emailSender = emailSender; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [EmailAddress] | ||||||
|  |             public string Email { get; set; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void OnGet() | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task<IActionResult> 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 <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>."); | ||||||
|  | 
 | ||||||
|  |             ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); | ||||||
|  |             return Page(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								Areas/Identity/Pages/Account/ResetPassword.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								Areas/Identity/Pages/Account/ResetPassword.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | ||||||
|  | @page | ||||||
|  | @model ResetPasswordModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Reset password"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | <h2>Reset your password.</h2> | ||||||
|  | <hr /> | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-4"> | ||||||
|  |         <form method="post"> | ||||||
|  |             <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> | ||||||
|  |             <input asp-for="Input.Code" type="hidden" /> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" /> | ||||||
|  |                 <label asp-for="Input.Email" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.Email" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please enter your password." /> | ||||||
|  |                 <label asp-for="Input.Password" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.Password" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-floating mb-3"> | ||||||
|  |                 <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please confirm your password." /> | ||||||
|  |                 <label asp-for="Input.ConfirmPassword" class="form-label"></label> | ||||||
|  |                 <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <button type="submit" class="w-100 btn btn-lg btn-primary">Reset</button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <partial name="_ValidationScriptsPartial" /> | ||||||
|  | } | ||||||
							
								
								
									
										117
									
								
								Areas/Identity/Pages/Account/ResetPassword.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								Areas/Identity/Pages/Account/ResetPassword.cshtml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<IdentityUser> _userManager; | ||||||
|  | 
 | ||||||
|  |         public ResetPasswordModel(UserManager<IdentityUser> userManager) | ||||||
|  |         { | ||||||
|  |             _userManager = userManager; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         [BindProperty] | ||||||
|  |         public InputModel Input { get; set; } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public class InputModel | ||||||
|  |         { | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             [EmailAddress] | ||||||
|  |             public string Email { get; set; } | ||||||
|  | 
 | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [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; } | ||||||
|  | 
 | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [DataType(DataType.Password)] | ||||||
|  |             [Display(Name = "Confirm password")] | ||||||
|  |             [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] | ||||||
|  |             public string ConfirmPassword { get; set; } | ||||||
|  | 
 | ||||||
|  |             /// <summary> | ||||||
|  |             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |             ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |             /// </summary> | ||||||
|  |             [Required] | ||||||
|  |             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<IActionResult> 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(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,10 @@ | ||||||
|  | @page | ||||||
|  | @model ResetPasswordConfirmationModel | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Reset password confirmation"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>@ViewData["Title"]</h1> | ||||||
|  | <p> | ||||||
|  |     Your password has been reset. Please <a asp-page="./Login">click here to log in</a>. | ||||||
|  | </p> | ||||||
|  | @ -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 | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |     ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |     /// </summary> | ||||||
|  |     [AllowAnonymous] | ||||||
|  |     public class ResetPasswordConfirmationModel : PageModel | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used | ||||||
|  |         ///     directly from your code. This API may change or be removed in future releases. | ||||||
|  |         /// </summary> | ||||||
|  |         public void OnGet() | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								Areas/Identity/Pages/Account/_StatusMessage.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								Areas/Identity/Pages/Account/_StatusMessage.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | @model string | ||||||
|  | 
 | ||||||
|  | @if (!String.IsNullOrEmpty(Model)) | ||||||
|  | { | ||||||
|  |     var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success"; | ||||||
|  |     <div class="alert alert-@statusMessageClass alert-dismissible" role="alert"> | ||||||
|  |         <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | ||||||
|  |         @Model | ||||||
|  |     </div> | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								Areas/Identity/Pages/Account/_ViewImports.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Areas/Identity/Pages/Account/_ViewImports.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | @using turf_tasker.Areas.Identity.Pages.Account | ||||||
							
								
								
									
										2
									
								
								Areas/Identity/Pages/_ValidationScriptsPartial.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								Areas/Identity/Pages/_ValidationScriptsPartial.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | ||||||
|  | <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script> | ||||||
|  | <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script> | ||||||
							
								
								
									
										4
									
								
								Areas/Identity/Pages/_ViewImports.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								Areas/Identity/Pages/_ViewImports.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | @using Microsoft.AspNetCore.Identity | ||||||
|  | @using turf_tasker.Areas.Identity | ||||||
|  | @using turf_tasker.Areas.Identity.Pages | ||||||
|  | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers | ||||||
							
								
								
									
										4
									
								
								Areas/Identity/Pages/_ViewStart.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								Areas/Identity/Pages/_ViewStart.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | 
 | ||||||
|  | @{ | ||||||
|  |     Layout = "/Views/Shared/_Layout.cshtml"; | ||||||
|  | } | ||||||
|  | @ -1,12 +1,20 @@ | ||||||
|  | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Microsoft.AspNetCore.Identity.UI; | ||||||
| using Microsoft.EntityFrameworkCore; | using Microsoft.EntityFrameworkCore; | ||||||
| using turf_tasker.Models; | using turf_tasker.Models; | ||||||
| 
 | 
 | ||||||
| namespace turf_tasker.Data; | namespace turf_tasker.Data; | ||||||
| 
 | 
 | ||||||
| public class ApplicationDbContext : DbContext | public class ApplicationDbContext : IdentityDbContext<IdentityUser> | ||||||
| { | { | ||||||
|  |     private readonly DbContextOptions<ApplicationDbContext> _options; | ||||||
|  | 
 | ||||||
|     public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) |     public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) | ||||||
|         : base(options) { } |         : base(options) | ||||||
|  |     { | ||||||
|  |         _options = options; | ||||||
|  |     } | ||||||
|      |      | ||||||
|     public DbSet<LawnCareEvent> LawnCareEvents { get; set; } |     public DbSet<LawnCareEvent> LawnCareEvents { get; set; } | ||||||
|      |      | ||||||
|  |  | ||||||
							
								
								
									
										333
									
								
								Migrations/20250621230711_AddIdentitySchema.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								Migrations/20250621230711_AddIdentitySchema.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,333 @@ | ||||||
|  | // <auto-generated /> | ||||||
|  | using System; | ||||||
|  | using Microsoft.EntityFrameworkCore; | ||||||
|  | using Microsoft.EntityFrameworkCore.Infrastructure; | ||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||||
|  | using turf_tasker.Data; | ||||||
|  | 
 | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Migrations | ||||||
|  | { | ||||||
|  |     [DbContext(typeof(ApplicationDbContext))] | ||||||
|  |     [Migration("20250621230711_AddIdentitySchema")] | ||||||
|  |     partial class AddIdentitySchema | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||||
|  |         { | ||||||
|  | #pragma warning disable 612, 618 | ||||||
|  |             modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("Id") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ConcurrencyStamp") | ||||||
|  |                         .IsConcurrencyToken() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Name") | ||||||
|  |                         .HasMaxLength(256) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("NormalizedName") | ||||||
|  |                         .HasMaxLength(256) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("NormalizedName") | ||||||
|  |                         .IsUnique() | ||||||
|  |                         .HasDatabaseName("RoleNameIndex"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetRoles", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ClaimType") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ClaimValue") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("RoleId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("RoleId"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetRoleClaims", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("Id") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<int>("AccessFailedCount") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ConcurrencyStamp") | ||||||
|  |                         .IsConcurrencyToken() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Email") | ||||||
|  |                         .HasMaxLength(256) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<bool>("EmailConfirmed") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<bool>("LockoutEnabled") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<DateTimeOffset?>("LockoutEnd") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("NormalizedEmail") | ||||||
|  |                         .HasMaxLength(256) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("NormalizedUserName") | ||||||
|  |                         .HasMaxLength(256) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("PasswordHash") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("PhoneNumber") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<bool>("PhoneNumberConfirmed") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("SecurityStamp") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<bool>("TwoFactorEnabled") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("UserName") | ||||||
|  |                         .HasMaxLength(256) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("NormalizedEmail") | ||||||
|  |                         .HasDatabaseName("EmailIndex"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("NormalizedUserName") | ||||||
|  |                         .IsUnique() | ||||||
|  |                         .HasDatabaseName("UserNameIndex"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetUsers", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ClaimType") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ClaimValue") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("UserId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("UserId"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetUserClaims", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("LoginProvider") | ||||||
|  |                         .HasMaxLength(128) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ProviderKey") | ||||||
|  |                         .HasMaxLength(128) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ProviderDisplayName") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("UserId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("LoginProvider", "ProviderKey"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("UserId"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetUserLogins", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("UserId") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("RoleId") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("UserId", "RoleId"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("RoleId"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetUserRoles", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("UserId") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("LoginProvider") | ||||||
|  |                         .HasMaxLength(128) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Name") | ||||||
|  |                         .HasMaxLength(128) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Value") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("UserId", "LoginProvider", "Name"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetUserTokens", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("turf_tasker.Models.LawnCareEvent", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<decimal?>("AppliedAmount") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<DateTime>("EventDate") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<int>("EventType") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<decimal?>("MowerHeightInches") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<int?>("MowingPattern") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Notes") | ||||||
|  |                         .HasMaxLength(500) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ProblemObserved") | ||||||
|  |                         .HasMaxLength(250) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ProductUsed") | ||||||
|  |                         .HasMaxLength(200) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("LawnCareEvents"); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("turf_tasker.Models.LawnCareTip", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<int>("Category") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Content") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Title") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasMaxLength(100) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("LawnCareTips"); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("RoleId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired(); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired(); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired(); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("RoleId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired(); | ||||||
|  | 
 | ||||||
|  |                     b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired(); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired(); | ||||||
|  |                 }); | ||||||
|  | #pragma warning restore 612, 618 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										222
									
								
								Migrations/20250621230711_AddIdentitySchema.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								Migrations/20250621230711_AddIdentitySchema.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,222 @@ | ||||||
|  | using System; | ||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  | 
 | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Migrations | ||||||
|  | { | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public partial class AddIdentitySchema : Migration | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Up(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.CreateTable( | ||||||
|  |                 name: "AspNetRoles", | ||||||
|  |                 columns: table => new | ||||||
|  |                 { | ||||||
|  |                     Id = table.Column<string>(type: "TEXT", nullable: false), | ||||||
|  |                     Name = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true), | ||||||
|  |                     NormalizedName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true), | ||||||
|  |                     ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true) | ||||||
|  |                 }, | ||||||
|  |                 constraints: table => | ||||||
|  |                 { | ||||||
|  |                     table.PrimaryKey("PK_AspNetRoles", x => x.Id); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateTable( | ||||||
|  |                 name: "AspNetUsers", | ||||||
|  |                 columns: table => new | ||||||
|  |                 { | ||||||
|  |                     Id = table.Column<string>(type: "TEXT", nullable: false), | ||||||
|  |                     UserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true), | ||||||
|  |                     NormalizedUserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true), | ||||||
|  |                     Email = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true), | ||||||
|  |                     NormalizedEmail = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true), | ||||||
|  |                     EmailConfirmed = table.Column<bool>(type: "INTEGER", nullable: false), | ||||||
|  |                     PasswordHash = table.Column<string>(type: "TEXT", nullable: true), | ||||||
|  |                     SecurityStamp = table.Column<string>(type: "TEXT", nullable: true), | ||||||
|  |                     ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true), | ||||||
|  |                     PhoneNumber = table.Column<string>(type: "TEXT", nullable: true), | ||||||
|  |                     PhoneNumberConfirmed = table.Column<bool>(type: "INTEGER", nullable: false), | ||||||
|  |                     TwoFactorEnabled = table.Column<bool>(type: "INTEGER", nullable: false), | ||||||
|  |                     LockoutEnd = table.Column<DateTimeOffset>(type: "TEXT", nullable: true), | ||||||
|  |                     LockoutEnabled = table.Column<bool>(type: "INTEGER", nullable: false), | ||||||
|  |                     AccessFailedCount = table.Column<int>(type: "INTEGER", nullable: false) | ||||||
|  |                 }, | ||||||
|  |                 constraints: table => | ||||||
|  |                 { | ||||||
|  |                     table.PrimaryKey("PK_AspNetUsers", x => x.Id); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateTable( | ||||||
|  |                 name: "AspNetRoleClaims", | ||||||
|  |                 columns: table => new | ||||||
|  |                 { | ||||||
|  |                     Id = table.Column<int>(type: "INTEGER", nullable: false) | ||||||
|  |                         .Annotation("Sqlite:Autoincrement", true), | ||||||
|  |                     RoleId = table.Column<string>(type: "TEXT", nullable: false), | ||||||
|  |                     ClaimType = table.Column<string>(type: "TEXT", nullable: true), | ||||||
|  |                     ClaimValue = table.Column<string>(type: "TEXT", nullable: true) | ||||||
|  |                 }, | ||||||
|  |                 constraints: table => | ||||||
|  |                 { | ||||||
|  |                     table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); | ||||||
|  |                     table.ForeignKey( | ||||||
|  |                         name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", | ||||||
|  |                         column: x => x.RoleId, | ||||||
|  |                         principalTable: "AspNetRoles", | ||||||
|  |                         principalColumn: "Id", | ||||||
|  |                         onDelete: ReferentialAction.Cascade); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateTable( | ||||||
|  |                 name: "AspNetUserClaims", | ||||||
|  |                 columns: table => new | ||||||
|  |                 { | ||||||
|  |                     Id = table.Column<int>(type: "INTEGER", nullable: false) | ||||||
|  |                         .Annotation("Sqlite:Autoincrement", true), | ||||||
|  |                     UserId = table.Column<string>(type: "TEXT", nullable: false), | ||||||
|  |                     ClaimType = table.Column<string>(type: "TEXT", nullable: true), | ||||||
|  |                     ClaimValue = table.Column<string>(type: "TEXT", nullable: true) | ||||||
|  |                 }, | ||||||
|  |                 constraints: table => | ||||||
|  |                 { | ||||||
|  |                     table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); | ||||||
|  |                     table.ForeignKey( | ||||||
|  |                         name: "FK_AspNetUserClaims_AspNetUsers_UserId", | ||||||
|  |                         column: x => x.UserId, | ||||||
|  |                         principalTable: "AspNetUsers", | ||||||
|  |                         principalColumn: "Id", | ||||||
|  |                         onDelete: ReferentialAction.Cascade); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateTable( | ||||||
|  |                 name: "AspNetUserLogins", | ||||||
|  |                 columns: table => new | ||||||
|  |                 { | ||||||
|  |                     LoginProvider = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false), | ||||||
|  |                     ProviderKey = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false), | ||||||
|  |                     ProviderDisplayName = table.Column<string>(type: "TEXT", nullable: true), | ||||||
|  |                     UserId = table.Column<string>(type: "TEXT", nullable: false) | ||||||
|  |                 }, | ||||||
|  |                 constraints: table => | ||||||
|  |                 { | ||||||
|  |                     table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); | ||||||
|  |                     table.ForeignKey( | ||||||
|  |                         name: "FK_AspNetUserLogins_AspNetUsers_UserId", | ||||||
|  |                         column: x => x.UserId, | ||||||
|  |                         principalTable: "AspNetUsers", | ||||||
|  |                         principalColumn: "Id", | ||||||
|  |                         onDelete: ReferentialAction.Cascade); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateTable( | ||||||
|  |                 name: "AspNetUserRoles", | ||||||
|  |                 columns: table => new | ||||||
|  |                 { | ||||||
|  |                     UserId = table.Column<string>(type: "TEXT", nullable: false), | ||||||
|  |                     RoleId = table.Column<string>(type: "TEXT", nullable: false) | ||||||
|  |                 }, | ||||||
|  |                 constraints: table => | ||||||
|  |                 { | ||||||
|  |                     table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); | ||||||
|  |                     table.ForeignKey( | ||||||
|  |                         name: "FK_AspNetUserRoles_AspNetRoles_RoleId", | ||||||
|  |                         column: x => x.RoleId, | ||||||
|  |                         principalTable: "AspNetRoles", | ||||||
|  |                         principalColumn: "Id", | ||||||
|  |                         onDelete: ReferentialAction.Cascade); | ||||||
|  |                     table.ForeignKey( | ||||||
|  |                         name: "FK_AspNetUserRoles_AspNetUsers_UserId", | ||||||
|  |                         column: x => x.UserId, | ||||||
|  |                         principalTable: "AspNetUsers", | ||||||
|  |                         principalColumn: "Id", | ||||||
|  |                         onDelete: ReferentialAction.Cascade); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateTable( | ||||||
|  |                 name: "AspNetUserTokens", | ||||||
|  |                 columns: table => new | ||||||
|  |                 { | ||||||
|  |                     UserId = table.Column<string>(type: "TEXT", nullable: false), | ||||||
|  |                     LoginProvider = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false), | ||||||
|  |                     Name = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false), | ||||||
|  |                     Value = table.Column<string>(type: "TEXT", nullable: true) | ||||||
|  |                 }, | ||||||
|  |                 constraints: table => | ||||||
|  |                 { | ||||||
|  |                     table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); | ||||||
|  |                     table.ForeignKey( | ||||||
|  |                         name: "FK_AspNetUserTokens_AspNetUsers_UserId", | ||||||
|  |                         column: x => x.UserId, | ||||||
|  |                         principalTable: "AspNetUsers", | ||||||
|  |                         principalColumn: "Id", | ||||||
|  |                         onDelete: ReferentialAction.Cascade); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateIndex( | ||||||
|  |                 name: "IX_AspNetRoleClaims_RoleId", | ||||||
|  |                 table: "AspNetRoleClaims", | ||||||
|  |                 column: "RoleId"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateIndex( | ||||||
|  |                 name: "RoleNameIndex", | ||||||
|  |                 table: "AspNetRoles", | ||||||
|  |                 column: "NormalizedName", | ||||||
|  |                 unique: true); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateIndex( | ||||||
|  |                 name: "IX_AspNetUserClaims_UserId", | ||||||
|  |                 table: "AspNetUserClaims", | ||||||
|  |                 column: "UserId"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateIndex( | ||||||
|  |                 name: "IX_AspNetUserLogins_UserId", | ||||||
|  |                 table: "AspNetUserLogins", | ||||||
|  |                 column: "UserId"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateIndex( | ||||||
|  |                 name: "IX_AspNetUserRoles_RoleId", | ||||||
|  |                 table: "AspNetUserRoles", | ||||||
|  |                 column: "RoleId"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateIndex( | ||||||
|  |                 name: "EmailIndex", | ||||||
|  |                 table: "AspNetUsers", | ||||||
|  |                 column: "NormalizedEmail"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.CreateIndex( | ||||||
|  |                 name: "UserNameIndex", | ||||||
|  |                 table: "AspNetUsers", | ||||||
|  |                 column: "NormalizedUserName", | ||||||
|  |                 unique: true); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Down(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.DropTable( | ||||||
|  |                 name: "AspNetRoleClaims"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.DropTable( | ||||||
|  |                 name: "AspNetUserClaims"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.DropTable( | ||||||
|  |                 name: "AspNetUserLogins"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.DropTable( | ||||||
|  |                 name: "AspNetUserRoles"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.DropTable( | ||||||
|  |                 name: "AspNetUserTokens"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.DropTable( | ||||||
|  |                 name: "AspNetRoles"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.DropTable( | ||||||
|  |                 name: "AspNetUsers"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -15,7 +15,203 @@ namespace turf_tasker.Migrations | ||||||
|         protected override void BuildModel(ModelBuilder modelBuilder) |         protected override void BuildModel(ModelBuilder modelBuilder) | ||||||
|         { |         { | ||||||
| #pragma warning disable 612, 618 | #pragma warning disable 612, 618 | ||||||
|             modelBuilder.HasAnnotation("ProductVersion", "9.0.6"); |             modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("Id") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ConcurrencyStamp") | ||||||
|  |                         .IsConcurrencyToken() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Name") | ||||||
|  |                         .HasMaxLength(256) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("NormalizedName") | ||||||
|  |                         .HasMaxLength(256) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("NormalizedName") | ||||||
|  |                         .IsUnique() | ||||||
|  |                         .HasDatabaseName("RoleNameIndex"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetRoles", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ClaimType") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ClaimValue") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("RoleId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("RoleId"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetRoleClaims", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("Id") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<int>("AccessFailedCount") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ConcurrencyStamp") | ||||||
|  |                         .IsConcurrencyToken() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Email") | ||||||
|  |                         .HasMaxLength(256) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<bool>("EmailConfirmed") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<bool>("LockoutEnabled") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<DateTimeOffset?>("LockoutEnd") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("NormalizedEmail") | ||||||
|  |                         .HasMaxLength(256) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("NormalizedUserName") | ||||||
|  |                         .HasMaxLength(256) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("PasswordHash") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("PhoneNumber") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<bool>("PhoneNumberConfirmed") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("SecurityStamp") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<bool>("TwoFactorEnabled") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("UserName") | ||||||
|  |                         .HasMaxLength(256) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("NormalizedEmail") | ||||||
|  |                         .HasDatabaseName("EmailIndex"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("NormalizedUserName") | ||||||
|  |                         .IsUnique() | ||||||
|  |                         .HasDatabaseName("UserNameIndex"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetUsers", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ClaimType") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ClaimValue") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("UserId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("UserId"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetUserClaims", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("LoginProvider") | ||||||
|  |                         .HasMaxLength(128) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ProviderKey") | ||||||
|  |                         .HasMaxLength(128) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ProviderDisplayName") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("UserId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("LoginProvider", "ProviderKey"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("UserId"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetUserLogins", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("UserId") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("RoleId") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("UserId", "RoleId"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("RoleId"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetUserRoles", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("UserId") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("LoginProvider") | ||||||
|  |                         .HasMaxLength(128) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Name") | ||||||
|  |                         .HasMaxLength(128) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Value") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("UserId", "LoginProvider", "Name"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("AspNetUserTokens", (string)null); | ||||||
|  |                 }); | ||||||
| 
 | 
 | ||||||
|             modelBuilder.Entity("turf_tasker.Models.LawnCareEvent", b => |             modelBuilder.Entity("turf_tasker.Models.LawnCareEvent", b => | ||||||
|                 { |                 { | ||||||
|  | @ -77,6 +273,57 @@ namespace turf_tasker.Migrations | ||||||
| 
 | 
 | ||||||
|                     b.ToTable("LawnCareTips"); |                     b.ToTable("LawnCareTips"); | ||||||
|                 }); |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("RoleId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired(); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired(); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired(); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("RoleId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired(); | ||||||
|  | 
 | ||||||
|  |                     b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired(); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired(); | ||||||
|  |                 }); | ||||||
| #pragma warning restore 612, 618 | #pragma warning restore 612, 618 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,20 +1,22 @@ | ||||||
| using Microsoft.EntityFrameworkCore; | using Microsoft.EntityFrameworkCore; | ||||||
| using turf_tasker.Data; | using turf_tasker.Data; | ||||||
|  | using Microsoft.AspNetCore.Identity; | ||||||
| 
 | 
 | ||||||
| var builder = WebApplication.CreateBuilder(args); | var builder = WebApplication.CreateBuilder(args); | ||||||
| 
 | 
 | ||||||
| // Add services to the container. |  | ||||||
| builder.Services.AddDbContext<ApplicationDbContext>(options => | builder.Services.AddDbContext<ApplicationDbContext>(options => | ||||||
|     options.UseSqlite( |     options.UseSqlite( | ||||||
|         builder.Configuration.GetConnectionString("DefaultConnection") |         builder.Configuration.GetConnectionString("DefaultConnection") | ||||||
|     ) |     ) | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
|  | builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) | ||||||
|  |     .AddEntityFrameworkStores<ApplicationDbContext>(); | ||||||
|  | 
 | ||||||
| builder.Services.AddControllersWithViews(); | builder.Services.AddControllersWithViews(); | ||||||
| 
 | 
 | ||||||
| var app = builder.Build(); | var app = builder.Build(); | ||||||
| 
 | 
 | ||||||
| // Configure the HTTP request pipeline. |  | ||||||
| if (!app.Environment.IsDevelopment()) | if (!app.Environment.IsDevelopment()) | ||||||
| { | { | ||||||
|     app.UseExceptionHandler("/Home/Error"); |     app.UseExceptionHandler("/Home/Error"); | ||||||
|  | @ -26,6 +28,7 @@ app.UseStaticFiles(); | ||||||
| 
 | 
 | ||||||
| app.UseRouting(); | app.UseRouting(); | ||||||
| 
 | 
 | ||||||
|  | app.UseAuthentication(); | ||||||
| app.UseAuthorization(); | app.UseAuthorization(); | ||||||
| 
 | 
 | ||||||
| app.MapControllerRoute( | app.MapControllerRoute( | ||||||
|  | @ -33,4 +36,6 @@ app.MapControllerRoute( | ||||||
|     pattern: "{controller=Home}/{action=Index}/{id?}" |     pattern: "{controller=Home}/{action=Index}/{id?}" | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
|  | app.MapRazorPages(); | ||||||
|  | 
 | ||||||
| app.Run(); | app.Run(); | ||||||
|  | @ -33,6 +33,7 @@ | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|         </nav> |         </nav> | ||||||
|  |         <partial name="_LoginPartial" /> | ||||||
|     </header> |     </header> | ||||||
|     <div class="container"> |     <div class="container"> | ||||||
|         <main role="main" class="pb-3"> |         <main role="main" class="pb-3"> | ||||||
|  |  | ||||||
							
								
								
									
										26
									
								
								Views/Shared/_LoginPartial.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Views/Shared/_LoginPartial.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | ||||||
|  | @using Microsoft.AspNetCore.Identity | ||||||
|  | @inject SignInManager<IdentityUser> SignInManager | ||||||
|  | @inject UserManager<IdentityUser> UserManager | ||||||
|  | 
 | ||||||
|  | <ul class="navbar-nav"> | ||||||
|  |     @if (SignInManager.IsSignedIn(User)) | ||||||
|  |     { | ||||||
|  |         <li class="nav-item"> | ||||||
|  |             <a id="manage" class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @UserManager.GetUserName(User)!</a> | ||||||
|  |         </li> | ||||||
|  |         <li class="nav-item"> | ||||||
|  |             <form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })"> | ||||||
|  |                 <button id="logout" type="submit" class="nav-link btn btn-link text-dark">Logout</button> | ||||||
|  |             </form> | ||||||
|  |         </li> | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |         <li class="nav-item"> | ||||||
|  |             <a class="nav-link text-dark" id="register" asp-area="Identity" asp-page="/Account/Register">Register</a> | ||||||
|  |         </li> | ||||||
|  |         <li class="nav-item"> | ||||||
|  |             <a class="nav-link text-dark" id="login" asp-area="Identity" asp-page="/Account/Login">Login</a> | ||||||
|  |         </li> | ||||||
|  |     } | ||||||
|  | </ul> | ||||||
|  | @ -7,12 +7,21 @@ | ||||||
|     </PropertyGroup> |     </PropertyGroup> | ||||||
| 
 | 
 | ||||||
|     <ItemGroup> |     <ItemGroup> | ||||||
|       <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" /> |         <!-- Identity Packages --> | ||||||
|       <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.17" /> |         <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.6" /> | ||||||
|       <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.6"> |         <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.6" /> | ||||||
|  | 
 | ||||||
|  |         <!-- Explicitly define ALL core EF Core packages to force version alignment --> | ||||||
|  |         <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" /> | ||||||
|  |         <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.6" /> | ||||||
|  |         <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" /> | ||||||
|  |         <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" /> | ||||||
|  |         <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6"> | ||||||
|             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||||
|             <PrivateAssets>all</PrivateAssets> |             <PrivateAssets>all</PrivateAssets> | ||||||
|         </PackageReference> |         </PackageReference> | ||||||
|  | 
 | ||||||
|  |         <!-- Scaffolding Package --> | ||||||
|         <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" /> |         <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" /> | ||||||
|     </ItemGroup> |     </ItemGroup> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue