diff --git a/WatchLog/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs b/WatchLog/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs deleted file mode 100644 index 4f65234..0000000 --- a/WatchLog/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs +++ /dev/null @@ -1,113 +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 WatchLog.Components.Account.Pages; -using WatchLog.Components.Account.Pages.Manage; -using WatchLog.Data; - -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(ApplicationUser).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; - } - } -} diff --git a/WatchLog/Components/Account/IdentityNoOpEmailSender.cs b/WatchLog/Components/Account/IdentityNoOpEmailSender.cs deleted file mode 100644 index 3c7539c..0000000 --- a/WatchLog/Components/Account/IdentityNoOpEmailSender.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.UI.Services; -using WatchLog.Data; - -namespace WatchLog.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(ApplicationUser user, string email, string confirmationLink) => - emailSender.SendEmailAsync(email, "Confirm your email", $"Please confirm your account by clicking here."); - - public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) => - emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password by clicking here."); - - public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) => - emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password using the following code: {resetCode}"); - } -} diff --git a/WatchLog/Components/Account/IdentityRedirectManager.cs b/WatchLog/Components/Account/IdentityRedirectManager.cs deleted file mode 100644 index f9707db..0000000 --- a/WatchLog/Components/Account/IdentityRedirectManager.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Components; - -namespace WatchLog.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); - } -} diff --git a/WatchLog/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs b/WatchLog/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs deleted file mode 100644 index 86b1ecb..0000000 --- a/WatchLog/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs +++ /dev/null @@ -1,48 +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 WatchLog.Data; - -namespace WatchLog.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; - } - } - } -} diff --git a/WatchLog/Components/Account/IdentityUserAccessor.cs b/WatchLog/Components/Account/IdentityUserAccessor.cs deleted file mode 100644 index d834fb7..0000000 --- a/WatchLog/Components/Account/IdentityUserAccessor.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using WatchLog.Data; - -namespace WatchLog.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; - } - } -} diff --git a/WatchLog/Components/Account/Pages/AccessDenied.razor b/WatchLog/Components/Account/Pages/AccessDenied.razor deleted file mode 100644 index 905dec3..0000000 --- a/WatchLog/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.

-
diff --git a/WatchLog/Components/Account/Pages/ConfirmEmail.razor b/WatchLog/Components/Account/Pages/ConfirmEmail.razor deleted file mode 100644 index 0f79513..0000000 --- a/WatchLog/Components/Account/Pages/ConfirmEmail.razor +++ /dev/null @@ -1,48 +0,0 @@ -@page "/Account/ConfirmEmail" - -@using System.Text -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using WatchLog.Data - -@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."; - } - } -} diff --git a/WatchLog/Components/Account/Pages/ConfirmEmailChange.razor b/WatchLog/Components/Account/Pages/ConfirmEmailChange.razor deleted file mode 100644 index 03c7dda..0000000 --- a/WatchLog/Components/Account/Pages/ConfirmEmailChange.razor +++ /dev/null @@ -1,68 +0,0 @@ -@page "/Account/ConfirmEmailChange" - -@using System.Text -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using WatchLog.Data - -@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."; - } -} diff --git a/WatchLog/Components/Account/Pages/ExternalLogin.razor b/WatchLog/Components/Account/Pages/ExternalLogin.razor deleted file mode 100644 index 6cca98d..0000000 --- a/WatchLog/Components/Account/Pages/ExternalLogin.razor +++ /dev/null @@ -1,205 +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 WatchLog.Data - -@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 ApplicationUser CreateUser() - { - try - { - return Activator.CreateInstance(); - } - catch - { - throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " + - $"Ensure that '{nameof(ApplicationUser)}' 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; } = ""; - } -} diff --git a/WatchLog/Components/Account/Pages/ForgotPassword.razor b/WatchLog/Components/Account/Pages/ForgotPassword.razor deleted file mode 100644 index c95d4e2..0000000 --- a/WatchLog/Components/Account/Pages/ForgotPassword.razor +++ /dev/null @@ -1,68 +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 WatchLog.Data - -@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; } = ""; - } -} diff --git a/WatchLog/Components/Account/Pages/ForgotPasswordConfirmation.razor b/WatchLog/Components/Account/Pages/ForgotPasswordConfirmation.razor deleted file mode 100644 index a771a3a..0000000 --- a/WatchLog/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. -

