chore: applied code style
This commit is contained in:
parent
760203083a
commit
3ce25305a9
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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}");
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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) { }
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 { }
|
Loading…
Reference in a new issue