diff --git a/src/NexusReader.UI.Shared/Components/Atoms/NexusIcon.razor b/src/NexusReader.UI.Shared/Components/Atoms/NexusIcon.razor
index 4909fe2..3d73d8f 100644
--- a/src/NexusReader.UI.Shared/Components/Atoms/NexusIcon.razor
+++ b/src/NexusReader.UI.Shared/Components/Atoms/NexusIcon.razor
@@ -46,6 +46,9 @@
case "arrow-right":
break;
+ case "log-out":
+
+ break;
default:
diff --git a/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor b/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor
index 0c3f270..f603abb 100644
--- a/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor
+++ b/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor
@@ -2,6 +2,8 @@
@using NexusReader.Application.Abstractions.Services
@inject IFocusModeService FocusMode
@inject IKnowledgeService KnowledgeService
+@inject IIdentityService IdentityService
+@inject NavigationManager NavigationManager
@@ -56,6 +61,12 @@
}
}
+ private async Task HandleLogout()
+ {
+ await IdentityService.LogoutAsync();
+ NavigationManager.NavigateTo("/", true);
+ }
+
private Task HandleUpdate() => InvokeAsync(StateHasChanged);
public void Dispose()
diff --git a/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor.css b/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor.css
index eaa1e48..521ddd1 100644
--- a/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor.css
+++ b/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor.css
@@ -1,8 +1,8 @@
.intelligence-toolbar {
width: 50px;
height: 100%;
- background: #080808;
- border-right: 1px solid rgba(255, 255, 255, 0.03);
+ background: #0D0D0D;
+ border-right: 1px solid rgba(255, 255, 255, 0.08);
display: flex;
flex-direction: column;
justify-content: space-between;
@@ -10,6 +10,7 @@
align-items: center;
z-index: 20;
box-shadow: inset -2px 0 10px rgba(0,0,0,0.5);
+ backdrop-filter: blur(10px);
}
@@ -22,7 +23,7 @@
.toolbar-item {
background: none;
border: none;
- color: #444;
+ color: #555;
cursor: pointer;
width: 34px;
height: 34px;
@@ -37,11 +38,15 @@
.toolbar-item:hover {
color: var(--nexus-neon);
background: rgba(0, 255, 153, 0.05);
+ box-shadow: 0 0 15px rgba(0, 255, 153, 0.15);
+ filter: drop-shadow(0 0 5px var(--nexus-neon));
}
.toolbar-item.active {
color: var(--nexus-neon);
background: rgba(0, 255, 153, 0.08);
+ box-shadow: 0 0 20px rgba(0, 255, 153, 0.25);
+ filter: drop-shadow(0 0 8px var(--nexus-neon));
}
.toolbar-item.active::after {
diff --git a/src/NexusReader.UI.Shared/Layout/MainLayout.razor b/src/NexusReader.UI.Shared/Layout/MainLayout.razor
index 2092401..5b3bd3a 100644
--- a/src/NexusReader.UI.Shared/Layout/MainLayout.razor
+++ b/src/NexusReader.UI.Shared/Layout/MainLayout.razor
@@ -35,10 +35,7 @@
Asystent AI
-
- @context.User.Identity?.Name
-
-
+
@@ -93,11 +90,7 @@
}
}
- private async Task HandleLogout()
- {
- await IdentityService.LogoutAsync();
- NavigationManager.NavigateTo("/", true);
- }
+
protected override async Task OnAfterRenderAsync(bool firstRender)
{
diff --git a/src/NexusReader.Web.Client/Handlers/AuthenticationHeaderHandler.cs b/src/NexusReader.Web.Client/Handlers/AuthenticationHeaderHandler.cs
new file mode 100644
index 0000000..3772810
--- /dev/null
+++ b/src/NexusReader.Web.Client/Handlers/AuthenticationHeaderHandler.cs
@@ -0,0 +1,27 @@
+using System.Net.Http.Headers;
+using NexusReader.Application.Abstractions.Services;
+
+namespace NexusReader.Web.Client.Handlers;
+
+public class AuthenticationHeaderHandler : DelegatingHandler
+{
+ private readonly INativeStorageService _storageService;
+ private const string TokenKey = "nexus_auth_token";
+
+ public AuthenticationHeaderHandler(INativeStorageService storageService)
+ {
+ _storageService = storageService;
+ }
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ var tokenResult = await _storageService.GetSecureString(TokenKey);
+
+ if (tokenResult.IsSuccess && !string.IsNullOrEmpty(tokenResult.Value))
+ {
+ request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenResult.Value);
+ }
+
+ return await base.SendAsync(request, cancellationToken);
+ }
+}
diff --git a/src/NexusReader.Web.Client/NexusReader.Web.Client.csproj b/src/NexusReader.Web.Client/NexusReader.Web.Client.csproj
index d806b42..8046dc7 100644
--- a/src/NexusReader.Web.Client/NexusReader.Web.Client.csproj
+++ b/src/NexusReader.Web.Client/NexusReader.Web.Client.csproj
@@ -12,6 +12,7 @@
+
diff --git a/src/NexusReader.Web.Client/Program.cs b/src/NexusReader.Web.Client/Program.cs
index 56034d7..736517f 100644
--- a/src/NexusReader.Web.Client/Program.cs
+++ b/src/NexusReader.Web.Client/Program.cs
@@ -33,7 +33,14 @@ builder.Services.AddCascadingAuthenticationState();
// AI & Content Services
builder.Services.AddScoped();
-builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+
+builder.Services.AddTransient();
+builder.Services.AddHttpClient("NexusAPI", client =>
+{
+ client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
+}).AddHttpMessageHandler();
+
+builder.Services.AddScoped(sp => sp.GetRequiredService().CreateClient("NexusAPI"));
// Dummy registrations for server-only handlers to satisfy DI validation
builder.Services.AddSingleton>(new ThrowingDbContextFactory());
diff --git a/src/NexusReader.Web.New/Program.cs b/src/NexusReader.Web.New/Program.cs
index b335169..82cd785 100644
--- a/src/NexusReader.Web.New/Program.cs
+++ b/src/NexusReader.Web.New/Program.cs
@@ -96,6 +96,18 @@ builder.Services.ConfigureApplicationCookie(options =>
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = true;
+ options.Events.OnRedirectToLogin = context =>
+ {
+ if (context.Request.Path.StartsWithSegments("/api"))
+ {
+ context.Response.StatusCode = 401;
+ }
+ else
+ {
+ context.Response.Redirect(context.RedirectUri);
+ }
+ return Task.CompletedTask;
+ };
});
builder.Services.Configure(options =>