diff --git a/WatchLog/Components/Account/Pages/InvalidPasswordReset.razor b/WatchLog/Components/Account/Pages/InvalidPasswordReset.razor deleted file mode 100644 index 561b651..0000000 --- a/WatchLog/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. -

diff --git a/WatchLog/Components/Account/Pages/InvalidUser.razor b/WatchLog/Components/Account/Pages/InvalidUser.razor deleted file mode 100644 index e61fe5d..0000000 --- a/WatchLog/Components/Account/Pages/InvalidUser.razor +++ /dev/null @@ -1,7 +0,0 @@ -@page "/Account/InvalidUser" - -Invalid user - -

Invalid user

- - diff --git a/WatchLog/Components/Account/Pages/Lockout.razor b/WatchLog/Components/Account/Pages/Lockout.razor deleted file mode 100644 index 017e31d..0000000 --- a/WatchLog/Components/Account/Pages/Lockout.razor +++ /dev/null @@ -1,8 +0,0 @@ -@page "/Account/Lockout" - -Locked out - -
-

Locked out

- -
diff --git a/WatchLog/Components/Account/Pages/Login.razor b/WatchLog/Components/Account/Pages/Login.razor deleted file mode 100644 index ad8ad28..0000000 --- a/WatchLog/Components/Account/Pages/Login.razor +++ /dev/null @@ -1,128 +0,0 @@ -@page "/Account/Login" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Authentication -@using Microsoft.AspNetCore.Identity -@using WatchLog.Data - -@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; } - } -} diff --git a/WatchLog/Components/Account/Pages/LoginWith2fa.razor b/WatchLog/Components/Account/Pages/LoginWith2fa.razor deleted file mode 100644 index 02095e4..0000000 --- a/WatchLog/Components/Account/Pages/LoginWith2fa.razor +++ /dev/null @@ -1,101 +0,0 @@ -@page "/Account/LoginWith2fa" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using WatchLog.Data - -@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 ApplicationUser 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; } - } -} diff --git a/WatchLog/Components/Account/Pages/LoginWithRecoveryCode.razor b/WatchLog/Components/Account/Pages/LoginWithRecoveryCode.razor deleted file mode 100644 index 7da8c21..0000000 --- a/WatchLog/Components/Account/Pages/LoginWithRecoveryCode.razor +++ /dev/null @@ -1,85 +0,0 @@ -@page "/Account/LoginWithRecoveryCode" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using WatchLog.Data - -@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 ApplicationUser 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; } = ""; - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/ChangePassword.razor b/WatchLog/Components/Account/Pages/Manage/ChangePassword.razor deleted file mode 100644 index 7ff53c1..0000000 --- a/WatchLog/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 WatchLog.Data - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Change password - -

Change password

- -
-
- - - -
- - - -
-
- - - -
-
- - - -
- -
-
-
- -@code { - private string? message; - private ApplicationUser 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; } = ""; - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/DeletePersonalData.razor b/WatchLog/Components/Account/Pages/Manage/DeletePersonalData.razor deleted file mode 100644 index ca37a9a..0000000 --- a/WatchLog/Components/Account/Pages/Manage/DeletePersonalData.razor +++ /dev/null @@ -1,86 +0,0 @@ -@page "/Account/Manage/DeletePersonalData" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using WatchLog.Data - -@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 ApplicationUser 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; } = ""; - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/Disable2fa.razor b/WatchLog/Components/Account/Pages/Manage/Disable2fa.razor deleted file mode 100644 index 3f0ef62..0000000 --- a/WatchLog/Components/Account/Pages/Manage/Disable2fa.razor +++ /dev/null @@ -1,64 +0,0 @@ -@page "/Account/Manage/Disable2fa" - -@using Microsoft.AspNetCore.Identity -@using WatchLog.Data - -@inject UserManager UserManager -@inject IdentityUserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Disable two-factor authentication (2FA) - - -

Disable two-factor authentication (2FA)

