From 5f5f83a85a06263c44c54e52da615698229149bb Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Sun, 17 May 2026 08:29:12 +0200 Subject: [PATCH] Configured security, stable run --- ShopAdmin/Components/Layout/MainLayout.razor | 2 +- ShopAdmin/Components/Pages/NavShelf.razor | 46 ++++++--- ShopAdmin/Components/Pages/NavShelf.razor.css | 16 +++- .../Components/TopBarAuthstateView.razor | 94 ++++++++++++------- ShopAdmin/Program.cs | 29 +++++- ShopAdmin/wwwroot/app.css | 4 +- litecharms-shopadmin-uat.yml | 13 +++ 7 files changed, 151 insertions(+), 53 deletions(-) diff --git a/ShopAdmin/Components/Layout/MainLayout.razor b/ShopAdmin/Components/Layout/MainLayout.razor index 80c2494..765a2ad 100644 --- a/ShopAdmin/Components/Layout/MainLayout.razor +++ b/ShopAdmin/Components/Layout/MainLayout.razor @@ -17,7 +17,7 @@ - + diff --git a/ShopAdmin/Components/Pages/NavShelf.razor b/ShopAdmin/Components/Pages/NavShelf.razor index 490589d..b97e686 100644 --- a/ShopAdmin/Components/Pages/NavShelf.razor +++ b/ShopAdmin/Components/Pages/NavShelf.razor @@ -60,22 +60,24 @@ Notifications -
+
- - - Settings - - - - - Policies - - - - + + + + + Profile + + + + + + + + Logout + @@ -83,6 +85,24 @@ [Parameter] public bool IsOpen { get; set; } = false; [Parameter] public EventCallback 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; diff --git a/ShopAdmin/Components/Pages/NavShelf.razor.css b/ShopAdmin/Components/Pages/NavShelf.razor.css index d1f6e8b..fc4727d 100644 --- a/ShopAdmin/Components/Pages/NavShelf.razor.css +++ b/ShopAdmin/Components/Pages/NavShelf.razor.css @@ -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 */ diff --git a/ShopAdmin/Components/TopBarAuthstateView.razor b/ShopAdmin/Components/TopBarAuthstateView.razor index e50b75c..47aaadc 100644 --- a/ShopAdmin/Components/TopBarAuthstateView.razor +++ b/ShopAdmin/Components/TopBarAuthstateView.razor @@ -1,37 +1,67 @@ -
- @if (!IsAuthenticated) - { -
- - - - - - - - - - - - - - - - - -
- } - else - { -
-
- ADMIN_OPERATOR - ID: 409-CLUSTER +@using Microsoft.AspNetCore.Components.Authorization +
+ + +
+
+ @Name + @Email + @LoginTime +
-
- } + + +
+ + + + + + + + + + + + + + + + + +
+
+
@code { - [Parameter] public bool IsAuthenticated { get; set; } = false; + [CascadingParameter] + private Task? 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; + } + } + } } \ No newline at end of file diff --git a/ShopAdmin/Program.cs b/ShopAdmin/Program.cs index a145f05..34ce093 100644 --- a/ShopAdmin/Program.cs +++ b/ShopAdmin/Program.cs @@ -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() .AddInteractiveServerRenderMode(); diff --git a/ShopAdmin/wwwroot/app.css b/ShopAdmin/wwwroot/app.css index 5944cd6..c7838b2 100644 --- a/ShopAdmin/wwwroot/app.css +++ b/ShopAdmin/wwwroot/app.css @@ -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; } } diff --git a/litecharms-shopadmin-uat.yml b/litecharms-shopadmin-uat.yml index e1392cc..7586be7 100644 --- a/litecharms-shopadmin-uat.yml +++ b/litecharms-shopadmin-uat.yml @@ -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