Adjusting to use new extension methods to configure and handle token auth and oauth2

This commit is contained in:
Masu-Baumgartner
2024-11-06 16:46:24 +01:00
parent 288b0c8d97
commit f2d653563c
8 changed files with 63 additions and 330 deletions

View File

@@ -9,6 +9,17 @@
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
}, },
"hotReloadEnabled": true "hotReloadEnabled": true
},
"WASM Debug": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5165",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"hotReloadEnabled": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}"
} }
} }
} }

View File

@@ -1,8 +1,6 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using MoonCore.Blazor.Helpers;
using Moonlight.Client.Interfaces; using Moonlight.Client.Interfaces;
using Moonlight.Client.Services; using Moonlight.Client.Services;
using Moonlight.Client.UI.Screens;
namespace Moonlight.Client.Implementations; namespace Moonlight.Client.Implementations;
@@ -11,18 +9,12 @@ public class AuthenticationUiHandler : IAppLoader, IAppScreen
public int Priority => 0; public int Priority => 0;
public Task<bool> ShouldRender(IServiceProvider serviceProvider) public Task<bool> ShouldRender(IServiceProvider serviceProvider)
{ => Task.FromResult(false);
return Task.FromResult<bool>(false);
var identityService = serviceProvider.GetRequiredService<IdentityService>();
return Task.FromResult(!identityService.IsLoggedIn); // Only show the screen when we are not logged in public RenderFragment Render() => throw new NotImplementedException();
}
public RenderFragment Render() => ComponentHelper.FromType<AuthenticationScreen>();
public async Task Load(IServiceProvider serviceProvider) public async Task Load(IServiceProvider serviceProvider)
{ {
return;
var identityService = serviceProvider.GetRequiredService<IdentityService>(); var identityService = serviceProvider.GetRequiredService<IdentityService>();
await identityService.Check(); await identityService.Check();
} }

View File

@@ -31,4 +31,8 @@
<Folder Include="wwwroot\css\" /> <Folder Include="wwwroot\css\" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="UI\Screens\AuthenticationScreen.razor" />
</ItemGroup>
</Project> </Project>

View File