- - - -
-
- - - -
- -@code { - private ApplicationUser 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); - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/Email.razor b/WatchLog/Components/Account/Pages/Manage/Email.razor deleted file mode 100644 index f89b7e4..0000000 --- a/WatchLog/Components/Account/Pages/Manage/Email.razor +++ /dev/null @@ -1,123 +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 WatchLog.Data - -@inject UserManager UserManager -@inject IEmailSender EmailSender -@inject IdentityUserAccessor UserAccessor -@inject NavigationManager NavigationManager - -Manage email - -

Manage email

- - -
-
-
- - - - - - @if (isEmailConfirmed) - { -
- -
- ✓ -
- -
- } - else - { -
- - - -
- } -
- - - -
- -
-
-
- -@code { - private string? message; - private ApplicationUser 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; } - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/EnableAuthenticator.razor b/WatchLog/Components/Account/Pages/Manage/EnableAuthenticator.razor deleted file mode 100644 index 90e82bf..0000000 --- a/WatchLog/Components/Account/Pages/Manage/EnableAuthenticator.razor +++ /dev/null @@ -1,172 +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 WatchLog.Data - -@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 ApplicationUser 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(ApplicationUser 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; } = ""; - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/ExternalLogins.razor b/WatchLog/Components/Account/Pages/Manage/ExternalLogins.razor deleted file mode 100644 index 2ba0958..0000000 --- a/WatchLog/Components/Account/Pages/Manage/ExternalLogins.razor +++ /dev/null @@ -1,140 +0,0 @@ -@page "/Account/Manage/ExternalLogins" - -@using Microsoft.AspNetCore.Authentication -@using Microsoft.AspNetCore.Identity -@using WatchLog.Data - -@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 ApplicationUser 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); - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor b/WatchLog/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor deleted file mode 100644 index 3069853..0000000 --- a/WatchLog/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor +++ /dev/null @@ -1,68 +0,0 @@ -@page "/Account/Manage/GenerateRecoveryCodes" - -@using Microsoft.AspNetCore.Identity -@using WatchLog.Data - -@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 ApplicationUser 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); - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/Index.razor b/WatchLog/Components/Account/Pages/Manage/Index.razor deleted file mode 100644 index 7db734d..0000000 --- a/WatchLog/Components/Account/Pages/Manage/Index.razor +++ /dev/null @@ -1,77 +0,0 @@ -@page "/Account/Manage" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using WatchLog.Data - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager - -Profile - -

Profile

