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 - + @@ -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 =>