@@ -9,10 +9,10 @@ namespace Moonlight.Client.Services;
[Scoped] [Scoped]
public class IdentityService public class IdentityService
{ {
public string Username { get; private set; } public string Username { get; private set; } = "";
public string Email { get; private set; } public string Email { get; private set; } = "";
public string[] Permissions { get; private set; } public string[] Permissions { get; private set; } = [];
public bool IsLoggedIn { get; private set; } public bool IsLoggedIn { get; private set; } = false;
private readonly HttpApiClient HttpApiClient; private readonly HttpApiClient HttpApiClient;
private readonly LocalStorageService LocalStorageService; private readonly LocalStorageService LocalStorageService;
@@ -25,8 +25,8 @@ public class IdentityService
public async Task Check() public async Task Check()
{ {
try IsLoggedIn = false;
{
var response = await HttpApiClient.GetJson<CheckResponse>("api/auth/check"); var response = await HttpApiClient.GetJson<CheckResponse>("api/auth/check");
Username = response.Username; Username = response.Username;
@@ -34,11 +34,6 @@ public class IdentityService
Permissions = response.Permissions; Permissions = response.Permissions;
IsLoggedIn = true; IsLoggedIn = true;
}
catch (HttpApiException)
{
IsLoggedIn = false;
}
//await OnStateChanged?.Invoke(); //await OnStateChanged?.Invoke();
} }

View File

@@ -1,5 +1,9 @@
@using Moonlight.Client.UI.Layouts @using Moonlight.Client.UI.Layouts
@using MoonCore.Blazor.Components
@using Moonlight.Client.UI.Components
<ErrorLogger>
<OAuth2AuthenticationHandler>
<Router AppAssembly="@typeof(App).Assembly"> <Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData"> <Found Context="routeData">
<CascadingValue Name="TargetPageType" Value="routeData.PageType"> <CascadingValue Name="TargetPageType" Value="routeData.PageType">
@@ -20,3 +24,5 @@
</LayoutView> </LayoutView>
</NotFound> </NotFound>
</Router> </Router>
</OAuth2AuthenticationHandler>
</ErrorLogger>

View File

@@ -1,78 +0,0 @@
@using Microsoft.AspNetCore.WebUtilities
@using MoonCore.Blazor.Services
@using MoonCore.Exceptions
@using MoonCore.Helpers
@inherits ErrorBoundaryBase
@inject NavigationManager Navigation
@inject OAuth2FrontendService OAuth2FrontendService
@if (IsCompleting)
{
}
else
{
@ChildContent
}
@code
{
private bool IsCompleting = false;
private string Code;
protected override void OnInitialized()
{
var uri = new Uri(Navigation.Uri);
if (!QueryHelpers.ParseQuery(uri.Query).TryGetValue("code", out var codeSv))
return;
try
{
if (codeSv.Count == 0 || string.IsNullOrEmpty(codeSv.First()))
return;
var code = codeSv.First();
Code = code!;
IsCompleting = true;
}
catch (Exception e)
{
Console.WriteLine("FUUCK");
Console.WriteLine(e);
throw;
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
return;
if(!IsCompleting)
return;
if (!await OAuth2FrontendService.Complete(Code))
await RedirectToAuth();
IsCompleting = false;
await InvokeAsync(StateHasChanged);
}
protected override async Task OnErrorAsync(Exception exception)
{
if (exception is not HttpApiException httpApiException || httpApiException.Status != 401)
throw exception;
// If we reach this point, we got a 401 unauthenticated, so we need to log in
await RedirectToAuth();
}
private async Task RedirectToAuth()
{
var url = await OAuth2FrontendService.Start();
Navigation.NavigateTo(url, true);
}
}

View File

@@ -35,16 +35,12 @@ else
<main class="py-10"> <main class="py-10">
<div class="px-4 sm:px-6 lg:px-8"> <div class="px-4 sm:px-6 lg:px-8">
<ErrorHandler CustomHandler="HandleException"> <ErrorHandler>
<TestyHmm>
<PermissionHandler CheckFunction="CheckPermission"> <PermissionHandler CheckFunction="CheckPermission">
<CascadingValue Value="this" IsFixed="true"> <CascadingValue Value="this" IsFixed="true">
@Body @Body
</CascadingValue> </CascadingValue>
</PermissionHandler> </PermissionHandler>
</TestyHmm>
</ErrorHandler> </ErrorHandler>
</div> </div>
</main> </main>
@@ -110,6 +106,10 @@ else
Logger.LogDebug("Running application loader '{name}'", loader.GetType().Name); Logger.LogDebug("Running application loader '{name}'", loader.GetType().Name);
await loader.Load(ServiceProvider); await loader.Load(ServiceProvider);
} }
catch (HttpApiException)
{
throw;
}
catch (Exception e) catch (Exception e)
{ {
Logger.LogCritical("An app loader threw an unhandled exception: {e}", e); Logger.LogCritical("An app loader threw an unhandled exception: {e}", e);
@@ -139,15 +139,4 @@ else
} }
private bool CheckPermission(string permission) => IdentityService.HasPermission(permission); private bool CheckPermission(string permission) => IdentityService.HasPermission(permission);
private Task<bool> HandleException(Exception exception, ErrorHandler handler)
{
if (exception is HttpApiException httpApiException && httpApiException.Status == 401)
{
Task.Run(Load);
return Task.FromResult(true);
}
return Task.FromResult(false);
}
} }

View File

