feat(dashboard): Blazor LoginCard page reusing the hardened /login endpoint
This commit is contained in:
+12
-58
@@ -1,7 +1,6 @@
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
||||
using ZB.MOM.WW.MxGateway.Server.Dashboard.Components;
|
||||
using ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs;
|
||||
@@ -25,12 +24,11 @@ public static class DashboardEndpointRouteBuilderExtensions
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
endpoints.MapGet(
|
||||
"/login",
|
||||
(HttpContext httpContext, IAntiforgery antiforgery) => GetLoginAsync(httpContext, antiforgery))
|
||||
.AllowAnonymous()
|
||||
.WithName("DashboardLogin");
|
||||
|
||||
// GET /login is served by the [AllowAnonymous] Blazor <Login> component
|
||||
// (Components/Pages/Login.razor → @page "/login"), which renders the shared
|
||||
// kit's <LoginCard>. Its [AllowAnonymous] attribute overrides the
|
||||
// RequireAuthorization(ViewerPolicy) that MapRazorComponents<App>() applies,
|
||||
// so the cookie scheme's LoginPath="/login" redirect resolves for anonymous users.
|
||||
endpoints.MapPost(
|
||||
"/login",
|
||||
(HttpContext httpContext, IAntiforgery antiforgery, IDashboardAuthenticator authenticator) =>
|
||||
@@ -92,17 +90,6 @@ public static class DashboardEndpointRouteBuilderExtensions
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private static Task<ContentHttpResult> GetLoginAsync(
|
||||
HttpContext httpContext,
|
||||
IAntiforgery antiforgery)
|
||||
{
|
||||
string returnUrl = SanitizeReturnUrl(httpContext.Request.Query["returnUrl"].ToString());
|
||||
|
||||
return Task.FromResult(TypedResults.Content(
|
||||
RenderLoginPage(httpContext, antiforgery, returnUrl, failureMessage: null),
|
||||
"text/html"));
|
||||
}
|
||||
|
||||
private static async Task<IResult> PostLoginAsync(
|
||||
HttpContext httpContext,
|
||||
IAntiforgery antiforgery,
|
||||
@@ -124,10 +111,13 @@ public static class DashboardEndpointRouteBuilderExtensions
|
||||
|
||||
if (!result.Succeeded || result.Principal is null)
|
||||
{
|
||||
return TypedResults.Content(
|
||||
RenderLoginPage(httpContext, antiforgery, returnUrl, result.FailureMessage),
|
||||
"text/html",
|
||||
statusCode: StatusCodes.Status401Unauthorized);
|
||||
// Round-trip the failure back to the anonymous Blazor /login page, carrying
|
||||
// the (sanitized) returnUrl so a successful retry still lands on the target.
|
||||
string failureMessage = result.FailureMessage
|
||||
?? "The username or password is invalid, or the user is not authorized.";
|
||||
return Results.Redirect(
|
||||
$"/login?error={Uri.EscapeDataString(failureMessage)}"
|
||||
+ $"&returnUrl={Uri.EscapeDataString(returnUrl)}");
|
||||
}
|
||||
|
||||
await httpContext
|
||||
@@ -158,42 +148,6 @@ public static class DashboardEndpointRouteBuilderExtensions
|
||||
return Results.LocalRedirect("/login");
|
||||
}
|
||||
|
||||
private static string RenderLoginPage(
|
||||
HttpContext httpContext,
|
||||
IAntiforgery antiforgery,
|
||||
string returnUrl,
|
||||
string? failureMessage)
|
||||
{
|
||||
AntiforgeryTokenSet tokens = antiforgery.GetAndStoreTokens(httpContext);
|
||||
string requestToken = tokens.RequestToken ?? string.Empty;
|
||||
string alert = string.IsNullOrWhiteSpace(failureMessage)
|
||||
? string.Empty
|
||||
: $"<p class=\"alert alert-danger\" role=\"alert\">{HtmlEncoder.Default.Encode(failureMessage)}</p>";
|
||||
|
||||
string body = $"""
|
||||
<section class="dashboard-login">
|
||||
{alert}
|
||||
<form method="post" action="/login" class="card login-card">
|
||||
<div class="card-body">
|
||||
<input name="{tokens.FormFieldName}" type="hidden" value="{HtmlEncoder.Default.Encode(requestToken)}" />
|
||||
<input name="returnUrl" type="hidden" value="{HtmlEncoder.Default.Encode(returnUrl)}" />
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input id="username" name="username" type="text" autocomplete="username" class="form-control" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input id="password" name="password" type="password" autocomplete="current-password" class="form-control" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Sign in</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
""";
|
||||
|
||||
return RenderPage("Dashboard Sign In", heading: null, body);
|
||||
}
|
||||
|
||||
private static string RenderPage(string title, string body)
|
||||
=> RenderPage(title, heading: title, body);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user