fix(theme): correct sticky rail selector, harden bool attrs/tests, doc LoginCard security contract
- layout.css: fix @media sticky selector from #sidebar-collapse → #theme-rail (Fix 1) - NavRailTests/CommonControlsTests: add TDD tests verifying Blazor omits false bool attrs (Fix 2) - TechButton: rename Extra → AdditionalAttributes, move @attributes splat first (Fix 3) - LoginCard: add security contract XML/comment docs on ReturnUrl and ChildContent (Fix 4) - build/pack.sh, push.sh: fix comment from ZB.MOM.WW.Auth → ZB.MOM.WW.Theme (Fix 5)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# pack.sh — produce the ZB.MOM.WW.Auth NuGet packages into ./artifacts.
|
||||
# pack.sh — produce the ZB.MOM.WW.Theme NuGet packages into ./artifacts.
|
||||
#
|
||||
# Usage:
|
||||
# ./build/pack.sh
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# push.sh — pack and push all ZB.MOM.WW.Auth NuGet packages to the Gitea feed.
|
||||
# push.sh — pack and push all ZB.MOM.WW.Theme NuGet packages to the Gitea feed.
|
||||
#
|
||||
# Required environment variables:
|
||||
# GITEA_NUGET_SOURCE — full URL of the Gitea NuGet feed
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
@namespace ZB.MOM.WW.Theme
|
||||
@* Components/LoginCard.razor — static form-POST sign-in card. *@
|
||||
@* Components/LoginCard.razor — static form-POST sign-in card.
|
||||
SECURITY NOTES:
|
||||
- ReturnUrl is echoed into a hidden field verbatim; the consuming app's POST handler
|
||||
MUST validate it is a local/relative URL before redirecting to prevent open-redirect.
|
||||
- This form is NOT auto-protected by Blazor antiforgery; the caller MUST pass an
|
||||
antiforgery token via ChildContent (e.g. <AntiforgeryToken />). *@
|
||||
<div class="login-wrap rise">
|
||||
<section class="panel">
|
||||
<div class="login-body">
|
||||
@@ -33,7 +38,21 @@
|
||||
@code {
|
||||
[Parameter, EditorRequired] public string Product { get; set; } = string.Empty;
|
||||
[Parameter] public string Action { get; set; } = "/auth/login";
|
||||
|
||||
/// <summary>
|
||||
/// Optional URL to redirect to after a successful login. Echoed into a hidden
|
||||
/// <c>returnUrl</c> field. The consuming app's POST handler MUST validate this is
|
||||
/// a local/relative URL before redirecting — do not redirect to arbitrary values
|
||||
/// to prevent open-redirect vulnerabilities.
|
||||
/// </summary>
|
||||
[Parameter] public string? ReturnUrl { get; set; }
|
||||
|
||||
[Parameter] public string? Error { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content rendered inside the form, before the username/password fields.
|
||||
/// The caller MUST supply an antiforgery token here (e.g. <c><AntiforgeryToken /></c>)
|
||||
/// because this static POST form is not auto-protected by Blazor's antiforgery middleware.
|
||||
/// </summary>
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@namespace ZB.MOM.WW.Theme
|
||||
@* Components/TechButton.razor *@
|
||||
<button type="@Type" class="btn @VariantClass" disabled="@Busy" @attributes="Extra">
|
||||
<button @attributes="AdditionalAttributes" type="@Type" class="btn @VariantClass" disabled="@Busy">
|
||||
@if (Busy) { <span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span> }
|
||||
@ChildContent
|
||||
</button>
|
||||
@@ -10,7 +10,7 @@
|
||||
[Parameter] public string Type { get; set; } = "button";
|
||||
[Parameter] public bool Busy { get; set; }
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
[Parameter(CaptureUnmatchedValues = true)] public IDictionary<string, object>? Extra { get; set; }
|
||||
[Parameter(CaptureUnmatchedValues = true)] public IDictionary<string, object>? AdditionalAttributes { get; set; }
|
||||
|
||||
private string VariantClass => Variant switch
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
/* On lg+ keep the side rail pinned so it stays visible when content scrolls. */
|
||||
@media (min-width: 992px) {
|
||||
#sidebar-collapse {
|
||||
#theme-rail {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
|
||||
@@ -27,6 +27,13 @@ public class CommonControlsTests : TestContext
|
||||
Assert.Equal("save", btn.GetAttribute("id"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TechButton_not_busy_is_not_disabled()
|
||||
{
|
||||
var cut = RenderComponent<TechButton>(p => p.Add(x => x.Busy, false).AddChildContent("Go"));
|
||||
Assert.False(cut.Find("button").HasAttribute("disabled"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TechCard_renders_title_and_body()
|
||||
{
|
||||
|
||||
@@ -24,4 +24,13 @@ public class NavRailTests : TestContext
|
||||
Assert.Contains("Navigation", cut.Find("summary").TextContent);
|
||||
Assert.NotNull(cut.Find(".rail-section-body .rail-link"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NavRailSection_collapsed_when_not_expanded()
|
||||
{
|
||||
var cut = RenderComponent<NavRailSection>(p => p
|
||||
.Add(x => x.Title, "Nav").Add(x => x.Expanded, false)
|
||||
.AddChildContent("<a class='rail-link'>X</a>"));
|
||||
Assert.False(cut.Find("details.rail-section").HasAttribute("open"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user