Adjusting to use new extension methods to configure and handle token auth and oauth2
This commit is contained in:
@@ -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}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,4 +31,8 @@
|
|||||||
<Folder Include="wwwroot\css\" />
|
<Folder Include="wwwroot\css\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<_ContentIncludedByDefault Remove="UI\Screens\AuthenticationScreen.razor" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -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,20 +25,15 @@ public class IdentityService
|
|||||||
|
|
||||||
public async Task Check()
|
public async Task Check()
|
||||||
{
|
{
|
||||||
try
|
IsLoggedIn = false;
|
||||||
{
|
|
||||||
var response = await HttpApiClient.GetJson<CheckResponse>("api/auth/check");
|
|
||||||
|
|
||||||
Username = response.Username;
|
var response = await HttpApiClient.GetJson<CheckResponse>("api/auth/check");
|
||||||
Email = response.Email;
|
|
||||||
Permissions = response.Permissions;
|
|
||||||
|
|
||||||
IsLoggedIn = true;
|
Username = response.Username;
|
||||||
}
|
Email = response.Email;
|
||||||
catch (HttpApiException)
|
Permissions = response.Permissions;
|
||||||
{
|
|
||||||
IsLoggedIn = false;
|
IsLoggedIn = true;
|
||||||
}
|
|
||||||
|
|
||||||
//await OnStateChanged?.Invoke();
|
//await OnStateChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
@using Moonlight.Client.UI.Layouts
|
@using Moonlight.Client.UI.Layouts
|
||||||
|
@using MoonCore.Blazor.Components
|
||||||
|
@using Moonlight.Client.UI.Components
|
||||||
|
|
||||||
<Router AppAssembly="@typeof(App).Assembly">
|
<ErrorLogger>
|
||||||
<Found Context="routeData">
|
<OAuth2AuthenticationHandler>
|
||||||
<CascadingValue Name="TargetPageType" Value="routeData.PageType">
|
<Router AppAssembly="@typeof(App).Assembly">
|
||||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
<Found Context="routeData">
|
||||||
</CascadingValue>
|
<CascadingValue Name="TargetPageType" Value="routeData.PageType">
|
||||||
</Found>
|
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||||
<NotFound>
|
</CascadingValue>
|
||||||
<PageTitle>Not found</PageTitle>
|
</Found>
|
||||||
<LayoutView Layout="@typeof(MainLayout)">
|
<NotFound>
|
||||||
<div class="flex flex-col justify-center text-center">
|
<PageTitle>Not found</PageTitle>
|
||||||
<img class="h-48 mt-5 mb-3" src="/svg/notfound.svg" alt="Not found illustration"/>
|
<LayoutView Layout="@typeof(MainLayout)">
|
||||||
|
<div class="flex flex-col justify-center text-center">
|
||||||
|
<img class="h-48 mt-5 mb-3" src="/svg/notfound.svg" alt="Not found illustration"/>
|
||||||
|
|
||||||
<h3 class="mt-2 font-semibold text-white text-lg">Page not found</h3>
|
<h3 class="mt-2 font-semibold text-white text-lg">Page not found</h3>
|
||||||
<p class="mt-1 text-gray-300">
|
<p class="mt-1 text-gray-300">
|
||||||
The page you requested does not exist
|
The page you requested does not exist
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</LayoutView>
|
</LayoutView>
|
||||||
</NotFound>
|
</NotFound>
|
||||||
</Router>
|
</Router>
|
||||||
|
</OAuth2AuthenticationHandler>
|
||||||
|
</ErrorLogger>
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
||||||
|
<PermissionHandler CheckFunction="CheckPermission">
|
||||||
<TestyHmm>
|
<CascadingValue Value="this" IsFixed="true">
|
||||||
<PermissionHandler CheckFunction="CheckPermission">
|
@Body
|
||||||
<CascadingValue Value="this" IsFixed="true">
|
</CascadingValue>
|
||||||
@Body
|
</PermissionHandler>
|
||||||
</CascadingValue>
|
|
||||||
</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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user