- - -
-
- - - -
- - -
-
- - - -
- -
-
-
- -@code { - private ApplicationUser 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; } - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/PersonalData.razor b/WatchLog/Components/Account/Pages/Manage/PersonalData.razor deleted file mode 100644 index 851eb54..0000000 --- a/WatchLog/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); - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/ResetAuthenticator.razor b/WatchLog/Components/Account/Pages/Manage/ResetAuthenticator.razor deleted file mode 100644 index c8e0bb4..0000000 --- a/WatchLog/Components/Account/Pages/Manage/ResetAuthenticator.razor +++ /dev/null @@ -1,52 +0,0 @@ -@page "/Account/Manage/ResetAuthenticator" - -@using Microsoft.AspNetCore.Identity -@using WatchLog.Data - -@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); - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/SetPassword.razor b/WatchLog/Components/Account/Pages/Manage/SetPassword.razor deleted file mode 100644 index 86e1320..0000000 --- a/WatchLog/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 WatchLog.Data - -@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 ApplicationUser 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; } - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/TwoFactorAuthentication.razor b/WatchLog/Components/Account/Pages/Manage/TwoFactorAuthentication.razor deleted file mode 100644 index 870020b..0000000 --- a/WatchLog/Components/Account/Pages/Manage/TwoFactorAuthentication.razor +++ /dev/null @@ -1,101 +0,0 @@ -@page "/Account/Manage/TwoFactorAuthentication" - -@using Microsoft.AspNetCore.Http.Features -@using Microsoft.AspNetCore.Identity -@using WatchLog.Data - -@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); - } -} diff --git a/WatchLog/Components/Account/Pages/Manage/_Imports.razor b/WatchLog/Components/Account/Pages/Manage/_Imports.razor deleted file mode 100644 index ada5bb0..0000000 --- a/WatchLog/Components/Account/Pages/Manage/_Imports.razor +++ /dev/null @@ -1,2 +0,0 @@ -@layout ManageLayout -@attribute [Microsoft.AspNetCore.Authorization.Authorize] diff --git a/WatchLog/Components/Account/Pages/Register.razor b/WatchLog/Components/Account/Pages/Register.razor deleted file mode 100644 index 4e81d58..0000000 --- a/WatchLog/Components/Account/Pages/Register.razor +++ /dev/null @@ -1,145 +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 WatchLog.Data - -@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 ApplicationUser CreateUser() - { - try - { - return Activator.CreateInstance(); - } - catch - { - throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " + - $"Ensure that '{nameof(ApplicationUser)}' 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; } = ""; - } -} diff --git a/WatchLog/Components/Account/Pages/RegisterConfirmation.razor b/WatchLog/Components/Account/Pages/RegisterConfirmation.razor deleted file mode 100644 index 304330a..0000000 --- a/WatchLog/Components/Account/Pages/RegisterConfirmation.razor +++ /dev/null @@ -1,68 +0,0 @@ -@page "/Account/RegisterConfirmation" - -@using System.Text -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using WatchLog.Data - -@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 }); - } - } -} diff --git a/WatchLog/Components/Account/Pages/ResendEmailConfirmation.razor b/WatchLog/Components/Account/Pages/ResendEmailConfirmation.razor deleted file mode 100644 index 5e6eba5..0000000 --- a/WatchLog/Components/Account/Pages/ResendEmailConfirmation.razor +++ /dev/null @@ -1,68 +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 WatchLog.Data - -@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; } = ""; - } -} diff --git a/WatchLog/Components/Account/Pages/ResetPassword.razor b/WatchLog/Components/Account/Pages/ResetPassword.razor deleted file mode 100644 index 50a8fc1..0000000 --- a/WatchLog/Components/Account/Pages/ResetPassword.razor +++ /dev/null @@ -1,103 +0,0 @@ -@page "/Account/ResetPassword" - -@using System.ComponentModel.DataAnnotations -@using System.Text -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using WatchLog.Data - -@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; } = ""; - } -} diff --git a/WatchLog/Components/Account/Pages/ResetPasswordConfirmation.razor b/WatchLog/Components/Account/Pages/ResetPasswordConfirmation.razor deleted file mode 100644 index 247e96e..0000000 --- a/WatchLog/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. -

diff --git a/WatchLog/Components/Account/Pages/_Imports.razor b/WatchLog/Components/Account/Pages/_Imports.razor deleted file mode 100644 index f4c39f8..0000000 --- a/WatchLog/Components/Account/Pages/_Imports.razor +++ /dev/null @@ -1,2 +0,0 @@ -@using WatchLog.Components.Account.Shared -@attribute [ExcludeFromInteractiveRouting] diff --git a/WatchLog/Components/Account/Shared/ExternalLoginPicker.razor b/WatchLog/Components/Account/Shared/ExternalLoginPicker.razor deleted file mode 100644 index ef0b693..0000000 --- a/WatchLog/Components/Account/Shared/ExternalLoginPicker.razor +++ /dev/null @@ -1,43 +0,0 @@ -@using Microsoft.AspNetCore.Authentication -@using Microsoft.AspNetCore.Identity -@using WatchLog.Data - -@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(); - } -} diff --git a/WatchLog/Components/Account/Shared/ManageLayout.razor b/WatchLog/Components/Account/Shared/ManageLayout.razor deleted file mode 100644 index a61d240..0000000 --- a/WatchLog/Components/Account/Shared/ManageLayout.razor +++ /dev/null @@ -1,17 +0,0 @@ -@inherits LayoutComponentBase -@layout WatchLog.Components.Layout.MainLayout - -

Manage your account

- -
-

Change your account settings

