Configured security, stable run
continuous-integration/drone/pr Build is passing

This commit is contained in:
Khwezi Mngoma
2026-05-17 08:29:12 +02:00
parent 3bdf897ac8
commit 5f5f83a85a
7 changed files with 151 additions and 53 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
</div>
</a>
<TopBarAuthstateView IsAuthenticated="false" />
<TopBarAuthstateView />
</header>
<CascadingAuthenticationState>
+32 -12
View File
@@ -62,20 +62,22 @@
<div class="shelf-divider"></div>
<NavLink class="shelf-link" href="settings" @onclick="CloseShelf">
<svg class="link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
<span>Settings</span>
</NavLink>
<NavLink class="shelf-link" href="policies" @onclick="CloseShelf">
<svg class="link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
<span>Policies</span>
</NavLink>
<NavLink class="shelf-link" href="profile" @onclick="CloseShelf">
<svg class="link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
<NavLink class="shelf-link" href="@ProfileUrl" target="_blank" @onclick="CloseShelf">
<svg class="link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
<span>Profile</span>
</NavLink>
<NavLink class="shelf-link" href="auth/logout" ForceLoad="true" @onclick="CloseShelf">
<svg class="link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
</svg>
<span>Logout</span>
</NavLink>
</nav>
</aside>
@@ -83,6 +85,24 @@
[Parameter] public bool IsOpen { get; set; } = false;
[Parameter] public EventCallback<bool> IsOpenChanged { get; set; }
[Inject] private IConfiguration? Configuration { get; set; }
private string? ProfileUrl { get; set; }
protected override void OnInitialized()
{
if (Configuration is null) return;
var authority = Configuration["IdKongisa:Authority"];
if (!string.IsNullOrWhiteSpace(authority))
{
var uri = new Uri(authority);
ProfileUrl = $"{uri.Scheme}://{uri.Host}/if/user/#/settings";
}
}
private async Task ToggleShelf()
{
IsOpen = !IsOpen;
+13 -3
View File
@@ -1,8 +1,8 @@
/* --- 1. The Flyout Side Panel --- */
.nav-shelf-panel {
position: fixed;
top: var(--header-height); /* Drops clean below your top-bar */
right: -300px; /* Hidden by default */
top: var(--header-height);
right: -300px;
width: 300px;
height: calc(100vh - var(--header-height));
background-color: var(--shelf-bg);
@@ -10,7 +10,8 @@
z-index: 2000;
display: flex;
flex-direction: column;
padding: 2rem 1.5rem;
/* MODIFIED: Reduce padding-bottom so items don't clip raw against the bottom line window frame */
padding: 2rem 1.5rem 1rem 1.5rem;
box-sizing: border-box;
transition: right 0.35s cubic-bezier(0.16, 1, 0.3, 1);
box-shadow: -15px 0 40px rgba(0, 0, 0, 0.6);
@@ -68,6 +69,7 @@
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid rgba(144, 224, 239, 0.1);
flex-shrink: 0; /* CRITICAL: Prevents the header text from squishing up on small screens */
}
.shelf-header h3 {
@@ -92,6 +94,14 @@
display: flex;
flex-direction: column;
gap: 8px;
/* NEW RULES: Absorb all available viewport height and isolate scrolling bounds */
flex: 1;
overflow-y: auto;
overflow-x: hidden;
/* Optional: Adds smooth mobile elastic touch responses on iOS devices */
-webkit-overflow-scrolling: touch;
/* Clean up spacing so bottom-most links have breathing room when fully scrolled down */
padding-bottom: 2rem;
}
/* Deep selection matching Blazor's active class matching system */
+44 -14
View File
@@ -1,6 +1,16 @@
<div class="auth-state-container">
@if (!IsAuthenticated)
{
@using Microsoft.AspNetCore.Components.Authorization
<div class="auth-state-container">
<AuthorizeView>
<Authorized>
<div class="auth-indicator authenticated">
<div class="user-meta-stack">
<span class="meta-row-primary">@Name</span>
<span class="meta-row-secondary">@Email</span>
<span class="meta-row-secondary">@LoginTime</span>
</div>
</div>
</Authorized>
<NotAuthorized>
<div class="auth-indicator unauthenticated">
<svg class="security-lock-vector" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<defs>
@@ -20,18 +30,38 @@
</g>
</svg>
</div>
}
else
{
<div class="auth-indicator authenticated">
<div class="user-meta-stack">
<span class="meta-row-primary">ADMIN_OPERATOR</span>
<span class="meta-row-secondary">ID: 409-CLUSTER</span>
</div>
</div>
}
</NotAuthorized>
</AuthorizeView>
</div>
@code {
[Parameter] public bool IsAuthenticated { get; set; } = false;
[CascadingParameter]
private Task<AuthenticationState>? AuthStateTask { get; set; }
private System.Security.Claims.ClaimsPrincipal? UserPrincipal;
private string? Name { get; set; }
private string? Email { get; set; }
private DateTime? LoginTime { get; set; }
protected override async Task OnInitializedAsync()
{
if (AuthStateTask != null)
{
var authState = await AuthStateTask;
UserPrincipal = authState.User;
Name = UserPrincipal?.Identity?.Name;
Email = UserPrincipal?.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value;
var authTimeClaim = UserPrincipal?.FindFirst("auth_time")?.Value;
if (!string.IsNullOrEmpty(authTimeClaim) && long.TryParse(authTimeClaim, out long unixSeconds))
{
var dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(unixSeconds);
LoginTime = dateTimeOffset.LocalDateTime;
}
}
}
}
+27 -2
View File
@@ -65,6 +65,21 @@ builder.Services.AddAuthentication(options =>
NameClaimType = "name",
RoleClaimType = "groups"
};
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProviderForSignOut = async callbackContext =>
{
var request = callbackContext.Request;
string currentBaseUrl = $"{request.Scheme}://{request.Host}{request.PathBase}/";
callbackContext.ProtocolMessage.PostLogoutRedirectUri = currentBaseUrl;
var idToken = await callbackContext.HttpContext.GetTokenAsync("id_token");
if (!string.IsNullOrEmpty(idToken)) callbackContext.ProtocolMessage.IdTokenHint = idToken;
}
};
});
var app = builder.Build();
@@ -97,8 +112,18 @@ app.UseAuthorization();
app.MapStaticAssets();
app.MapGet("/auth/login", (string redirectUri = "/") =>
Results.Challenge(new AuthenticationProperties { RedirectUri = redirectUri },[OpenIdConnectDefaults.AuthenticationScheme]));
app.MapGet("/auth/logout", () => Results.SignOut(new AuthenticationProperties { RedirectUri = "/" }, [CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme]));
Results.Challenge(new AuthenticationProperties { RedirectUri = redirectUri }, [OpenIdConnectDefaults.AuthenticationScheme]));
app.MapGet("/auth/logout", async (HttpContext context) =>
{
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
string currentBaseUrl = $"{context.Request.Scheme}://{context.Request.Host}{context.Request.PathBase}/";
await context.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties
{
RedirectUri = currentBaseUrl
});
});
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
+2 -2
View File
@@ -42,7 +42,7 @@ html, body {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 4rem 0 1rem;
padding: 0 1.5rem; /* Balanced horizontal spacing for both sides */
z-index: 1000;
border-bottom: 1px solid rgba(144, 224, 239, 0.15);
}
@@ -119,7 +119,7 @@ main {
/* --- Responsive Logic --- */
@media (max-width: 1200px) {
.top-bar {
padding: 0 2rem;
padding: 0 1rem;
}
}
+13
View File
@@ -14,6 +14,7 @@ data:
ASPNETCORE_URLS: "http://0.0.0.0:8080"
Monitoring__Address: "http://aspire-dashboard-service.aspire.svc.cluster.local:18889"
Monitoring__ServiceName: "LiteCharms.ShopAdmin.Uat"
IdKongisa__Authority: "https://id.khongisa.co.za/application/o/litecharms-shopadmin"
---
apiVersion: v1
kind: Secret
@@ -25,6 +26,8 @@ data:
connection-string: SG9zdD0xOTIuMTY4LjEuMTcwO0RhdGFiYXNlPXNob3AtZGV2O1VzZXJuYW1lPXNob3AtZGV2LXVzZXI7UGFzc3dvcmQ9a1ZWbW9XS0ozeHpnUVg7UGVyc2lzdCBTZWN1cml0eSBJbmZvPVRydWU=
connection-string-quartz: SG9zdD0xOTIuMTY4LjEuMTcwO0RhdGFiYXNlPXNjaGVkdWxlci1kZXY7VXNlcm5hbWU9c2NoZWR1bGVyLWRldi11c2VyO1Bhc3N3b3JkPWtWVm1vV0tKM3h6Z1FYO1BlcnNpc3QgU2VjdXJpdHkgSW5mbz1UcnVl
aspire-apikey: bWMzRzYzSzJqNVpPRXNpMEFqTW9qTFRYbTFLRVpGY3R6SUlqU3dEaVRHdXQ4cUdTa1B1V3d4R1AxUmJzY0pVbw==
auth-clientid: NUxldE5hSERsUlhOWXo1N3FkMVV1RWN4R01uUDNmT3FXc0RHcWdjUg==
auth-clientsecret: a3ZxN3k1anc0M0g4WDRjQW91eGRqRDhNNXdxVUhUQ2I4UjNSVjNVWjI4TUZjTk51NWxhM3g3V1ZZRzQ2QnJFMjVPMnhXRmhoeEk0VXNSaFlMTHRqSGRhWWVrUTBmdHpIQ3ZNczV5TXdRdERpcDBkM3QzTkNDa0RtN1JXeW1XSTg=
---
apiVersion: v1
kind: PersistentVolumeClaim
@@ -102,6 +105,16 @@ spec:
secretKeyRef:
name: shopadmin-secrets
key: aspire-apikey
- name: IdKongisa__ClientId
valueFrom:
secretKeyRef:
name: shopadmin-secrets
key: auth-clientid
- name: IdKongisa__ClientSecret
valueFrom:
secretKeyRef:
name: shopadmin-secrets
key: auth-clientsecret
volumeMounts:
- name: data
mountPath: /app/wwwroot/content