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 turf_tasker.Models; | ||||
| 
 | ||||
| namespace turf_tasker.Data; | ||||
| 
 | ||||
| public class ApplicationDbContext : DbContext | ||||
| public class ApplicationDbContext : IdentityDbContext<IdentityUser> | ||||
| { | ||||
|     private readonly DbContextOptions<ApplicationDbContext> _options; | ||||
| 
 | ||||
|     public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) | ||||
|         : base(options) { } | ||||
|         : base(options) | ||||
|     { | ||||
|         _options = options; | ||||
|     } | ||||
|      | ||||
|     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) | ||||
|         { | ||||
| #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 => | ||||
|                 { | ||||
|  | @ -77,6 +273,57 @@ namespace turf_tasker.Migrations | |||
| 
 | ||||
|                     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 | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -1,20 +1,22 @@ | |||
| using Microsoft.EntityFrameworkCore; | ||||
| using turf_tasker.Data; | ||||
| using Microsoft.AspNetCore.Identity; | ||||
| 
 | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
| 
 | ||||
| // Add services to the container. | ||||
| builder.Services.AddDbContext<ApplicationDbContext>(options => | ||||
|     options.UseSqlite( | ||||
|         builder.Configuration.GetConnectionString("DefaultConnection") | ||||
|     ) | ||||
| ); | ||||
| 
 | ||||
| builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) | ||||
|     .AddEntityFrameworkStores<ApplicationDbContext>(); | ||||
| 
 | ||||
| builder.Services.AddControllersWithViews(); | ||||
| 
 | ||||
| var app = builder.Build(); | ||||
| 
 | ||||
| // Configure the HTTP request pipeline. | ||||
| if (!app.Environment.IsDevelopment()) | ||||
| { | ||||
|     app.UseExceptionHandler("/Home/Error"); | ||||
|  | @ -26,6 +28,7 @@ app.UseStaticFiles(); | |||
| 
 | ||||
| app.UseRouting(); | ||||
| 
 | ||||
| app.UseAuthentication(); | ||||
| app.UseAuthorization(); | ||||
| 
 | ||||
| app.MapControllerRoute( | ||||
|  | @ -33,4 +36,6 @@ app.MapControllerRoute( | |||
|     pattern: "{controller=Home}/{action=Index}/{id?}" | ||||
| ); | ||||
| 
 | ||||
| app.MapRazorPages(); | ||||
| 
 | ||||
| app.Run(); | ||||
|  | @ -33,6 +33,7 @@ | |||
|                 </div> | ||||
|             </div> | ||||
|         </nav> | ||||
|         <partial name="_LoginPartial" /> | ||||
|     </header> | ||||
|     <div class="container"> | ||||
|         <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> | ||||
| 
 | ||||
|     <ItemGroup> | ||||
|       <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" /> | ||||
|       <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.17" /> | ||||
|       <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.6"> | ||||
|         <!-- Identity Packages --> | ||||
|         <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.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> | ||||
|             <PrivateAssets>all</PrivateAssets> | ||||
|         </PackageReference> | ||||
| 
 | ||||
|         <!-- Scaffolding Package --> | ||||
|         <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" /> | ||||
|     </ItemGroup> | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue