chore: applied code style

This commit is contained in:
Mia Rose Winter 2024-01-11 15:15:30 +01:00
parent 760203083a
commit 3ce25305a9
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
6 changed files with 152 additions and 172 deletions

View file

@ -10,104 +10,96 @@
using Wave.Components.Account.Pages.Manage; using Wave.Components.Account.Pages.Manage;
using Wave.Data; using Wave.Data;
namespace Microsoft.AspNetCore.Routing 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"); 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);
accountGroup.MapPost("/PerformExternalLogin", ( var accountGroup = endpoints.MapGroup("/Account");
HttpContext context,
[FromServices] SignInManager<ApplicationUser> signInManager,
[FromForm] string provider,
[FromForm] string returnUrl) =>
{
IEnumerable<KeyValuePair<string, StringValues>> query = [
new("ReturnUrl", returnUrl),
new("Action", ExternalLogin.LoginCallbackAction)];
var redirectUrl = UriHelper.BuildRelative( accountGroup.MapPost("/PerformExternalLogin", (
context.Request.PathBase, HttpContext context,
"/Account/ExternalLogin", [FromServices] SignInManager<ApplicationUser> signInManager,
QueryString.Create(query)); [FromForm] string provider,
[FromForm] string returnUrl) => {
IEnumerable<KeyValuePair<string, StringValues>> query = [
new("ReturnUrl", returnUrl),
new("Action", ExternalLogin.LoginCallbackAction)
];
var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); var redirectUrl = UriHelper.BuildRelative(
return TypedResults.Challenge(properties, [provider]); context.Request.PathBase,
}); "/Account/ExternalLogin",
QueryString.Create(query));
accountGroup.MapPost("/Logout", async ( var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
ClaimsPrincipal user, return TypedResults.Challenge(properties, [provider]);
SignInManager<ApplicationUser> signInManager, });
[FromForm] string returnUrl) =>
{
await signInManager.SignOutAsync();
return TypedResults.LocalRedirect($"~/{returnUrl}");
});
var manageGroup = accountGroup.MapGroup("/Manage").RequireAuthorization(); accountGroup.MapPost("/Logout", async (
ClaimsPrincipal user,
SignInManager<ApplicationUser> signInManager,
[FromForm] string returnUrl) => {
await signInManager.SignOutAsync();
return TypedResults.LocalRedirect($"~/{returnUrl}");
});
manageGroup.MapPost("/LinkExternalLogin", async ( var manageGroup = accountGroup.MapGroup("/Manage").RequireAuthorization();
HttpContext context,
[FromServices] SignInManager<ApplicationUser> signInManager,
[FromForm] string provider) =>
{
// Clear the existing external cookie to ensure a clean login process
await context.SignOutAsync(IdentityConstants.ExternalScheme);
var redirectUrl = UriHelper.BuildRelative( manageGroup.MapPost("/LinkExternalLogin", async (
context.Request.PathBase, HttpContext context,
"/Account/Manage/ExternalLogins", [FromServices] SignInManager<ApplicationUser> signInManager,
QueryString.Create("Action", ExternalLogins.LinkLoginCallbackAction)); [FromForm] string provider) => {
// Clear the existing external cookie to ensure a clean login process
await context.SignOutAsync(IdentityConstants.ExternalScheme);
var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, signInManager.UserManager.GetUserId(context.User)); var redirectUrl = UriHelper.BuildRelative(
return TypedResults.Challenge(properties, [provider]); context.Request.PathBase,
}); "/Account/Manage/ExternalLogins",
QueryString.Create("Action", ExternalLogins.LinkLoginCallbackAction));
var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>(); var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl,
var downloadLogger = loggerFactory.CreateLogger("DownloadPersonalData"); signInManager.UserManager.GetUserId(context.User));
return TypedResults.Challenge(properties, [provider]);
});
manageGroup.MapPost("/DownloadPersonalData", async ( var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>();
HttpContext context, var downloadLogger = loggerFactory.CreateLogger("DownloadPersonalData");
[FromServices] UserManager<ApplicationUser> 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); manageGroup.MapPost("/DownloadPersonalData", async (
downloadLogger.LogInformation("User with ID '{UserId}' asked for their personal data.", userId); HttpContext context,
[FromServices] UserManager<ApplicationUser> 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)}'.");
}
// Only include personal data for download var userId = await userManager.GetUserIdAsync(user);
var personalData = new Dictionary<string, string>(); downloadLogger.LogInformation("User with ID '{UserId}' asked for their personal data.", userId);
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); // Only include personal data for download
foreach (var l in logins) var personalData = new Dictionary<string, string>();
{ var personalDataProps = typeof(ApplicationUser).GetProperties().Where(
personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey); prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
} foreach (var p in personalDataProps) {
personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
}
personalData.Add("Authenticator Key", (await userManager.GetAuthenticatorKeyAsync(user))!); var logins = await userManager.GetLoginsAsync(user);
var fileBytes = JsonSerializer.SerializeToUtf8Bytes(personalData); foreach (var l in logins) {
personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey);
}
context.Response.Headers.TryAdd("Content-Disposition", "attachment; filename=PersonalData.json"); personalData.Add("Authenticator Key", (await userManager.GetAuthenticatorKeyAsync(user))!);
return TypedResults.File(fileBytes, contentType: "application/json", fileDownloadName: "PersonalData.json"); var fileBytes = JsonSerializer.SerializeToUtf8Bytes(personalData);
});
return accountGroup; context.Response.Headers.TryAdd("Content-Disposition", "attachment; filename=PersonalData.json");
} return TypedResults.File(fileBytes, contentType: "application/json", fileDownloadName: "PersonalData.json");
});
return accountGroup;
} }
} }

View file

@ -2,20 +2,21 @@
using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Identity.UI.Services;
using Wave.Data; using Wave.Data;
namespace Wave.Components.Account namespace Wave.Components.Account;
{
// Remove the "else if (EmailSender is IdentityNoOpEmailSender)" block from RegisterConfirmation.razor after updating with a real implementation.
internal sealed class IdentityNoOpEmailSender : IEmailSender<ApplicationUser>
{
private readonly IEmailSender emailSender = new NoOpEmailSender();
public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) => // Remove the "else if (EmailSender is IdentityNoOpEmailSender)" block from RegisterConfirmation.razor after updating with a real implementation.
emailSender.SendEmailAsync(email, "Confirm your email", $"Please confirm your account by <a href='{confirmationLink}'>clicking here</a>."); internal sealed class IdentityNoOpEmailSender : IEmailSender<ApplicationUser> {
private readonly IEmailSender emailSender = new NoOpEmailSender();
public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) => public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) =>
emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password by <a href='{resetLink}'>clicking here</a>."); emailSender.SendEmailAsync(email, "Confirm your email",
$"Please confirm your account by <a href='{confirmationLink}'>clicking here</a>.");
public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) => public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) =>
emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password using the following code: {resetCode}"); emailSender.SendEmailAsync(email, "Reset your password",
} $"Please reset your password by <a href='{resetLink}'>clicking here</a>.");
}
public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) =>
emailSender.SendEmailAsync(email, "Reset your password",
$"Please reset your password using the following code: {resetCode}");
}

View file

@ -1,59 +1,53 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace Wave.Components.Account namespace Wave.Components.Account;
{
internal sealed class IdentityRedirectManager(NavigationManager navigationManager)
{
public const string StatusCookieName = "Identity.StatusMessage";
private static readonly CookieBuilder StatusCookieBuilder = new() internal sealed class IdentityRedirectManager(NavigationManager navigationManager) {
{ public const string StatusCookieName = "Identity.StatusMessage";
SameSite = SameSiteMode.Strict,
HttpOnly = true,
IsEssential = true,
MaxAge = TimeSpan.FromSeconds(5),
};
[DoesNotReturn] private static readonly CookieBuilder StatusCookieBuilder = new() {
public void RedirectTo(string? uri) SameSite = SameSiteMode.Strict,
{ HttpOnly = true,
uri ??= ""; IsEssential = true,
MaxAge = TimeSpan.FromSeconds(5),
};
// Prevent open redirects. [DoesNotReturn]
if (!Uri.IsWellFormedUriString(uri, UriKind.Relative)) public void RedirectTo(string? uri) {
{ uri ??= "";
uri = navigationManager.ToBaseRelativePath(uri);
}
// During static rendering, NavigateTo throws a NavigationException which is handled by the framework as a redirect. // Prevent open redirects.
// So as long as this is called from a statically rendered Identity component, the InvalidOperationException is never thrown. if (!Uri.IsWellFormedUriString(uri, UriKind.Relative)) {
navigationManager.NavigateTo(uri); uri = navigationManager.ToBaseRelativePath(uri);
throw new InvalidOperationException($"{nameof(IdentityRedirectManager)} can only be used during static rendering.");
} }
[DoesNotReturn] // During static rendering, NavigateTo throws a NavigationException which is handled by the framework as a redirect.
public void RedirectTo(string uri, Dictionary<string, object?> queryParameters) // So as long as this is called from a statically rendered Identity component, the InvalidOperationException is never thrown.
{ navigationManager.NavigateTo(uri);
var uriWithoutQuery = navigationManager.ToAbsoluteUri(uri).GetLeftPart(UriPartial.Path); throw new InvalidOperationException(
var newUri = navigationManager.GetUriWithQueryParameters(uriWithoutQuery, queryParameters); $"{nameof(IdentityRedirectManager)} can only be used during static rendering.");
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);
} }
}
[DoesNotReturn]
public void RedirectTo(string uri, Dictionary<string, object?> 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);
}

View file

@ -1,20 +1,19 @@
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Wave.Data; using Wave.Data;
namespace Wave.Components.Account namespace Wave.Components.Account;
{
internal sealed class IdentityUserAccessor(UserManager<ApplicationUser> userManager, IdentityRedirectManager redirectManager)
{
public async Task<ApplicationUser> GetRequiredUserAsync(HttpContext context)
{
var user = await userManager.GetUserAsync(context.User);
if (user is null) internal sealed class IdentityUserAccessor(
{ UserManager<ApplicationUser> userManager,
redirectManager.RedirectToWithStatus("Account/InvalidUser", $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context); IdentityRedirectManager redirectManager) {
} public async Task<ApplicationUser> GetRequiredUserAsync(HttpContext context) {
var user = await userManager.GetUserAsync(context.User);
return user; if (user is null) {
redirectManager.RedirectToWithStatus("Account/InvalidUser",
$"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context);
} }
return user;
} }
} }

View file

@ -1,9 +1,7 @@
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Wave.Data namespace Wave.Data;
{
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : IdentityDbContext<ApplicationUser>(options) public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
{ : IdentityDbContext<ApplicationUser>(options) { }
}
}

View file

@ -1,10 +1,6 @@
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
namespace Wave.Data namespace Wave.Data;
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
}
} // Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser { }