diff --git a/Wishlist/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs b/Wishlist/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs deleted file mode 100644 index 4f78465..0000000 --- a/Wishlist/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System.Security.Claims; -using System.Text.Json; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Primitives; -using Wishlist.Components.Account.Pages; -using Wishlist.Components.Account.Pages.Manage; -using Wishlist.Data; -using Wishlist.Data.Entities; - -namespace Microsoft.AspNetCore.Routing; - -internal static class IdentityComponentsEndpointRouteBuilderExtensions -{ - // These endpoints are required by the Identity Razor components defined in the /Components/Account/Pages directory of this project. - public static IEndpointConventionBuilder MapAdditionalIdentityEndpoints(this IEndpointRouteBuilder endpoints) - { - ArgumentNullException.ThrowIfNull(endpoints); - - var accountGroup = endpoints.MapGroup("/Account"); - - accountGroup.MapPost("/PerformExternalLogin", ( - HttpContext context, - [FromServices] SignInManager signInManager, - [FromForm] string provider, - [FromForm] string returnUrl) => - { - IEnumerable> query = - [ - new("ReturnUrl", returnUrl), - new("Action", ExternalLogin.LoginCallbackAction) - ]; - - var redirectUrl = UriHelper.BuildRelative( - context.Request.PathBase, - "/Account/ExternalLogin", - QueryString.Create(query)); - - var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); - return TypedResults.Challenge(properties, [provider]); - }); - - accountGroup.MapPost("/Logout", async ( - ClaimsPrincipal user, - [FromServices] SignInManager signInManager, - [FromForm] string returnUrl) => - { - await signInManager.SignOutAsync(); - return TypedResults.LocalRedirect($"~/{returnUrl}"); - }); - - var manageGroup = accountGroup.MapGroup("/Manage").RequireAuthorization(); - - manageGroup.MapPost("/LinkExternalLogin", async ( - HttpContext context, - [FromServices] SignInManager signInManager, - [FromForm] string provider) => - { - // Clear the existing external cookie to ensure a clean login process - await context.SignOutAsync(IdentityConstants.ExternalScheme); - - var redirectUrl = UriHelper.BuildRelative( - context.Request.PathBase, - "/Account/Manage/ExternalLogins", - QueryString.Create("Action", ExternalLogins.LinkLoginCallbackAction)); - - var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, - signInManager.UserManager.GetUserId(context.User)); - return TypedResults.Challenge(properties, [provider]); - }); - - var loggerFactory = endpoints.ServiceProvider.GetRequiredService(); - var downloadLogger = loggerFactory.CreateLogger("DownloadPersonalData"); - - manageGroup.MapPost("/DownloadPersonalData", async ( - HttpContext context, - [FromServices] UserManager userManager, - [FromServices] AuthenticationStateProvider authenticationStateProvider) => - { - var user = await userManager.GetUserAsync(context.User); - if (user is null) - { - return Results.NotFound($"Unable to load user with ID '{userManager.GetUserId(context.User)}'."); - } - - var userId = await userManager.GetUserIdAsync(user); - downloadLogger.LogInformation("User with ID '{UserId}' asked for their personal data.", userId); - - // Only include personal data for download - var personalData = new Dictionary(); - var personalDataProps = typeof(User).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))!); - var fileBytes = JsonSerializer.SerializeToUtf8Bytes(personalData); - - context.Response.Headers.TryAdd("Content-Disposition", "attachment; filename=PersonalData.json"); - return TypedResults.File(fileBytes, contentType: "application/json", fileDownloadName: "PersonalData.json"); - }); - - return accountGroup; - } -} \ No newline at end of file diff --git a/Wishlist/Components/Account/IdentityNoOpEmailSender.cs b/Wishlist/Components/Account/IdentityNoOpEmailSender.cs deleted file mode 100644 index 96ef426..0000000 --- a/Wishlist/Components/Account/IdentityNoOpEmailSender.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.UI.Services; -using Wishlist.Data; -using Wishlist.Data.Entities; - -namespace Wishlist.Components.Account; - -// Remove the "else if (EmailSender is IdentityNoOpEmailSender)" block from RegisterConfirmation.razor after updating with a real implementation. -internal sealed class IdentityNoOpEmailSender : IEmailSender -{ - private readonly IEmailSender emailSender = new NoOpEmailSender(); - - public Task SendConfirmationLinkAsync(User user, string email, string confirmationLink) => - emailSender.SendEmailAsync(email, "Confirm your email", - $"Please confirm your account by clicking here."); - - public Task SendPasswordResetLinkAsync(User user, string email, string resetLink) => - emailSender.SendEmailAsync(email, "Reset your password", - $"Please reset your password by clicking here."); - - public Task SendPasswordResetCodeAsync(User user, string email, string resetCode) => - emailSender.SendEmailAsync(email, "Reset your password", - $"Please reset your password using the following code: {resetCode}"); -} \ No newline at end of file diff --git a/Wishlist/Components/Account/IdentityRedirectManager.cs b/Wishlist/Components/Account/IdentityRedirectManager.cs deleted file mode 100644 index 56b9f38..0000000 --- a/Wishlist/Components/Account/IdentityRedirectManager.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Components; - -namespace Wishlist.Components.Account; - -internal sealed class IdentityRedirectManager(NavigationManager navigationManager) -{ - public const string StatusCookieName = "Identity.StatusMessage"; - - private static readonly CookieBuilder StatusCookieBuilder = new() - { - SameSite = SameSiteMode.Strict, - HttpOnly = true, - IsEssential = true, - MaxAge = TimeSpan.FromSeconds(5), - }; - - [DoesNotReturn] - public void RedirectTo(string? uri) - { - uri ??= ""; - - // Prevent open redirects. - if (!Uri.IsWellFormedUriString(uri, UriKind.Relative)) - { - uri = navigationManager.ToBaseRelativePath(uri); - } - - // During static rendering, NavigateTo throws a NavigationException which is handled by the framework as a redirect. - // So as long as this is called from a statically rendered Identity component, the InvalidOperationException is never thrown. - navigationManager.NavigateTo(uri); - throw new InvalidOperationException( - $"{nameof(IdentityRedirectManager)} can only be used during static rendering."); - } - - [DoesNotReturn] - public void RedirectTo(string uri, Dictionary queryParameters) - { - var uriWithoutQuery = navigationManager.ToAbsoluteUri(uri).GetLeftPart(UriPartial.Path); - var newUri = navigationManager.GetUriWithQueryParameters(uriWithoutQuery, queryParameters); - RedirectTo(newUri); - } - - [DoesNotReturn] - public void RedirectToWithStatus(string uri, string message, HttpContext context) - { - context.Response.Cookies.Append(StatusCookieName, message, StatusCookieBuilder.Build(context)); - RedirectTo(uri); - } - - private string CurrentPath => navigationManager.ToAbsoluteUri(navigationManager.Uri).GetLeftPart(UriPartial.Path); - - [DoesNotReturn] - public void RedirectToCurrentPage() => RedirectTo(CurrentPath); - - [DoesNotReturn] - public void RedirectToCurrentPageWithStatus(string message, HttpContext context) - => RedirectToWithStatus(CurrentPath, message, context); -} \ No newline at end of file diff --git a/Wishlist/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs b/Wishlist/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs deleted file mode 100644 index d725380..0000000 --- a/Wishlist/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Components.Server; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using Wishlist.Data; -using Wishlist.Data.Entities; - -namespace Wishlist.Components.Account; - -// This is a server-side AuthenticationStateProvider that revalidates the security stamp for the connected user -// every 30 minutes an interactive circuit is connected. -internal sealed class IdentityRevalidatingAuthenticationStateProvider( - ILoggerFactory loggerFactory, - IServiceScopeFactory scopeFactory, - IOptions options) - : RevalidatingServerAuthenticationStateProvider(loggerFactory) -{ - protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30); - - protected override async Task ValidateAuthenticationStateAsync( - AuthenticationState authenticationState, CancellationToken cancellationToken) - { - // Get the user manager from a new scope to ensure it fetches fresh data - await using var scope = scopeFactory.CreateAsyncScope(); - var userManager = scope.ServiceProvider.GetRequiredService>(); - return await ValidateSecurityStampAsync(userManager, authenticationState.User); - } - - private async Task ValidateSecurityStampAsync(UserManager userManager, - ClaimsPrincipal principal) - { - var user = await userManager.GetUserAsync(principal); - if (user is null) - { - return false; - } - else if (!userManager.SupportsUserSecurityStamp) - { - return true; - } - else - { - var principalStamp = principal.FindFirstValue(options.Value.ClaimsIdentity.SecurityStampClaimType); - var userStamp = await userManager.GetSecurityStampAsync(user); - return principalStamp == userStamp; - } - } -} \ No newline at end of file diff --git a/Wishlist/Components/Account/IdentityUserAccessor.cs b/Wishlist/Components/Account/IdentityUserAccessor.cs deleted file mode 100644 index aeeb18f..0000000 --- a/Wishlist/Components/Account/IdentityUserAccessor.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Wishlist.Data; -using Wishlist.Data.Entities; - -namespace Wishlist.Components.Account; - -internal sealed class IdentityUserAccessor( - UserManager userManager, - IdentityRedirectManager redirectManager) -{ - public async Task GetRequiredUserAsync(HttpContext context) - { - var user = await userManager.GetUserAsync(context.User); - - if (user is null) - { - redirectManager.RedirectToWithStatus("Account/InvalidUser", - $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context); - } - - return user; - } -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/AccessDenied.razor b/Wishlist/Components/Account/Pages/AccessDenied.razor deleted file mode 100644 index 8600a2d..0000000 --- a/Wishlist/Components/Account/Pages/AccessDenied.razor +++ /dev/null @@ -1,8 +0,0 @@ -@page "/Account/AccessDenied" - -Access denied - -
-

Access denied

-

You do not have access to this resource.

-
\ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/ConfirmEmail.razor b/Wishlist/Components/Account/Pages/ConfirmEmail.razor deleted file mode 100644 index 644bc97..0000000 --- a/Wishlist/Components/Account/Pages/ConfirmEmail.razor +++ /dev/null @@ -1,47 +0,0 @@ -@page "/Account/ConfirmEmail" - -@using System.Text -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject IdentityRedirectManager RedirectManager - -Confirm email - -

Confirm email

- - -@code { - private string? statusMessage; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromQuery] private string? UserId { get; set; } - - [SupplyParameterFromQuery] private string? Code { get; set; } - - protected override async Task OnInitializedAsync() - { - if (UserId is null || Code is null) - { - RedirectManager.RedirectTo(""); - } - - var user = await UserManager.FindByIdAsync(UserId); - if (user is null) - { - HttpContext.Response.StatusCode = StatusCodes.Status404NotFound; - statusMessage = $"Error loading user with ID {UserId}"; - } - else - { - var 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."; - } - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/ConfirmEmailChange.razor b/Wishlist/Components/Account/Pages/ConfirmEmailChange.razor deleted file mode 100644 index 725c25e..0000000 --- a/Wishlist/Components/Account/Pages/ConfirmEmailChange.razor +++ /dev/null @@ -1,66 +0,0 @@ -@page "/Account/ConfirmEmailChange" - -@using System.Text -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject IdentityRedirectManager RedirectManager - -Confirm email change - -

Confirm email change

- - - -@code { - private string? message; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromQuery] private string? UserId { get; set; } - - [SupplyParameterFromQuery] private string? Email { get; set; } - - [SupplyParameterFromQuery] private string? Code { get; set; } - - protected override async Task OnInitializedAsync() - { - if (UserId is null || Email is null || Code is null) - { - RedirectManager.RedirectToWithStatus( - "Account/Login", "Error: Invalid email change confirmation link.", HttpContext); - } - - var user = await UserManager.FindByIdAsync(UserId); - if (user is null) - { - message = "Unable to find user with Id '{userId}'"; - return; - } - - var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); - var result = await UserManager.ChangeEmailAsync(user, Email, code); - if (!result.Succeeded) - { - message = "Error changing email."; - return; - } - - // 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) - { - message = "Error changing user name."; - return; - } - - await SignInManager.RefreshSignInAsync(user); - message = "Thank you for confirming your email change."; - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/ExternalLogin.razor b/Wishlist/Components/Account/Pages/ExternalLogin.razor deleted file mode 100644 index c557335..0000000 --- a/Wishlist/Components/Account/Pages/ExternalLogin.razor +++ /dev/null @@ -1,201 +0,0 @@ -@page "/Account/ExternalLogin" - -@using System.ComponentModel.DataAnnotations -@using System.Security.Claims -@using System.Text -@using System.Text.Encodings.Web -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject SignInManager SignInManager -@inject UserManager UserManager -@inject IUserStore UserStore -@inject IEmailSender EmailSender -@inject NavigationManager NavigationManager -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Register - - -

Register

-

Associate your @ProviderDisplayName account.

-
- -
- You've successfully authenticated with @ProviderDisplayName. - Please enter an email address for this site below and click the Register button to finish - logging in. -
- -
-
- - - -
- - - -
- -
-
-
- -@code { - public const string LoginCallbackAction = "LoginCallback"; - - private string? message; - private ExternalLoginInfo? externalLoginInfo; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - [SupplyParameterFromQuery] private string? RemoteError { get; set; } - - [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } - - [SupplyParameterFromQuery] private string? Action { get; set; } - - private string? ProviderDisplayName => externalLoginInfo?.ProviderDisplayName; - - protected override async Task OnInitializedAsync() - { - if (RemoteError is not null) - { - RedirectManager.RedirectToWithStatus("Account/Login", $"Error from external provider: {RemoteError}", HttpContext); - } - - var info = await SignInManager.GetExternalLoginInfoAsync(); - if (info is null) - { - RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information.", HttpContext); - } - - externalLoginInfo = info; - - if (HttpMethods.IsGet(HttpContext.Request.Method)) - { - if (Action == LoginCallbackAction) - { - await OnLoginCallbackAsync(); - return; - } - - // We should only reach this page via the login callback, so redirect back to - // the login page if we get here some other way. - RedirectManager.RedirectTo("Account/Login"); - } - } - - private async Task OnLoginCallbackAsync() - { - if (externalLoginInfo is null) - { - RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information.", HttpContext); - } - - // Sign in the user with this external login provider if the user already has a login. - var result = await SignInManager.ExternalLoginSignInAsync( - externalLoginInfo.LoginProvider, - externalLoginInfo.ProviderKey, - isPersistent: false, - bypassTwoFactor: true); - - if (result.Succeeded) - { - Logger.LogInformation( - "{Name} logged in with {LoginProvider} provider.", - externalLoginInfo.Principal.Identity?.Name, - externalLoginInfo.LoginProvider); - RedirectManager.RedirectTo(ReturnUrl); - } - else if (result.IsLockedOut) - { - RedirectManager.RedirectTo("Account/Lockout"); - } - - // If the user does not have an account, then ask the user to create an account. - if (externalLoginInfo.Principal.HasClaim(c => c.Type == ClaimTypes.Email)) - { - Input.Email = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? ""; - } - } - - private async Task OnValidSubmitAsync() - { - if (externalLoginInfo is null) - { - RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information during confirmation.", HttpContext); - } - - var emailStore = GetEmailStore(); - 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, externalLoginInfo); - if (result.Succeeded) - { - Logger.LogInformation("User created an account using {Name} provider.", externalLoginInfo.LoginProvider); - - var userId = await UserManager.GetUserIdAsync(user); - var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); - code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); - - var callbackUrl = NavigationManager.GetUriWithQueryParameters( - NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, - new Dictionary { ["userId"] = userId, ["code"] = code }); - await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); - - // 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) - { - RedirectManager.RedirectTo("Account/RegisterConfirmation", new() { ["email"] = Input.Email }); - } - - await SignInManager.SignInAsync(user, isPersistent: false, externalLoginInfo.LoginProvider); - RedirectManager.RedirectTo(ReturnUrl); - } - } - - message = $"Error: {string.Join(",", result.Errors.Select(error => error.Description))}"; - } - - private User CreateUser() - { - try - { - return Activator.CreateInstance(); - } - catch - { - throw new InvalidOperationException($"Can't create an instance of '{nameof(User)}'. " + - $"Ensure that '{nameof(User)}' is not an abstract class and has a parameterless constructor"); - } - } - - private IUserEmailStore GetEmailStore() - { - if (!UserManager.SupportsUserEmail) - { - throw new NotSupportedException("The default UI requires a user store with email support."); - } - - return (IUserEmailStore)UserStore; - } - - private sealed class InputModel - { - [Required] [EmailAddress] public string Email { get; set; } = ""; - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/ForgotPassword.razor b/Wishlist/Components/Account/Pages/ForgotPassword.razor deleted file mode 100644 index c721a29..0000000 --- a/Wishlist/Components/Account/Pages/ForgotPassword.razor +++ /dev/null @@ -1,67 +0,0 @@ -@page "/Account/ForgotPassword" - -@using System.ComponentModel.DataAnnotations -@using System.Text -@using System.Text.Encodings.Web -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject IEmailSender EmailSender -@inject NavigationManager NavigationManager -@inject IdentityRedirectManager RedirectManager - -Forgot your password? - -

Forgot your password?

-

Enter your email.

-
-
-
- - - - -
- - - -
- -
-
-
- -@code { - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - private async Task OnValidSubmitAsync() - { - var user = await UserManager.FindByEmailAsync(Input.Email); - if (user is null || !(await UserManager.IsEmailConfirmedAsync(user))) - { - // Don't reveal that the user does not exist or is not confirmed - RedirectManager.RedirectTo("Account/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 = NavigationManager.GetUriWithQueryParameters( - NavigationManager.ToAbsoluteUri("Account/ResetPassword").AbsoluteUri, - new Dictionary { ["code"] = code }); - - await EmailSender.SendPasswordResetLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); - - RedirectManager.RedirectTo("Account/ForgotPasswordConfirmation"); - } - - private sealed class InputModel - { - [Required] [EmailAddress] public string Email { get; set; } = ""; - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/ForgotPasswordConfirmation.razor b/Wishlist/Components/Account/Pages/ForgotPasswordConfirmation.razor deleted file mode 100644 index 09a5161..0000000 --- a/Wishlist/Components/Account/Pages/ForgotPasswordConfirmation.razor +++ /dev/null @@ -1,8 +0,0 @@ -@page "/Account/ForgotPasswordConfirmation" - -Forgot password confirmation - -

Forgot password confirmation

-

- Please check your email to reset your password. -

\ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/InvalidPasswordReset.razor b/Wishlist/Components/Account/Pages/InvalidPasswordReset.razor deleted file mode 100644 index badd7ed..0000000 --- a/Wishlist/Components/Account/Pages/InvalidPasswordReset.razor +++ /dev/null @@ -1,8 +0,0 @@ -@page "/Account/InvalidPasswordReset" - -Invalid password reset - -

Invalid password reset

-

- The password reset link is invalid. -

\ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/InvalidUser.razor b/Wishlist/Components/Account/Pages/InvalidUser.razor deleted file mode 100644 index 1a5391c..0000000 --- a/Wishlist/Components/Account/Pages/InvalidUser.razor +++ /dev/null @@ -1,7 +0,0 @@ -@page "/Account/InvalidUser" - -Invalid user - -

Invalid user

- - \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Lockout.razor b/Wishlist/Components/Account/Pages/Lockout.razor deleted file mode 100644 index 886cabe..0000000 --- a/Wishlist/Components/Account/Pages/Lockout.razor +++ /dev/null @@ -1,8 +0,0 @@ -@page "/Account/Lockout" - -Locked out - -
-

Locked out

- -
\ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Login.razor b/Wishlist/Components/Account/Pages/Login.razor deleted file mode 100644 index b7b54cf..0000000 --- a/Wishlist/Components/Account/Pages/Login.razor +++ /dev/null @@ -1,124 +0,0 @@ -@page "/Account/Login" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Authentication -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject SignInManager SignInManager -@inject ILogger Logger -@inject NavigationManager NavigationManager -@inject IdentityRedirectManager RedirectManager - -Log in - -

Log in

-
-
-
- - - -

Use a local account to log in.

-
- -
- - - -
-
- - - -
-
- -
-
- -
- -
-
-
-
-
-

Use another service to log in.

-
- -
-
-
- -@code { - private string? errorMessage; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } - - protected override async Task OnInitializedAsync() - { - if (HttpMethods.IsGet(HttpContext.Request.Method)) - { - // Clear the existing external cookie to ensure a clean login process - await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); - } - } - - public async Task LoginUser() - { - // 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."); - RedirectManager.RedirectTo(ReturnUrl); - } - else if (result.RequiresTwoFactor) - { - RedirectManager.RedirectTo( - "Account/LoginWith2fa", - new() { ["returnUrl"] = ReturnUrl, ["rememberMe"] = Input.RememberMe }); - } - else if (result.IsLockedOut) - { - Logger.LogWarning("User account locked out."); - RedirectManager.RedirectTo("Account/Lockout"); - } - else - { - errorMessage = "Error: Invalid login attempt."; - } - } - - private sealed class InputModel - { - [Required] [EmailAddress] public string Email { get; set; } = ""; - - [Required] - [DataType(DataType.Password)] - public string Password { get; set; } = ""; - - [Display(Name = "Remember me?")] public bool RememberMe { get; set; } - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/LoginWith2fa.razor b/Wishlist/Components/Account/Pages/LoginWith2fa.razor deleted file mode 100644 index 29e9db9..0000000 --- a/Wishlist/Components/Account/Pages/LoginWith2fa.razor +++ /dev/null @@ -1,100 +0,0 @@ -@page "/Account/LoginWith2fa" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject SignInManager SignInManager -@inject UserManager UserManager -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Two-factor authentication - -

Two-factor authentication

-
- -

Your login is protected with an authenticator app. Enter your authenticator code below.

-
-
- - - - - -
- - - -
-
- -
-
- -
-
-
-
-

- Don't have access to your authenticator device? You can - log in with a recovery code. -

- -@code { - private string? message; - private User user = default!; - - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } - - [SupplyParameterFromQuery] private bool RememberMe { get; set; } - - protected override async Task OnInitializedAsync() - { - // Ensure the user has gone through the username & password screen first - user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ?? - throw new InvalidOperationException("Unable to load two-factor authentication user."); - } - - private async Task OnValidSubmitAsync() - { - 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.", userId); - RedirectManager.RedirectTo(ReturnUrl); - } - else if (result.IsLockedOut) - { - Logger.LogWarning("User with ID '{UserId}' account locked out.", userId); - RedirectManager.RedirectTo("Account/Lockout"); - } - else - { - Logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", userId); - message = "Error: Invalid authenticator code."; - } - } - - private sealed class InputModel - { - [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; } - - [Display(Name = "Remember this machine")] - public bool RememberMachine { get; set; } - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/LoginWithRecoveryCode.razor b/Wishlist/Components/Account/Pages/LoginWithRecoveryCode.razor deleted file mode 100644 index 35642fd..0000000 --- a/Wishlist/Components/Account/Pages/LoginWithRecoveryCode.razor +++ /dev/null @@ -1,85 +0,0 @@ -@page "/Account/LoginWithRecoveryCode" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject SignInManager SignInManager -@inject UserManager UserManager -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Recovery code verification - -

Recovery code verification

-
- -

- 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. -

-
-
- - - -
- - - -
- -
-
-
- -@code { - private string? message; - private User user = default!; - - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } - - protected override async Task OnInitializedAsync() - { - // Ensure the user has gone through the username & password screen first - user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ?? - throw new InvalidOperationException("Unable to load two-factor authentication user."); - } - - private async Task OnValidSubmitAsync() - { - 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.", userId); - RedirectManager.RedirectTo(ReturnUrl); - } - else if (result.IsLockedOut) - { - Logger.LogWarning("User account locked out."); - RedirectManager.RedirectTo("Account/Lockout"); - } - else - { - Logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", userId); - message = "Error: Invalid recovery code entered."; - } - } - - private sealed class InputModel - { - [Required] - [DataType(DataType.Text)] - [Display(Name = "Recovery Code")] - public string RecoveryCode { get; set; } = ""; - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/ChangePassword.razor b/Wishlist/Components/Account/Pages/Manage/ChangePassword.razor deleted file mode 100644 index c26644a..0000000 --- a/Wishlist/Components/Account/Pages/Manage/ChangePassword.razor +++ /dev/null @@ -1,96 +0,0 @@ -@page "/Account/Manage/ChangePassword" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Change password - -

Change password

- -
-
- - - -
- - - -
-
- - - -
-
- - - -
- -
-
-
- -@code { - private string? message; - private User user = default!; - private bool hasPassword; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - protected override async Task OnInitializedAsync() - { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); - hasPassword = await UserManager.HasPasswordAsync(user); - if (!hasPassword) - { - RedirectManager.RedirectTo("Account/Manage/SetPassword"); - } - } - - private async Task OnValidSubmitAsync() - { - var changePasswordResult = await UserManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword); - if (!changePasswordResult.Succeeded) - { - message = $"Error: {string.Join(",", changePasswordResult.Errors.Select(error => error.Description))}"; - return; - } - - await SignInManager.RefreshSignInAsync(user); - Logger.LogInformation("User changed their password successfully."); - - RedirectManager.RedirectToCurrentPageWithStatus("Your password has been changed", HttpContext); - } - - private sealed class InputModel - { - [Required] - [DataType(DataType.Password)] - [Display(Name = "Current password")] - public string OldPassword { get; set; } = ""; - - [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; } = ""; - - [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; } = ""; - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/DeletePersonalData.razor b/Wishlist/Components/Account/Pages/Manage/DeletePersonalData.razor deleted file mode 100644 index 5c129e0..0000000 --- a/Wishlist/Components/Account/Pages/Manage/DeletePersonalData.razor +++ /dev/null @@ -1,85 +0,0 @@ -@page "/Account/Manage/DeletePersonalData" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Delete Personal Data - - - -

Delete Personal Data

- - - -
- - - - @if (requirePassword) - { -
- - - -
- } - -
-
- -@code { - private string? message; - private User user = default!; - private bool requirePassword; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - protected override async Task OnInitializedAsync() - { - Input ??= new(); - user = await UserAccessor.GetRequiredUserAsync(HttpContext); - requirePassword = await UserManager.HasPasswordAsync(user); - } - - private async Task OnValidSubmitAsync() - { - if (requirePassword && !await UserManager.CheckPasswordAsync(user, Input.Password)) - { - message = "Error: Incorrect password."; - return; - } - - var result = await UserManager.DeleteAsync(user); - if (!result.Succeeded) - { - throw new InvalidOperationException("Unexpected error occurred deleting user."); - } - - await SignInManager.SignOutAsync(); - - var userId = await UserManager.GetUserIdAsync(user); - Logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId); - - RedirectManager.RedirectToCurrentPage(); - } - - private sealed class InputModel - { - [DataType(DataType.Password)] public string Password { get; set; } = ""; - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/Disable2fa.razor b/Wishlist/Components/Account/Pages/Manage/Disable2fa.razor deleted file mode 100644 index ea407fd..0000000 --- a/Wishlist/Components/Account/Pages/Manage/Disable2fa.razor +++ /dev/null @@ -1,65 +0,0 @@ -@page "/Account/Manage/Disable2fa" - -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject IdentityUserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Disable two-factor authentication (2FA) - - -

Disable two-factor authentication (2FA)

- - - -
-
- - - -
- -@code { - private User user = default!; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - protected override async Task OnInitializedAsync() - { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); - - if (HttpMethods.IsGet(HttpContext.Request.Method) && !await UserManager.GetTwoFactorEnabledAsync(user)) - { - throw new InvalidOperationException("Cannot disable 2FA for user as it's not currently enabled."); - } - } - - private async Task OnSubmitAsync() - { - var disable2faResult = await UserManager.SetTwoFactorEnabledAsync(user, false); - if (!disable2faResult.Succeeded) - { - throw new InvalidOperationException("Unexpected error occurred disabling 2FA."); - } - - var userId = await UserManager.GetUserIdAsync(user); - Logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", userId); - RedirectManager.RedirectToWithStatus( - "Account/Manage/TwoFactorAuthentication", - "2fa has been disabled. You can reenable 2fa when you setup an authenticator app", - HttpContext); - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/Email.razor b/Wishlist/Components/Account/Pages/Manage/Email.razor deleted file mode 100644 index bca6c95..0000000 --- a/Wishlist/Components/Account/Pages/Manage/Email.razor +++ /dev/null @@ -1,124 +0,0 @@ -@page "/Account/Manage/Email" - -@using System.ComponentModel.DataAnnotations -@using System.Text -@using System.Text.Encodings.Web -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject IEmailSender EmailSender -@inject IdentityUserAccessor UserAccessor -@inject NavigationManager NavigationManager - -Manage email - -

Manage email

- - -
-
-
- - - - - - @if (isEmailConfirmed) - { -
- -
- -
- -
- } - else - { -
- - - -
- } -
- - - -
- -
-
-
- -@code { - private string? message; - private User user = default!; - private string? email; - private bool isEmailConfirmed; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromForm(FormName = "change-email")] - private InputModel Input { get; set; } = new(); - - protected override async Task OnInitializedAsync() - { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); - email = await UserManager.GetEmailAsync(user); - isEmailConfirmed = await UserManager.IsEmailConfirmedAsync(user); - - Input.NewEmail ??= email; - } - - private async Task OnValidSubmitAsync() - { - if (Input.NewEmail is null || Input.NewEmail == email) - { - message = "Your email is unchanged."; - return; - } - - var userId = await UserManager.GetUserIdAsync(user); - var code = await UserManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail); - code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); - var callbackUrl = NavigationManager.GetUriWithQueryParameters( - NavigationManager.ToAbsoluteUri("Account/ConfirmEmailChange").AbsoluteUri, - new Dictionary { ["userId"] = userId, ["email"] = Input.NewEmail, ["code"] = code }); - - await EmailSender.SendConfirmationLinkAsync(user, Input.NewEmail, HtmlEncoder.Default.Encode(callbackUrl)); - - message = "Confirmation link to change email sent. Please check your email."; - } - - private async Task OnSendEmailVerificationAsync() - { - if (email is null) - { - return; - } - - var userId = await UserManager.GetUserIdAsync(user); - var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); - code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); - var callbackUrl = NavigationManager.GetUriWithQueryParameters( - NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, - new Dictionary { ["userId"] = userId, ["code"] = code }); - - await EmailSender.SendConfirmationLinkAsync(user, email, HtmlEncoder.Default.Encode(callbackUrl)); - - message = "Verification email sent. Please check your email."; - } - - private sealed class InputModel - { - [Required] - [EmailAddress] - [Display(Name = "New email")] - public string? NewEmail { get; set; } - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/EnableAuthenticator.razor b/Wishlist/Components/Account/Pages/Manage/EnableAuthenticator.razor deleted file mode 100644 index c2a199b..0000000 --- a/Wishlist/Components/Account/Pages/Manage/EnableAuthenticator.razor +++ /dev/null @@ -1,173 +0,0 @@ -@page "/Account/Manage/EnableAuthenticator" - -@using System.ComponentModel.DataAnnotations -@using System.Globalization -@using System.Text -@using System.Text.Encodings.Web -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject IdentityUserAccessor UserAccessor -@inject UrlEncoder UrlEncoder -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Configure authenticator app - -@if (recoveryCodes is not null) -{ - -} -else -{ - -

Configure authenticator app

-
-

To use an authenticator app go through the following steps:

-
    -
  1. -

    - Download a two-factor authenticator app like Microsoft Authenticator for - Android and - iOS or - Google Authenticator for - Android and - iOS. -

    -
  2. -
  3. -

    Scan the QR Code or enter this key @sharedKey into your two factor authenticator app. Spaces and casing do not matter.

    - -
    -
    -
  4. -
  5. -

    - 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. -

    -
    -
    - - -
    - - - -
    - - -
    -
    -
    -
  6. -
-
-} - -@code { - private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; - - private string? message; - private User user = default!; - private string? sharedKey; - private string? authenticatorUri; - private IEnumerable? recoveryCodes; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - protected override async Task OnInitializedAsync() - { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); - - await LoadSharedKeyAndQrCodeUriAsync(user); - } - - private async Task OnValidSubmitAsync() - { - // 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) - { - message = "Error: Verification code is invalid."; - return; - } - - 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); - - message = "Your authenticator app has been verified."; - - if (await UserManager.CountRecoveryCodesAsync(user) == 0) - { - recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); - } - else - { - RedirectManager.RedirectToWithStatus("Account/Manage/TwoFactorAuthentication", message, HttpContext); - } - } - - private async ValueTask LoadSharedKeyAndQrCodeUriAsync(User 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); - } - - private sealed class InputModel - { - [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; } = ""; - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/ExternalLogins.razor b/Wishlist/Components/Account/Pages/Manage/ExternalLogins.razor deleted file mode 100644 index 3eac9d1..0000000 --- a/Wishlist/Components/Account/Pages/Manage/ExternalLogins.razor +++ /dev/null @@ -1,138 +0,0 @@ -@page "/Account/Manage/ExternalLogins" - -@using Microsoft.AspNetCore.Authentication -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor -@inject IUserStore UserStore -@inject IdentityRedirectManager RedirectManager - -Manage your external logins - - -@if (currentLogins?.Count > 0) -{ -

Registered Logins

- - - @foreach (var login in currentLogins) - { - - - - - } - -
@login.ProviderDisplayName - @if (showRemoveButton) - { -
- -
- - - -
- - } - else - { - @:   - } -
-} -@if (otherLogins?.Count > 0) -{ -

Add another service to log in.

-
-
- -
-

- @foreach (var provider in otherLogins) - { - - } -

-
- -} - -@code { - public const string LinkLoginCallbackAction = "LinkLoginCallback"; - - private User user = default!; - private IList? currentLogins; - private IList? otherLogins; - private bool showRemoveButton; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromForm] private string? LoginProvider { get; set; } - - [SupplyParameterFromForm] private string? ProviderKey { get; set; } - - [SupplyParameterFromQuery] private string? Action { get; set; } - - protected override async Task OnInitializedAsync() - { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); - currentLogins = await UserManager.GetLoginsAsync(user); - otherLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()) - .Where(auth => currentLogins.All(ul => auth.Name != ul.LoginProvider)) - .ToList(); - - string? passwordHash = null; - if (UserStore is IUserPasswordStore userPasswordStore) - { - passwordHash = await userPasswordStore.GetPasswordHashAsync(user, HttpContext.RequestAborted); - } - - showRemoveButton = passwordHash is not null || currentLogins.Count > 1; - - if (HttpMethods.IsGet(HttpContext.Request.Method) && Action == LinkLoginCallbackAction) - { - await OnGetLinkLoginCallbackAsync(); - } - } - - private async Task OnSubmitAsync() - { - var result = await UserManager.RemoveLoginAsync(user, LoginProvider!, ProviderKey!); - if (!result.Succeeded) - { - RedirectManager.RedirectToCurrentPageWithStatus("Error: The external login was not removed.", HttpContext); - } - - await SignInManager.RefreshSignInAsync(user); - RedirectManager.RedirectToCurrentPageWithStatus("The external login was removed.", HttpContext); - } - - private async Task OnGetLinkLoginCallbackAsync() - { - var userId = await UserManager.GetUserIdAsync(user); - var info = await SignInManager.GetExternalLoginInfoAsync(userId); - if (info is null) - { - RedirectManager.RedirectToCurrentPageWithStatus("Error: Could not load external login info.", HttpContext); - } - - var result = await UserManager.AddLoginAsync(user, info); - if (!result.Succeeded) - { - RedirectManager.RedirectToCurrentPageWithStatus("Error: The external login was not added. External logins can only be associated with one account.", HttpContext); - } - - // Clear the existing external cookie to ensure a clean login process - await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); - - RedirectManager.RedirectToCurrentPageWithStatus("The external login was added.", HttpContext); - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor b/Wishlist/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor deleted file mode 100644 index 92ee800..0000000 --- a/Wishlist/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor +++ /dev/null @@ -1,69 +0,0 @@ -@page "/Account/Manage/GenerateRecoveryCodes" - -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject IdentityUserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Generate two-factor authentication (2FA) recovery codes - -@if (recoveryCodes is not null) -{ - -} -else -{ -

Generate two-factor authentication (2FA) recovery codes

- -
-
- - - -
-} - -@code { - private string? message; - private User user = default!; - private IEnumerable? recoveryCodes; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - protected override async Task OnInitializedAsync() - { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); - - var isTwoFactorEnabled = await UserManager.GetTwoFactorEnabledAsync(user); - if (!isTwoFactorEnabled) - { - throw new InvalidOperationException("Cannot generate recovery codes for user because they do not have 2FA enabled."); - } - } - - private async Task OnSubmitAsync() - { - var userId = await UserManager.GetUserIdAsync(user); - recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); - message = "You have generated new recovery codes."; - - Logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId); - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/Index.razor b/Wishlist/Components/Account/Pages/Manage/Index.razor deleted file mode 100644 index eeae158..0000000 --- a/Wishlist/Components/Account/Pages/Manage/Index.razor +++ /dev/null @@ -1,77 +0,0 @@ -@page "/Account/Manage" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager - -Profile - -

Profile

- - -
-
- - - -
- - -
-
- - - -
- -
-
-
- -@code { - private User user = default!; - private string? username; - private string? phoneNumber; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - protected override async Task OnInitializedAsync() - { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); - username = await UserManager.GetUserNameAsync(user); - phoneNumber = await UserManager.GetPhoneNumberAsync(user); - - Input.PhoneNumber ??= phoneNumber; - } - - private async Task OnValidSubmitAsync() - { - if (Input.PhoneNumber != phoneNumber) - { - var setPhoneResult = await UserManager.SetPhoneNumberAsync(user, Input.PhoneNumber); - if (!setPhoneResult.Succeeded) - { - RedirectManager.RedirectToCurrentPageWithStatus("Error: Failed to set phone number.", HttpContext); - } - } - - await SignInManager.RefreshSignInAsync(user); - RedirectManager.RedirectToCurrentPageWithStatus("Your profile has been updated", HttpContext); - } - - private sealed class InputModel - { - [Phone] - [Display(Name = "Phone number")] - public string? PhoneNumber { get; set; } - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/PersonalData.razor b/Wishlist/Components/Account/Pages/Manage/PersonalData.razor deleted file mode 100644 index 1631b24..0000000 --- a/Wishlist/Components/Account/Pages/Manage/PersonalData.razor +++ /dev/null @@ -1,34 +0,0 @@ -@page "/Account/Manage/PersonalData" - -@inject IdentityUserAccessor UserAccessor - -Personal Data - - -

Personal Data

- -
-
-

Your account contains personal data that you have given us. This page allows you to download or delete that data.

-

- Deleting this data will permanently remove your account, and this cannot be recovered. -

-
- - - -

- Delete -

-
-
- -@code { - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - protected override async Task OnInitializedAsync() - { - _ = await UserAccessor.GetRequiredUserAsync(HttpContext); - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/ResetAuthenticator.razor b/Wishlist/Components/Account/Pages/Manage/ResetAuthenticator.razor deleted file mode 100644 index a84c227..0000000 --- a/Wishlist/Components/Account/Pages/Manage/ResetAuthenticator.razor +++ /dev/null @@ -1,53 +0,0 @@ -@page "/Account/Manage/ResetAuthenticator" - -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Reset authenticator key - - -

Reset authenticator key

- -
-
- - - -
- -@code { - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - private async Task OnSubmitAsync() - { - var user = await UserAccessor.GetRequiredUserAsync(HttpContext); - 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.", userId); - - await SignInManager.RefreshSignInAsync(user); - - RedirectManager.RedirectToWithStatus( - "Account/Manage/EnableAuthenticator", - "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.", - HttpContext); - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/SetPassword.razor b/Wishlist/Components/Account/Pages/Manage/SetPassword.razor deleted file mode 100644 index 7464715..0000000 --- a/Wishlist/Components/Account/Pages/Manage/SetPassword.razor +++ /dev/null @@ -1,87 +0,0 @@ -@page "/Account/Manage/SetPassword" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager - -Set password - -

Set your password

- -

- You do not have a local username/password for this site. Add a local - account so you can log in without an external login. -

-
-
- - - -
- - - -
-
- - - -
- -
-
-
- -@code { - private string? message; - private User user = default!; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - protected override async Task OnInitializedAsync() - { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); - - var hasPassword = await UserManager.HasPasswordAsync(user); - if (hasPassword) - { - RedirectManager.RedirectTo("Account/Manage/ChangePassword"); - } - } - - private async Task OnValidSubmitAsync() - { - var addPasswordResult = await UserManager.AddPasswordAsync(user, Input.NewPassword!); - if (!addPasswordResult.Succeeded) - { - message = $"Error: {string.Join(",", addPasswordResult.Errors.Select(error => error.Description))}"; - return; - } - - await SignInManager.RefreshSignInAsync(user); - RedirectManager.RedirectToCurrentPageWithStatus("Your password has been set.", HttpContext); - } - - private sealed class InputModel - { - [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; } - - [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; } - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/TwoFactorAuthentication.razor b/Wishlist/Components/Account/Pages/Manage/TwoFactorAuthentication.razor deleted file mode 100644 index f5840c5..0000000 --- a/Wishlist/Components/Account/Pages/Manage/TwoFactorAuthentication.razor +++ /dev/null @@ -1,102 +0,0 @@ -@page "/Account/Manage/TwoFactorAuthentication" - -@using Microsoft.AspNetCore.Http.Features -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager - -Two-factor authentication (2FA) - - -

Two-factor authentication (2FA)

-@if (canTrack) -{ - if (is2faEnabled) - { - if (recoveryCodesLeft == 0) - { -
- You have no recovery codes left. -

You must generate a new set of recovery codes before you can log in with a recovery code.

-
- } - else if (recoveryCodesLeft == 1) - { -
- You have 1 recovery code left. -

You can generate a new set of recovery codes.

-
- } - else if (recoveryCodesLeft <= 3) - { -
- You have @recoveryCodesLeft recovery codes left. -

You should generate a new set of recovery codes.

-
- } - - if (isMachineRemembered) - { -
- - - - } - - Disable 2FA - Reset recovery codes - } - -

Authenticator app

- @if (!hasAuthenticator) - { - Add authenticator app - } - else - { - Set up authenticator app - Reset authenticator app - } -} -else -{ -
- Privacy and cookie policy have not been accepted. -

You must accept the policy before you can enable two factor authentication.

-
-} - -@code { - private bool canTrack; - private bool hasAuthenticator; - private int recoveryCodesLeft; - private bool is2faEnabled; - private bool isMachineRemembered; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - protected override async Task OnInitializedAsync() - { - var user = await UserAccessor.GetRequiredUserAsync(HttpContext); - canTrack = HttpContext.Features.Get()?.CanTrack ?? true; - hasAuthenticator = await UserManager.GetAuthenticatorKeyAsync(user) is not null; - is2faEnabled = await UserManager.GetTwoFactorEnabledAsync(user); - isMachineRemembered = await SignInManager.IsTwoFactorClientRememberedAsync(user); - recoveryCodesLeft = await UserManager.CountRecoveryCodesAsync(user); - } - - private async Task OnSubmitForgetBrowserAsync() - { - await SignInManager.ForgetTwoFactorClientAsync(); - - RedirectManager.RedirectToCurrentPageWithStatus( - "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code.", - HttpContext); - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Manage/_Imports.razor b/Wishlist/Components/Account/Pages/Manage/_Imports.razor deleted file mode 100644 index 0750910..0000000 --- a/Wishlist/Components/Account/Pages/Manage/_Imports.razor +++ /dev/null @@ -1,2 +0,0 @@ -@layout ManageLayout -@attribute [Microsoft.AspNetCore.Authorization.Authorize] \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/Register.razor b/Wishlist/Components/Account/Pages/Register.razor deleted file mode 100644 index c477aef..0000000 --- a/Wishlist/Components/Account/Pages/Register.razor +++ /dev/null @@ -1,146 +0,0 @@ -@page "/Account/Register" - -@using System.ComponentModel.DataAnnotations -@using System.Text -@using System.Text.Encodings.Web -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject IUserStore UserStore -@inject SignInManager SignInManager -@inject IEmailSender EmailSender -@inject ILogger Logger -@inject NavigationManager NavigationManager -@inject IdentityRedirectManager RedirectManager - -Register - -

Register

- -
-
- - - -

Create a new account.

-
- -
- - - -
-
- - - -
-
- - - -
- -
-
-
-
-

Use another service to register.

-
- -
-
-
- -@code { - private IEnumerable? identityErrors; - - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } - - private string? Message => identityErrors is null ? null : $"Error: {string.Join(", ", identityErrors.Select(error => error.Description))}"; - - public async Task RegisterUser(EditContext editContext) - { - var user = CreateUser(); - - await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); - var emailStore = GetEmailStore(); - await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); - var result = await UserManager.CreateAsync(user, Input.Password); - - if (!result.Succeeded) - { - identityErrors = result.Errors; - return; - } - - 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 = NavigationManager.GetUriWithQueryParameters( - NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, - new Dictionary { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl }); - - await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); - - if (UserManager.Options.SignIn.RequireConfirmedAccount) - { - RedirectManager.RedirectTo( - "Account/RegisterConfirmation", - new() { ["email"] = Input.Email, ["returnUrl"] = ReturnUrl }); - } - - await SignInManager.SignInAsync(user, isPersistent: false); - RedirectManager.RedirectTo(ReturnUrl); - } - - private User CreateUser() - { - try - { - return Activator.CreateInstance(); - } - catch - { - throw new InvalidOperationException($"Can't create an instance of '{nameof(User)}'. " + - $"Ensure that '{nameof(User)}' is not an abstract class and has a parameterless constructor."); - } - } - - private IUserEmailStore GetEmailStore() - { - if (!UserManager.SupportsUserEmail) - { - throw new NotSupportedException("The default UI requires a user store with email support."); - } - - return (IUserEmailStore)UserStore; - } - - private sealed class InputModel - { - [Required] - [EmailAddress] - [Display(Name = "Email")] - public string Email { get; set; } = ""; - - [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; } = ""; - - [DataType(DataType.Password)] - [Display(Name = "Confirm password")] - [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] - public string ConfirmPassword { get; set; } = ""; - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/RegisterConfirmation.razor b/Wishlist/Components/Account/Pages/RegisterConfirmation.razor deleted file mode 100644 index 4ff27e5..0000000 --- a/Wishlist/Components/Account/Pages/RegisterConfirmation.razor +++ /dev/null @@ -1,67 +0,0 @@ -@page "/Account/RegisterConfirmation" - -@using System.Text -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject IEmailSender EmailSender -@inject NavigationManager NavigationManager -@inject IdentityRedirectManager RedirectManager - -Register confirmation - -

Register confirmation

- - - -@if (emailConfirmationLink is not null) -{ -

- This app does not currently have a real email sender registered, see these docs for how to configure a real email sender. - Normally this would be emailed: Click here to confirm your account -

-} -else -{ -

Please check your email to confirm your account.

-} - -@code { - private string? emailConfirmationLink; - private string? statusMessage; - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromQuery] private string? Email { get; set; } - - [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } - - protected override async Task OnInitializedAsync() - { - if (Email is null) - { - RedirectManager.RedirectTo(""); - } - - var user = await UserManager.FindByEmailAsync(Email); - if (user is null) - { - HttpContext.Response.StatusCode = StatusCodes.Status404NotFound; - statusMessage = "Error finding user for unspecified email"; - } - else if (EmailSender is IdentityNoOpEmailSender) - { - // Once you add a real email sender, you should remove this code that lets you confirm the account - var userId = await UserManager.GetUserIdAsync(user); - var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); - code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); - emailConfirmationLink = NavigationManager.GetUriWithQueryParameters( - NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, - new Dictionary { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl }); - } - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/ResendEmailConfirmation.razor b/Wishlist/Components/Account/Pages/ResendEmailConfirmation.razor deleted file mode 100644 index c90cb07..0000000 --- a/Wishlist/Components/Account/Pages/ResendEmailConfirmation.razor +++ /dev/null @@ -1,67 +0,0 @@ -@page "/Account/ResendEmailConfirmation" - -@using System.ComponentModel.DataAnnotations -@using System.Text -@using System.Text.Encodings.Web -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject UserManager UserManager -@inject IEmailSender EmailSender -@inject NavigationManager NavigationManager -@inject IdentityRedirectManager RedirectManager - -Resend email confirmation - -

Resend email confirmation

-

Enter your email.

-
- -
-
- - - -
- - - -
- -
-
-
- -@code { - private string? message; - - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - private async Task OnValidSubmitAsync() - { - var user = await UserManager.FindByEmailAsync(Input.Email!); - if (user is null) - { - message = "Verification email sent. Please check your email."; - return; - } - - var userId = await UserManager.GetUserIdAsync(user); - var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); - code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); - var callbackUrl = NavigationManager.GetUriWithQueryParameters( - NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, - new Dictionary { ["userId"] = userId, ["code"] = code }); - await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); - - message = "Verification email sent. Please check your email."; - } - - private sealed class InputModel - { - [Required] [EmailAddress] public string Email { get; set; } = ""; - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/ResetPassword.razor b/Wishlist/Components/Account/Pages/ResetPassword.razor deleted file mode 100644 index 8879d4d..0000000 --- a/Wishlist/Components/Account/Pages/ResetPassword.razor +++ /dev/null @@ -1,100 +0,0 @@ -@page "/Account/ResetPassword" - -@using System.ComponentModel.DataAnnotations -@using System.Text -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject IdentityRedirectManager RedirectManager -@inject UserManager UserManager - -Reset password - -

Reset password

-

Reset your password.

-
-
-
- - - - - - -
- - - -
-
- - - -
-
- - - -
- -
-
-
- -@code { - private IEnumerable? identityErrors; - - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - - [SupplyParameterFromQuery] private string? Code { get; set; } - - private string? Message => identityErrors is null ? null : $"Error: {string.Join(", ", identityErrors.Select(error => error.Description))}"; - - protected override void OnInitialized() - { - if (Code is null) - { - RedirectManager.RedirectTo("Account/InvalidPasswordReset"); - } - - Input.Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); - } - - private async Task OnValidSubmitAsync() - { - var user = await UserManager.FindByEmailAsync(Input.Email); - if (user is null) - { - // Don't reveal that the user does not exist - RedirectManager.RedirectTo("Account/ResetPasswordConfirmation"); - } - - var result = await UserManager.ResetPasswordAsync(user, Input.Code, Input.Password); - if (result.Succeeded) - { - RedirectManager.RedirectTo("Account/ResetPasswordConfirmation"); - } - - identityErrors = result.Errors; - } - - private sealed class InputModel - { - [Required] [EmailAddress] public string Email { get; set; } = ""; - - [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; } = ""; - - [DataType(DataType.Password)] - [Display(Name = "Confirm password")] - [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] - public string ConfirmPassword { get; set; } = ""; - - [Required] public string Code { get; set; } = ""; - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/ResetPasswordConfirmation.razor b/Wishlist/Components/Account/Pages/ResetPasswordConfirmation.razor deleted file mode 100644 index 479923f..0000000 --- a/Wishlist/Components/Account/Pages/ResetPasswordConfirmation.razor +++ /dev/null @@ -1,7 +0,0 @@ -@page "/Account/ResetPasswordConfirmation" -Reset password confirmation - -

Reset password confirmation

-

- Your password has been reset. Please click here to log in. -

\ No newline at end of file diff --git a/Wishlist/Components/Account/Pages/_Imports.razor b/Wishlist/Components/Account/Pages/_Imports.razor deleted file mode 100644 index 1125ee1..0000000 --- a/Wishlist/Components/Account/Pages/_Imports.razor +++ /dev/null @@ -1,2 +0,0 @@ -@using Wishlist.Components.Account.Shared -@attribute [ExcludeFromInteractiveRouting] \ No newline at end of file diff --git a/Wishlist/Components/Account/Shared/ExternalLoginPicker.razor b/Wishlist/Components/Account/Shared/ExternalLoginPicker.razor deleted file mode 100644 index 2b612b7..0000000 --- a/Wishlist/Components/Account/Shared/ExternalLoginPicker.razor +++ /dev/null @@ -1,47 +0,0 @@ -@using Microsoft.AspNetCore.Authentication -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject SignInManager SignInManager -@inject IdentityRedirectManager RedirectManager - -@if (externalLogins.Length == 0) -{ -
-

- There are no external authentication services configured. See this - - article - about setting up this ASP.NET application to support logging in via external services - . -

-
-} -else -{ -
-
- - -

- @foreach (var provider in externalLogins) - { - - } -

-
-
-} - -@code { - private AuthenticationScheme[] externalLogins = []; - - [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } - - protected override async Task OnInitializedAsync() - { - externalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToArray(); - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Shared/ManageLayout.razor b/Wishlist/Components/Account/Shared/ManageLayout.razor deleted file mode 100644 index 4bfae6a..0000000 --- a/Wishlist/Components/Account/Shared/ManageLayout.razor +++ /dev/null @@ -1,17 +0,0 @@ -@inherits LayoutComponentBase -@layout Wishlist.Components.Layout.MainLayout - -

Manage your account

- -
-

Change your account settings

-
-
-
- -
-
- @Body -
-
-
\ No newline at end of file diff --git a/Wishlist/Components/Account/Shared/ManageNavMenu.razor b/Wishlist/Components/Account/Shared/ManageNavMenu.razor deleted file mode 100644 index febeb6a..0000000 --- a/Wishlist/Components/Account/Shared/ManageNavMenu.razor +++ /dev/null @@ -1,39 +0,0 @@ -@using Microsoft.AspNetCore.Identity -@using Wishlist.Data -@using Wishlist.Data.Entities - -@inject SignInManager SignInManager - - - -@code { - private bool hasExternalLogins; - - protected override async Task OnInitializedAsync() - { - hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Shared/RedirectToLogin.razor b/Wishlist/Components/Account/Shared/RedirectToLogin.razor deleted file mode 100644 index fc9df01..0000000 --- a/Wishlist/Components/Account/Shared/RedirectToLogin.razor +++ /dev/null @@ -1,10 +0,0 @@ -@inject NavigationManager NavigationManager - -@code { - - protected override void OnInitialized() - { - NavigationManager.NavigateTo($"Account/Login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true); - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Shared/ShowRecoveryCodes.razor b/Wishlist/Components/Account/Shared/ShowRecoveryCodes.razor deleted file mode 100644 index 9b862ed..0000000 --- a/Wishlist/Components/Account/Shared/ShowRecoveryCodes.razor +++ /dev/null @@ -1,26 +0,0 @@ - -

Recovery codes

- -
-
- @foreach (var recoveryCode in RecoveryCodes) - { -
- @recoveryCode -
- } -
-
- -@code { - [Parameter] public string[] RecoveryCodes { get; set; } = []; - - [Parameter] public string? StatusMessage { get; set; } -} \ No newline at end of file diff --git a/Wishlist/Components/Account/Shared/StatusMessage.razor b/Wishlist/Components/Account/Shared/StatusMessage.razor deleted file mode 100644 index bdee181..0000000 --- a/Wishlist/Components/Account/Shared/StatusMessage.razor +++ /dev/null @@ -1,28 +0,0 @@ -@if (!string.IsNullOrEmpty(DisplayMessage)) -{ - var statusMessageClass = DisplayMessage.StartsWith("Error") ? "danger" : "success"; - -} - -@code { - private string? messageFromCookie; - - [Parameter] public string? Message { get; set; } - - [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - - private string? DisplayMessage => Message ?? messageFromCookie; - - protected override void OnInitialized() - { - messageFromCookie = HttpContext.Request.Cookies[IdentityRedirectManager.StatusCookieName]; - - if (messageFromCookie is not null) - { - HttpContext.Response.Cookies.Delete(IdentityRedirectManager.StatusCookieName); - } - } - -} \ No newline at end of file diff --git a/Wishlist/Components/Pages/Auth.razor b/Wishlist/Components/Pages/Auth.razor deleted file mode 100644 index d43f463..0000000 --- a/Wishlist/Components/Pages/Auth.razor +++ /dev/null @@ -1,13 +0,0 @@ -@page "/auth" - -@using Microsoft.AspNetCore.Authorization - -@attribute [Authorize] - -Auth - -

You are authenticated

- - - Hello @context.User.Identity?.Name! - \ No newline at end of file diff --git a/Wishlist/Components/Routes.razor b/Wishlist/Components/Routes.razor index 8a4a77f..ae94e9e 100644 --- a/Wishlist/Components/Routes.razor +++ b/Wishlist/Components/Routes.razor @@ -1,11 +1,6 @@ -@using Wishlist.Components.Account.Shared - + - - - - - + \ No newline at end of file diff --git a/Wishlist/Components/_Imports.razor b/Wishlist/Components/_Imports.razor index 4160c06..fa47ce0 100644 --- a/Wishlist/Components/_Imports.razor +++ b/Wishlist/Components/_Imports.razor @@ -1,6 +1,5 @@ @using System.Net.Http @using System.Net.Http.Json -@using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web diff --git a/Wishlist/Data/AppDataContext.cs b/Wishlist/Data/AppDataContext.cs deleted file mode 100644 index 06abc26..0000000 --- a/Wishlist/Data/AppDataContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Wishlist.Data.Entities; - -namespace Wishlist.Data; - -public class AppDataContext : IdentityDbContext -{ - -} \ No newline at end of file diff --git a/Wishlist/Data/Entities/User.cs b/Wishlist/Data/Entities/User.cs deleted file mode 100644 index 255606b..0000000 --- a/Wishlist/Data/Entities/User.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Wishlist.Data.Entities; - -public class User : IdentityUser -{ - -} \ No newline at end of file diff --git a/Wishlist/Program.cs b/Wishlist/Program.cs index 76b64a3..71202d3 100644 --- a/Wishlist/Program.cs +++ b/Wishlist/Program.cs @@ -1,10 +1,4 @@ -using Wishlist.Components.Account; -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; using Wishlist.Components; -using Wishlist.Data; -using Wishlist.Data.Entities; namespace Wishlist; @@ -17,40 +11,11 @@ public class Program // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); - - builder.Services.AddCascadingAuthenticationState(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddAuthentication(options => - { - options.DefaultScheme = IdentityConstants.ApplicationScheme; - options.DefaultSignInScheme = IdentityConstants.ExternalScheme; - }) - .AddIdentityCookies(); - - var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? - throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); - builder.Services.AddDbContext(options => - options.UseNpgsql(connectionString)); - builder.Services.AddDatabaseDeveloperPageExceptionFilter(); - - builder.Services.AddIdentityCore(options => options.SignIn.RequireConfirmedAccount = true) - .AddEntityFrameworkStores() - .AddSignInManager() - .AddDefaultTokenProviders(); - - builder.Services.AddSingleton, IdentityNoOpEmailSender>(); - var app = builder.Build(); // Configure the HTTP request pipeline. - if (app.Environment.IsDevelopment()) - { - app.UseMigrationsEndPoint(); - } - else + if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. @@ -65,9 +30,6 @@ public class Program app.MapRazorComponents() .AddInteractiveServerRenderMode(); - // Add additional endpoints required by the Identity /Account Razor components. - app.MapAdditionalIdentityEndpoints(); - app.Run(); } } \ No newline at end of file diff --git a/Wishlist/Wishlist.csproj b/Wishlist/Wishlist.csproj index ff2846c..4e0ef6a 100644 --- a/Wishlist/Wishlist.csproj +++ b/Wishlist/Wishlist.csproj @@ -4,17 +4,9 @@ net9.0 enable enable - aspnet-Wishlist-2201de5f-c376-47e0-b8b3-9e7b804ccdeb Linux - - - - - - - .dockerignore