From 8883b521e952e76d1d2cc9ffd7ec13610580b4ef Mon Sep 17 00:00:00 2001 From: Masu Baumgartner <68913099+Masu-Baumgartner@users.noreply.github.com> Date: Sat, 19 Oct 2024 16:37:37 +0200 Subject: [PATCH] Started implementing client and api server auth and the refresh endpoint --- .../Http/Controllers/Auth/AuthController.cs | 35 ++++++++++++++ .../Moonlight.ApiServer.csproj | 2 +- Moonlight.ApiServer/Program.cs | 6 +-- Moonlight.Client/Moonlight.Client.csproj | 2 +- Moonlight.Client/Program.cs | 46 +++++++++++++++++-- Moonlight.Client/UI/Layouts/MainLayout.razor | 3 +- .../Http/Requests/Auth/RefreshRequest.cs | 9 ++++ 7 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 Moonlight.Shared/Http/Requests/Auth/RefreshRequest.cs diff --git a/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs b/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs index 18c42d6f..fb7294e1 100644 --- a/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs +++ b/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using MoonCore.Exceptions; using MoonCore.Extended.Abstractions; using MoonCore.Extended.Helpers; using MoonCore.Extended.OAuth2.ApiServer; @@ -39,6 +40,40 @@ public class AuthController : Controller return Mapper.Map(data); } + [HttpPost("refresh")] + public async Task Refresh([FromBody] RefreshRequest request) + { + var authConfig = ConfigService.Get().Authentication; + + var tokenPair = await TokenHelper.RefreshPair( + request.RefreshToken, + authConfig.MlAccessSecret, + authConfig.MlRefreshSecret, + (refreshTokenData, newTokenData) => + { + if (!refreshTokenData.TryGetValue("userId", out var userIdStr) || !int.TryParse(userIdStr, out var userId)) + return false; + + var user = UserRepository.Get().FirstOrDefault(x => x.Id == userId); + + if (user == null) + return false; + + //TODO: External check + + newTokenData.Add("userId", user.Id.ToString()); + return true; + } + ); + + if (!tokenPair.HasValue) + throw new HttpApiException("Unable to refresh token", 401); + + Response.Cookies.Append("ml-access", tokenPair.Value.AccessToken); + Response.Cookies.Append("ml-refresh", tokenPair.Value.RefreshToken); + Response.Cookies.Append("ml-timestamp", DateTimeOffset.UtcNow.AddSeconds(3600).ToUnixTimeSeconds().ToString()); + } + [HttpGet("handle")] public async Task Handle([FromQuery(Name = "code")] string code) { diff --git a/Moonlight.ApiServer/Moonlight.ApiServer.csproj b/Moonlight.ApiServer/Moonlight.ApiServer.csproj index 2b7f0291..cd64af09 100644 --- a/Moonlight.ApiServer/Moonlight.ApiServer.csproj +++ b/Moonlight.ApiServer/Moonlight.ApiServer.csproj @@ -12,7 +12,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Moonlight.ApiServer/Program.cs b/Moonlight.ApiServer/Program.cs index 7803801a..91d8b189 100644 --- a/Moonlight.ApiServer/Program.cs +++ b/Moonlight.ApiServer/Program.cs @@ -135,7 +135,7 @@ if (config.Authentication.UseLocalOAuth2Service) builder.Services.AddTokenAuthentication(configuration => { - configuration.AccessSecret = config.Authentication.AccessSecret; + configuration.AccessSecret = config.Authentication.MlAccessSecret; configuration.DataLoader = async (data, provider, context) => { if (!data.TryGetValue("userId", out var userIdStr) || !int.TryParse(userIdStr, out var userId)) @@ -148,7 +148,7 @@ builder.Services.AddTokenAuthentication(configuration => return false; // OAuth2 - Check external - if (DateTime.UtcNow > user.RefreshTimestamp) + if (false && DateTime.UtcNow > user.RefreshTimestamp) { var tokenConsumer = new TokenConsumer(user.AccessToken, user.RefreshToken, user.RefreshTimestamp, async refreshToken => @@ -170,7 +170,7 @@ builder.Services.AddTokenAuthentication(configuration => }; }); - await tokenConsumer.GetAccessToken(); + //await tokenConsumer.GetAccessToken(); //TODO: API CALL (modular) } diff --git a/Moonlight.Client/Moonlight.Client.csproj b/Moonlight.Client/Moonlight.Client.csproj index 46d2787c..af90e3bd 100644 --- a/Moonlight.Client/Moonlight.Client.csproj +++ b/Moonlight.Client/Moonlight.Client.csproj @@ -10,7 +10,7 @@ - + diff --git a/Moonlight.Client/Program.cs b/Moonlight.Client/Program.cs index 696c47d9..9ac53c51 100644 --- a/Moonlight.Client/Program.cs +++ b/Moonlight.Client/Program.cs @@ -1,14 +1,20 @@ +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MoonCore.Blazor.Tailwind.Extensions; using MoonCore.Blazor.Tailwind.Forms; using MoonCore.Blazor.Tailwind.Forms.Components; +using MoonCore.Blazor.Tailwind.Services; using MoonCore.Extensions; using MoonCore.Helpers; +using MoonCore.Models; using MoonCore.PluginFramework.Services; using Moonlight.Client.Implementations; using Moonlight.Client.Interfaces; using Moonlight.Client.UI; +using Moonlight.Shared.Http.Requests.Auth; // Build pre run logger var providers = LoggerBuildHelper.BuildFromConfiguration(configuration => @@ -48,12 +54,44 @@ builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); -builder.Services.AddScoped(sp => new HttpApiClient(sp.GetRequiredService())); +builder.Services.AddScoped(sp => +{ + var httpClient = sp.GetRequiredService(); + var result = new HttpApiClient(httpClient); -builder.Services.AutoAddServices(); + result.UseBearerTokenConsumer(async () => + { + var cookieService = sp.GetRequiredService(); + + return new TokenConsumer( + await cookieService.GetValue("ml-access"), + await cookieService.GetValue("ml-refresh"), + DateTimeOffset.FromUnixTimeSeconds(long.Parse(await cookieService.GetValue("ml-timestamp"))).UtcDateTime, + async refreshToken => + { + await httpClient.PostAsync("api/auth/refresh", new StringContent( + JsonSerializer.Serialize(new RefreshRequest() + { + RefreshToken = refreshToken + }), new MediaTypeHeaderValue("application/json") + )); + + return new TokenPair() + { + AccessToken = await cookieService.GetValue("ml-access"), + RefreshToken = await cookieService.GetValue("ml-refresh") + }; + } + ); + }); + + return result; +}); builder.Services.AddMoonCoreBlazorTailwind(); +builder.Services.AutoAddServices(); + FormComponentRepository.Set(); FormComponentRepository.Set(); @@ -68,4 +106,6 @@ implementationService.Register(authUiHandler); builder.Services.AddSingleton(implementationService); -await builder.Build().RunAsync(); \ No newline at end of file +var app = builder.Build(); + +await app.RunAsync(); \ No newline at end of file diff --git a/Moonlight.Client/UI/Layouts/MainLayout.razor b/Moonlight.Client/UI/Layouts/MainLayout.razor index 423dbcbc..26080d7b 100644 --- a/Moonlight.Client/UI/Layouts/MainLayout.razor +++ b/Moonlight.Client/UI/Layouts/MainLayout.razor @@ -1,4 +1,5 @@ @using MoonCore.Exceptions +@using MoonCore.Extensions @using MoonCore.Helpers @using MoonCore.PluginFramework.Services @using Moonlight.Client.Interfaces @@ -75,7 +76,7 @@ else { if(!firstRender) return; - + await Load(); } diff --git a/Moonlight.Shared/Http/Requests/Auth/RefreshRequest.cs b/Moonlight.Shared/Http/Requests/Auth/RefreshRequest.cs new file mode 100644 index 00000000..a3379eff --- /dev/null +++ b/Moonlight.Shared/Http/Requests/Auth/RefreshRequest.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.Shared.Http.Requests.Auth; + +public class RefreshRequest +{ + [Required(ErrorMessage = "You need to provide a refresh token")] + public string RefreshToken { get; set; } +} \ No newline at end of file