@@ -1,186 +0,0 @@
@page "/auth"
@using MoonCore.Blazor.Services
@using MoonCore.Helpers
@using Moonlight.Client.Services
@using Moonlight.Shared.Http.Requests.Auth
@using Moonlight.Shared.Http.Responses.Auth
@inject NavigationManager Navigation
@inject HttpApiClient HttpApiClient
@inject WindowService WindowService
@inject HttpClient HttpClient
@inject LocalStorageService LocalStorageService
@inject ILogger<AuthenticationScreen> Logger
@if (Code == null)
{
<div class="h-full w-full min-h-[100dvh] flex items-center justify-center px-5 md:px-0">
<div class="card card-body w-full max-w-lg">
@if (IsAuthenticating)
{
<div class="sm:mx-auto sm:w-full sm:max-w-sm mb-5">
<h2 class="mt-6 text-center text-2xl font-bold leading-9 tracking-tight text-gray-100">Login flow started in new window/tab</h2>
</div>
<div class="flex justify-center">
<div id="loader" class="my-10"></div>
</div>
}
else
{
<div class="sm:mx-auto sm:w-full sm:max-w-sm mb-5">
<img class="mx-auto h-16 w-auto" src="/logolong.webp" alt="Moonlight">
<h2 class="mt-6 text-center text-2xl font-bold leading-9 tracking-tight text-gray-100">Login to access your account</h2>
</div>
<div class="flex justify-center">
<WButton OnClick="StartAuth" CssClasses="btn btn-primary">
Proceed to login
<i class="bi bi-arrow-right"></i>
</WButton>
</div>
}
</div>
</div>
}
else
{
<div class="h-full w-full min-h-[100dvh] flex items-center justify-center px-5 md:px-0">
<div class="card card-body w-full max-w-lg">
@if (IsHandlingDone)
{
<div class="sm:mx-auto sm:w-full sm:max-w-sm mb-5">
<img class="mx-auto w-auto h-32" src="/svg/completed.svg" alt="Completed illustration">
<h2 class="mt-6 text-center text-2xl font-semibold leading-9 tracking-tight text-gray-100">
Login successful. You can close this window now
</h2>
</div>
}
else
{
<div class="flex justify-center">
<div id="loader"></div>
</div>
}
</div>
</div>
}
@code
{
[SupplyParameterFromQuery(Name = "code")]
[Parameter]
public string? Code { get; set; }
private bool IsAuthenticating = false;
private bool IsHandlingDone = false;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
return;
if (Code == null)
return;
try
{
var authHandleData = await HttpApiClient.PostJson<OAuth2HandleResponse>("api/auth", new OAuth2HandleRequest()
{
Code = Code
});
// Save the auth handle data
await LocalStorageService.SetString("AccessToken", authHandleData.AccessToken);
await LocalStorageService.SetString("RefreshToken", authHandleData.RefreshToken);
await LocalStorageService.Set("ExpiresAt", authHandleData.ExpiresAt);
// Update UI
IsHandlingDone = true;
await InvokeAsync(StateHasChanged);
try
{
await WindowService.Close();
}
finally
{
await Task.Delay(TimeSpan.FromSeconds(2));
Navigation.NavigateTo("/", true);
}
}
catch (Exception e)
{
Logger.LogError("An unhandled error occured while handling oauth2 code: {e}", e);
}
}
private async Task StartAuth(WButton _)
{
var authStartData = await HttpApiClient.GetJson<OAuth2StartResponse>("api/auth");
var uri = authStartData.Endpoint
+ $"?client_id={authStartData.ClientId}" +
$"&redirect_uri={authStartData.RedirectUri}" +
$"&response_type=code";
Navigation.NavigateTo(uri, true);
return;
// TODO: Make this configurable
try
{
await WindowService.Open(
uri,
"OAuth2 Flow",
600,
470
);
IsAuthenticating = true;
await InvokeAsync(StateHasChanged);
Task.Run(async () =>
{
while (true)
{
await Task.Delay(1000);
try
{
if(!await LocalStorageService.ContainsKey("AccessToken"))
continue;
if (HttpClient.DefaultRequestHeaders.Contains("Authorization"))
HttpClient.DefaultRequestHeaders.Remove("Authorization");
HttpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + await LocalStorageService.GetString("AccessToken"));
var res = await HttpClient.GetAsync("api/auth/check");
if (res.IsSuccessStatusCode)
break;
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
Navigation.NavigateTo(Navigation.Uri, true);
});
}
catch (Exception)
{
Navigation.NavigateTo(uri, true);
}
}
}