-
-
-
- -
-
- @Body -
-
-
diff --git a/WatchLog/Components/Account/Shared/ManageNavMenu.razor b/WatchLog/Components/Account/Shared/ManageNavMenu.razor deleted file mode 100644 index 6c73604..0000000 --- a/WatchLog/Components/Account/Shared/ManageNavMenu.razor +++ /dev/null @@ -1,37 +0,0 @@ -@using Microsoft.AspNetCore.Identity -@using WatchLog.Data - -@inject SignInManager SignInManager - - - -@code { - private bool hasExternalLogins; - - protected override async Task OnInitializedAsync() - { - hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); - } -} diff --git a/WatchLog/Components/Account/Shared/RedirectToLogin.razor b/WatchLog/Components/Account/Shared/RedirectToLogin.razor deleted file mode 100644 index c8b8eff..0000000 --- a/WatchLog/Components/Account/Shared/RedirectToLogin.razor +++ /dev/null @@ -1,8 +0,0 @@ -@inject NavigationManager NavigationManager - -@code { - protected override void OnInitialized() - { - NavigationManager.NavigateTo($"Account/Login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true); - } -} diff --git a/WatchLog/Components/Account/Shared/ShowRecoveryCodes.razor b/WatchLog/Components/Account/Shared/ShowRecoveryCodes.razor deleted file mode 100644 index aa92e11..0000000 --- a/WatchLog/Components/Account/Shared/ShowRecoveryCodes.razor +++ /dev/null @@ -1,28 +0,0 @@ - -

Recovery codes

- -
-
- @foreach (var recoveryCode in RecoveryCodes) - { -
- @recoveryCode -
- } -
-
- -@code { - [Parameter] - public string[] RecoveryCodes { get; set; } = []; - - [Parameter] - public string? StatusMessage { get; set; } -} diff --git a/WatchLog/Components/Account/Shared/StatusMessage.razor b/WatchLog/Components/Account/Shared/StatusMessage.razor deleted file mode 100644 index 12cd544..0000000 --- a/WatchLog/Components/Account/Shared/StatusMessage.razor +++ /dev/null @@ -1,29 +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); - } - } -} diff --git a/WatchLog/Components/App.razor b/WatchLog/Components/App.razor deleted file mode 100644 index e0e154f..0000000 --- a/WatchLog/Components/App.razor +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/WatchLog/Components/Layout/MainLayout.razor b/WatchLog/Components/Layout/MainLayout.razor deleted file mode 100644 index 78624f3..0000000 --- a/WatchLog/Components/Layout/MainLayout.razor +++ /dev/null @@ -1,23 +0,0 @@ -@inherits LayoutComponentBase - -
- - -
-
- About -
- -
- @Body -
-
-
- -
- An unhandled error has occurred. - Reload - 🗙 -
diff --git a/WatchLog/Components/Layout/MainLayout.razor.css b/WatchLog/Components/Layout/MainLayout.razor.css deleted file mode 100644 index 38d1f25..0000000 --- a/WatchLog/Components/Layout/MainLayout.razor.css +++ /dev/null @@ -1,98 +0,0 @@ -.page { - position: relative; - display: flex; - flex-direction: column; -} - -main { - flex: 1; -} - -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); -} - -.top-row { - background-color: #f7f7f7; - border-bottom: 1px solid #d6d5d5; - justify-content: flex-end; - height: 3.5rem; - display: flex; - align-items: center; -} - - .top-row ::deep a, .top-row ::deep .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - text-decoration: none; - } - - .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { - text-decoration: underline; - } - - .top-row ::deep a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - -@media (max-width: 640.98px) { - .top-row { - justify-content: space-between; - } - - .top-row ::deep a, .top-row ::deep .btn-link { - margin-left: 0; - } -} - -@media (min-width: 641px) { - .page { - flex-direction: row; - } - - .sidebar { - width: 250px; - height: 100vh; - position: sticky; - top: 0; - } - - .top-row { - position: sticky; - top: 0; - z-index: 1; - } - - .top-row.auth ::deep a:first-child { - flex: 1; - text-align: right; - width: 0; - } - - .top-row, article { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } -} - -#blazor-error-ui { - color-scheme: light only; - background: lightyellow; - bottom: 0; - box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); - box-sizing: border-box; - display: none; - left: 0; - padding: 0.6rem 1.25rem 0.7rem 1.25rem; - position: fixed; - width: 100%; - z-index: 1000; -} - - #blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; - } diff --git a/WatchLog/Components/Layout/NavMenu.razor b/WatchLog/Components/Layout/NavMenu.razor deleted file mode 100644 index 5f168ab..0000000 --- a/WatchLog/Components/Layout/NavMenu.razor +++ /dev/null @@ -1,92 +0,0 @@ -@implements IDisposable - -@inject NavigationManager NavigationManager - - - - - - - -@code { - private string? currentUrl; - - protected override void OnInitialized() - { - currentUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); - NavigationManager.LocationChanged += OnLocationChanged; - } - - private void OnLocationChanged(object? sender, LocationChangedEventArgs e) - { - currentUrl = NavigationManager.ToBaseRelativePath(e.Location); - StateHasChanged(); - } - - public void Dispose() - { - NavigationManager.LocationChanged -= OnLocationChanged; - } -} - diff --git a/WatchLog/Components/Layout/NavMenu.razor.css b/WatchLog/Components/Layout/NavMenu.razor.css deleted file mode 100644 index 0145d9d..0000000 --- a/WatchLog/Components/Layout/NavMenu.razor.css +++ /dev/null @@ -1,125 +0,0 @@ -.navbar-toggler { - appearance: none; - cursor: pointer; - width: 3.5rem; - height: 2.5rem; - color: white; - position: absolute; - top: 0.5rem; - right: 1rem; - border: 1px solid rgba(255, 255, 255, 0.1); - background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); -} - -.navbar-toggler:checked { - background-color: rgba(255, 255, 255, 0.5); -} - -.top-row { - min-height: 3.5rem; - background-color: rgba(0,0,0,0.4); -} - -.navbar-brand { - font-size: 1.1rem; -} - -.bi { - display: inline-block; - position: relative; - width: 1.25rem; - height: 1.25rem; - margin-right: 0.75rem; - top: -1px; - background-size: cover; -} - -.bi-house-door-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); -} - -.bi-plus-square-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); -} - -.bi-list-nested-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); -} - -.bi-lock-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath d='M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2zM5 8h6a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z'/%3E%3C/svg%3E"); -} - -.bi-person-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person' viewBox='0 0 16 16'%3E%3Cpath d='M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0Zm4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4Zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664h10Z'/%3E%3C/svg%3E"); -} - -.bi-person-badge-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-badge' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-3zM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z'/%3E%3Cpath d='M4.5 0A2.5 2.5 0 0 0 2 2.5V14a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2.5A2.5 2.5 0 0 0 11.5 0h-7zM3 2.5A1.5 1.5 0 0 1 4.5 1h7A1.5 1.5 0 0 1 13 2.5v10.795a4.2 4.2 0 0 0-.776-.492C11.392 12.387 10.063 12 8 12s-3.392.387-4.224.803a4.2 4.2 0 0 0-.776.492V2.5z'/%3E%3C/svg%3E"); -} - -.bi-person-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-fill' viewBox='0 0 16 16'%3E%3Cpath d='M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3Zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z'/%3E%3C/svg%3E"); -} - -.bi-arrow-bar-left-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-arrow-bar-left' viewBox='0 0 16 16'%3E%3Cpath d='M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5ZM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5Z'/%3E%3C/svg%3E"); -} - -.nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; -} - - .nav-item:first-of-type { - padding-top: 1rem; - } - - .nav-item:last-of-type { - padding-bottom: 1rem; - } - - .nav-item ::deep .nav-link { - color: #d7d7d7; - background: none; - border: none; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - width: 100%; - } - -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.37); - color: white; -} - -.nav-item ::deep .nav-link:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -.nav-scrollable { - display: none; -} - -.navbar-toggler:checked ~ .nav-scrollable { - display: block; -} - -@media (min-width: 641px) { - .navbar-toggler { - display: none; - } - - .nav-scrollable { - /* Never collapse the sidebar for wide screens */ - display: block; - - /* Allow sidebar to scroll for tall menus */ - height: calc(100vh - 3.5rem); - overflow-y: auto; - } -} diff --git a/WatchLog/Components/Pages/Auth.razor b/WatchLog/Components/Pages/Auth.razor deleted file mode 100644 index b7bbe6e..0000000 --- a/WatchLog/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! - diff --git a/WatchLog/Components/Pages/Counter.razor b/WatchLog/Components/Pages/Counter.razor deleted file mode 100644 index 50b94a7..0000000 --- a/WatchLog/Components/Pages/Counter.razor +++ /dev/null @@ -1,23 +0,0 @@ -@page "/counter" -@rendermode InteractiveServer - -@using Microsoft.AspNetCore.Authorization - -@attribute [Authorize] - -Counter - -

Counter

- -

Current count: @currentCount

- - - -@code { - private int currentCount = 0; - - private void IncrementCount() - { - currentCount++; - } -} diff --git a/WatchLog/Components/Pages/Error.razor b/WatchLog/Components/Pages/Error.razor deleted file mode 100644 index 576cc2d..0000000 --- a/WatchLog/Components/Pages/Error.razor +++ /dev/null @@ -1,36 +0,0 @@ -@page "/Error" -@using System.Diagnostics - -Error - -

Error.

-

An error occurred while processing your request.

- -@if (ShowRequestId) -{ -

- Request ID: @RequestId -

-} - -

Development Mode

-

- Swapping to Development environment will display more detailed information about the error that occurred. -

-

- The Development environment shouldn't be enabled for deployed applications. - It can result in displaying sensitive information from exceptions to end users. - For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development - and restarting the app. -

- -@code{ - [CascadingParameter] - private HttpContext? HttpContext { get; set; } - - private string? RequestId { get; set; } - private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - - protected override void OnInitialized() => - RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; -} diff --git a/WatchLog/Components/Pages/Home.razor b/WatchLog/Components/Pages/Home.razor deleted file mode 100644 index 6fda001..0000000 --- a/WatchLog/Components/Pages/Home.razor +++ /dev/null @@ -1,11 +0,0 @@ -@page "/" - -@using Microsoft.AspNetCore.Authorization - -@attribute [Authorize] - -Home - -

Hello, world!

- -Welcome to your new app. diff --git a/WatchLog/Components/Pages/Weather.razor b/WatchLog/Components/Pages/Weather.razor deleted file mode 100644 index 28f6249..0000000 --- a/WatchLog/Components/Pages/Weather.razor +++ /dev/null @@ -1,68 +0,0 @@ -@page "/weather" -@attribute [StreamRendering] - -@using Microsoft.AspNetCore.Authorization - -@attribute [Authorize(Roles = "Admin")] - -Weather - -

Weather

- -

This component demonstrates showing data.

- -@if (forecasts == null) -{ -

Loading...

-} -else -{ - - - - - - - - - - - @foreach (var forecast in forecasts) - { - - - - - - - } - -
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
-} - -@code { - private WeatherForecast[]? forecasts; - - protected override async Task OnInitializedAsync() - { - // Simulate asynchronous loading to demonstrate streaming rendering - await Task.Delay(500); - - var startDate = DateOnly.FromDateTime(DateTime.Now); - var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; - forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = startDate.AddDays(index), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = summaries[Random.Shared.Next(summaries.Length)] - }).ToArray(); - } - - private class WeatherForecast - { - public DateOnly Date { get; set; } - public int TemperatureC { get; set; } - public string? Summary { get; set; } - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - } -} diff --git a/WatchLog/Components/Routes.razor b/WatchLog/Components/Routes.razor deleted file mode 100644 index fd39f83..0000000 --- a/WatchLog/Components/Routes.razor +++ /dev/null @@ -1,11 +0,0 @@ -@using WatchLog.Components.Account.Shared - - - - - - - - - - diff --git a/WatchLog/Components/_Imports.razor b/WatchLog/Components/_Imports.razor deleted file mode 100644 index 7a1c914..0000000 --- a/WatchLog/Components/_Imports.razor +++ /dev/null @@ -1,11 +0,0 @@ -@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 -@using static Microsoft.AspNetCore.Components.Web.RenderMode -@using Microsoft.AspNetCore.Components.Web.Virtualization -@using Microsoft.JSInterop -@using WatchLog -@using WatchLog.Components diff --git a/WatchLog/Data/ApplicationDbContext.cs b/WatchLog/Data/ApplicationDbContext.cs deleted file mode 100644 index 6404cc0..0000000 --- a/WatchLog/Data/ApplicationDbContext.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - -namespace WatchLog.Data -{ - public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options) - { - - // Global - public DbSet Genres { get; set; } - public DbSet GlobalEntities { get; set; } - public DbSet StreamingPlatforms { get; set; } - public DbSet MediaType { get; set; } // 'Watchlog.Data.Type' if namecolsion with System.Type - - //Private - public DbSet