diff --git a/Moonlight.ApiServer/Configuration/AppConfiguration.cs b/Moonlight.ApiServer/Configuration/AppConfiguration.cs index 04399c8f..8d5aded2 100644 --- a/Moonlight.ApiServer/Configuration/AppConfiguration.cs +++ b/Moonlight.ApiServer/Configuration/AppConfiguration.cs @@ -1,7 +1,11 @@ -namespace Moonlight.ApiServer.Configuration; +using MoonCore.Helpers; + +namespace Moonlight.ApiServer.Configuration; public class AppConfiguration { + public string PublicUrl { get; set; } = "http://localhost:5165"; + public DatabaseConfig Database { get; set; } = new(); public AuthenticationConfig Authentication { get; set; } = new(); public DevelopmentConfig Development { get; set; } = new(); @@ -19,12 +23,25 @@ public class AppConfiguration public class AuthenticationConfig { - public string Secret { get; set; } = Guid - .NewGuid() - .ToString() - .Replace("-", ""); + public string MlAccessSecret { get; set; } = Formatter.GenerateString(32); + public string MlRefreshSecret { get; set; } = Formatter.GenerateString(32); + + public string Secret { get; set; } = Formatter.GenerateString(32); public int TokenDuration { get; set; } = 10; + + public bool UseLocalOAuth2Service { get; set; } = true; + public string AccessSecret { get; set; } = Formatter.GenerateString(32); + public string RefreshSecret { get; set; } = Formatter.GenerateString(32); + public string ClientId { get; set; } = Formatter.GenerateString(8); + public string ClientSecret { get; set; } = Formatter.GenerateString(32); + public string? AuthorizationUri { get; set; } + public string? AuthorizationRedirect { get; set; } + public string? AccessEndpoint { get; set; } + public string? RefreshEndpoint { get; set; } + + // Local OAuth2 Service + public string CodeSecret { get; set; } = Formatter.GenerateString(32); } public class DevelopmentConfig diff --git a/Moonlight.ApiServer/Database/Entities/User.cs b/Moonlight.ApiServer/Database/Entities/User.cs index b4e6ac5b..182a43a4 100644 --- a/Moonlight.ApiServer/Database/Entities/User.cs +++ b/Moonlight.ApiServer/Database/Entities/User.cs @@ -10,4 +10,8 @@ public class User public DateTime TokenValidTimestamp { get; set; } = DateTime.UtcNow; public string PermissionsJson { get; set; } = "[]"; + + public string AccessToken { get; set; } = ""; + public string RefreshToken { get; set; } = ""; + public DateTime RefreshTimestamp { get; set; } = DateTime.UtcNow; } \ No newline at end of file diff --git a/Moonlight.ApiServer/Database/Migrations/20241017214600_AddedAccessAndRefreshTokenFields.Designer.cs b/Moonlight.ApiServer/Database/Migrations/20241017214600_AddedAccessAndRefreshTokenFields.Designer.cs new file mode 100644 index 00000000..c81eb31c --- /dev/null +++ b/Moonlight.ApiServer/Database/Migrations/20241017214600_AddedAccessAndRefreshTokenFields.Designer.cs @@ -0,0 +1,74 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.ApiServer.Database; + +#nullable disable + +namespace Moonlight.ApiServer.Database.Migrations +{ + [DbContext(typeof(CoreDataContext))] + [Migration("20241017214600_AddedAccessAndRefreshTokenFields")] + partial class AddedAccessAndRefreshTokenFields + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Core") + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AccessToken") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PermissionsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RefreshTimestamp") + .HasColumnType("datetime(6)"); + + b.Property("RefreshToken") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TokenValidTimestamp") + .HasColumnType("datetime(6)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Users", "Core"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight.ApiServer/Database/Migrations/20241017214600_AddedAccessAndRefreshTokenFields.cs b/Moonlight.ApiServer/Database/Migrations/20241017214600_AddedAccessAndRefreshTokenFields.cs new file mode 100644 index 00000000..d815cc14 --- /dev/null +++ b/Moonlight.ApiServer/Database/Migrations/20241017214600_AddedAccessAndRefreshTokenFields.cs @@ -0,0 +1,58 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Moonlight.ApiServer.Database.Migrations +{ + /// + public partial class AddedAccessAndRefreshTokenFields : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AccessToken", + schema: "Core", + table: "Users", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "RefreshTimestamp", + schema: "Core", + table: "Users", + type: "datetime(6)", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "RefreshToken", + schema: "Core", + table: "Users", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AccessToken", + schema: "Core", + table: "Users"); + + migrationBuilder.DropColumn( + name: "RefreshTimestamp", + schema: "Core", + table: "Users"); + + migrationBuilder.DropColumn( + name: "RefreshToken", + schema: "Core", + table: "Users"); + } + } +} diff --git a/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs b/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs index 7c3a82da..45d9f8b5 100644 --- a/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs +++ b/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs @@ -31,6 +31,10 @@ namespace Moonlight.ApiServer.Database.Migrations MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("AccessToken") + .IsRequired() + .HasColumnType("longtext"); + b.Property("Email") .IsRequired() .HasColumnType("longtext"); @@ -43,6 +47,13 @@ namespace Moonlight.ApiServer.Database.Migrations .IsRequired() .HasColumnType("longtext"); + b.Property("RefreshTimestamp") + .HasColumnType("datetime(6)"); + + b.Property("RefreshToken") + .IsRequired() + .HasColumnType("longtext"); + b.Property("TokenValidTimestamp") .HasColumnType("datetime(6)"); diff --git a/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs b/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs index 55bf5154..18c42d6f 100644 --- a/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs +++ b/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs @@ -1,5 +1,12 @@ using Microsoft.AspNetCore.Mvc; +using MoonCore.Extended.Abstractions; +using MoonCore.Extended.Helpers; +using MoonCore.Extended.OAuth2.ApiServer; +using MoonCore.Helpers; +using MoonCore.Services; using Moonlight.ApiServer.Attributes; +using Moonlight.ApiServer.Configuration; +using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Helpers.Authentication; using Moonlight.ApiServer.Services; using Moonlight.Shared.Http.Requests.Auth; @@ -11,39 +18,58 @@ namespace Moonlight.ApiServer.Http.Controllers.Auth; [Route("api/auth")] public class AuthController : Controller { - private readonly AuthService AuthService; + private readonly OAuth2Service OAuth2Service; + private readonly TokenHelper TokenHelper; + private readonly ConfigService ConfigService; + private readonly DatabaseRepository UserRepository; - public AuthController(AuthService authService) + public AuthController(OAuth2Service oAuth2Service, TokenHelper tokenHelper, DatabaseRepository userRepository, ConfigService configService) { - AuthService = authService; + OAuth2Service = oAuth2Service; + TokenHelper = tokenHelper; + UserRepository = userRepository; + ConfigService = configService; } - [HttpPost("login")] - public async Task Login([FromBody] LoginRequest request) + [HttpGet("start")] + public async Task Start() { - var user = await AuthService.Login(request.Email, request.Password); + var data = await OAuth2Service.StartAuthorizing(); - return new LoginResponse() - { - Token = await AuthService.GenerateToken(user) - }; + return Mapper.Map(data); } - [HttpPost("register")] - public async Task Register([FromBody] RegisterRequest request) + [HttpGet("handle")] + public async Task Handle([FromQuery(Name = "code")] string code) { - var user = await AuthService.Register( - request.Username, - request.Email, - request.Password - ); + //TODO: Validate jwt syntax - return new RegisterResponse() + var accessData = await OAuth2Service.RequestAccess(code); + + //TODO: Add modular oauth2 consumer system + var userId = 1; + + var user = UserRepository.Get().First(x => x.Id == userId); + + user.AccessToken = accessData.AccessToken; + user.RefreshToken = accessData.RefreshToken; + user.RefreshTimestamp = DateTime.UtcNow.AddSeconds(accessData.ExpiresIn); + + UserRepository.Update(user); + + var authConfig = ConfigService.Get().Authentication; + var tokenPair = await TokenHelper.GeneratePair(authConfig.MlAccessSecret, authConfig.MlAccessSecret, data => { - Token = await AuthService.GenerateToken(user) - }; + data.Add("userId", user.Id.ToString()); + }); + + Response.Cookies.Append("ml-access", tokenPair.AccessToken); + Response.Cookies.Append("ml-refresh", tokenPair.RefreshToken); + Response.Cookies.Append("ml-timestamp", DateTimeOffset.UtcNow.AddSeconds(3600).ToUnixTimeSeconds().ToString()); + + Response.Redirect("/"); } - + [HttpGet("check")] [RequirePermission("meta.authenticated")] public async Task Check() diff --git a/Moonlight.ApiServer/Http/Controllers/OAuth2/OAuth2Controller.cs b/Moonlight.ApiServer/Http/Controllers/OAuth2/OAuth2Controller.cs new file mode 100644 index 00000000..62de5042 --- /dev/null +++ b/Moonlight.ApiServer/Http/Controllers/OAuth2/OAuth2Controller.cs @@ -0,0 +1,133 @@ +using Microsoft.AspNetCore.Mvc; +using MoonCore.Exceptions; +using MoonCore.Extended.Abstractions; +using MoonCore.Extended.OAuth2.AuthServer; +using MoonCore.Extended.OAuth2.Models; +using MoonCore.Services; +using Moonlight.ApiServer.Configuration; +using Moonlight.ApiServer.Database.Entities; +using Moonlight.ApiServer.Services; + +namespace Moonlight.ApiServer.Http.Controllers.OAuth2; + +[ApiController] +[Route("oauth2")] +public class OAuth2Controller : Controller +{ + private readonly OAuth2Service OAuth2Service; + private readonly AuthService AuthService; + private readonly DatabaseRepository UserRepository; + private readonly ConfigService ConfigService; + + public OAuth2Controller(OAuth2Service oAuth2Service, ConfigService configService, + AuthService authService, DatabaseRepository userRepository) + { + OAuth2Service = oAuth2Service; + ConfigService = configService; + AuthService = authService; + UserRepository = userRepository; + } + + [HttpGet("authorize")] + public async Task Authorize( + [FromQuery(Name = "response_type")] string responseType, + [FromQuery(Name = "client_id")] string clientId, + [FromQuery(Name = "redirect_uri")] string redirectUri + ) + { + if (responseType != "code") + throw new HttpApiException("Invalid response type", 400); + + var config = ConfigService.Get(); + + // TODO: This call should be handled by the OAuth2Service + if (clientId != config.Authentication.ClientId) + throw new HttpApiException("Invalid client id", 400); + + if (redirectUri != (config.Authentication.AuthorizationRedirect ?? $"{config.PublicUrl}/api/auth/handle")) + throw new HttpApiException("Invalid redirect uri", 400); + + Response.StatusCode = 200; + await Response.WriteAsync( + "

Login lol


" + + "
" + + "
" + + "
" + + "" + + "
" + + "
" + + "" + + "
" + + "
" + + "" + + "
" + ); + } + + [HttpPost("authorize")] + public async Task AuthorizePost( + [FromQuery(Name = "response_type")] string responseType, + [FromQuery(Name = "client_id")] string clientId, + [FromQuery(Name = "redirect_uri")] string redirectUri, + [FromForm(Name = "email")] string email, + [FromForm(Name = "password")] string password + ) + { + if (responseType != "code") + throw new HttpApiException("Invalid response type", 400); + + var config = ConfigService.Get(); + + // TODO: This call should be handled by the OAuth2Service + if (clientId != config.Authentication.ClientId) + throw new HttpApiException("Invalid client id", 400); + + if (redirectUri != (config.Authentication.AuthorizationRedirect ?? $"{config.PublicUrl}/api/auth/handle")) + throw new HttpApiException("Invalid redirect uri", 400); + + var user = await AuthService.Login(email, password); + + var code = await OAuth2Service.GenerateCode(data => { data.Add("userId", user.Id.ToString()); }); + + var redirectUrl = redirectUri + + $"?code={code}"; + + Response.Redirect(redirectUrl); + } + + [HttpPost("access")] + public async Task Access( + [FromForm(Name = "client_id")] string clientId, + [FromForm(Name = "client_secret")] string clientSecret, + [FromForm(Name = "redirect_uri")] string redirectUri, + [FromForm(Name = "grant_type")] string grantType, + [FromForm(Name = "code")] string code + ) + { + if (grantType != "authorization_code") + throw new HttpApiException("Invalid grant type", 400); + + User? user = null; + + var access = await OAuth2Service.ValidateAccess(clientId, clientSecret, redirectUri, code, data => + { + if (!data.TryGetValue("userId", out var userIdStr)) + return false; + + if (!int.TryParse(userIdStr, out var userId)) + return false; + + user = UserRepository.Get().FirstOrDefault(x => x.Id == userId); + + return user != null; + }, data => + { + data.Add("userId", user!.Id.ToString()); + }); + + if (access == null) + throw new HttpApiException("Unable to validate access", 400); + + return access; + } +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Middleware/AuthenticationMiddleware.cs b/Moonlight.ApiServer/Http/Middleware/AuthenticationMiddleware.cs index 25d15f9b..f4b30294 100644 --- a/Moonlight.ApiServer/Http/Middleware/AuthenticationMiddleware.cs +++ b/Moonlight.ApiServer/Http/Middleware/AuthenticationMiddleware.cs @@ -1,6 +1,8 @@ using System.Text.Json; using MoonCore.Extended.Abstractions; using MoonCore.Extended.Helpers; +using MoonCore.Extended.Models; +using MoonCore.Extended.OAuth2.ApiServer; using MoonCore.Services; using Moonlight.ApiServer.Configuration; using Moonlight.ApiServer.Database.Entities; @@ -28,12 +30,88 @@ public class AuthenticationMiddleware private async Task Authenticate(HttpContext context) { var request = context.Request; + + if (!request.Cookies.TryGetValue("ml-access", out var accessToken) || + !request.Cookies.TryGetValue("ml-refresh", out var refreshToken)) + return; + + if(string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(refreshToken)) + return; + + // TODO: Validate if both are valid jwts (maybe) + + // + var tokenHelper = context.RequestServices.GetRequiredService(); + var configService = context.RequestServices.GetRequiredService>(); + + User? user = null; + + if (!await tokenHelper.IsValidAccessToken(accessToken, configService.Get().Authentication.MlAccessSecret, + data => + { + if (!data.TryGetValue("userId", out var userIdStr)) + return false; + + if (!int.TryParse(userIdStr, out var userId)) + return false; + + var userRepo = context.RequestServices.GetRequiredService>(); + + user = userRepo.Get().FirstOrDefault(x => x.Id == userId); + + return user != null; + })) + { + return; + } + + if(user == null) + return; + + // Validate external access + if (DateTime.UtcNow > user.RefreshTimestamp) + { + var tokenConsumer = new TokenConsumer(user.AccessToken, user.RefreshToken, user.RefreshTimestamp, + async refreshToken => + { + var oauth2Service = context.RequestServices.GetRequiredService(); + + var accessData = await oauth2Service.RefreshAccess(refreshToken); + + user.AccessToken = accessData.AccessToken; + user.RefreshToken = accessData.RefreshToken; + user.RefreshTimestamp = DateTime.UtcNow.AddSeconds(accessData.ExpiresIn); + + var userRepo = context.RequestServices.GetRequiredService>(); + + userRepo.Update(user); + + return new TokenPair() + { + AccessToken = user.AccessToken, + RefreshToken = user.RefreshToken + }; + }); + + await tokenConsumer.GetAccessToken(); + //TODO: API CALL + } + + // Load permissions, handle empty values + var permissions = JsonSerializer.Deserialize( + string.IsNullOrEmpty(user.PermissionsJson) ? "[]" : user.PermissionsJson + ) ?? []; + + // Save permission state + context.User = new PermClaimsPrinciple(permissions, user); + + /* string? token = null; // Cookie for Moonlight.Client if (request.Cookies.ContainsKey("token") && !string.IsNullOrEmpty(request.Cookies["token"])) token = request.Cookies["token"]; - + // Header for api clients if (request.Headers.ContainsKey("Authorization") && !string.IsNullOrEmpty(request.Cookies["Authorization"])) { @@ -47,10 +125,10 @@ public class AuthenticationMiddleware token = headerParts[1]; } } - + if(token == null) return; - + // Validate token if (token.Length > 300) { @@ -62,7 +140,7 @@ public class AuthenticationMiddleware if (token.Count(x => x == '.') == 2) // JWT only has two dots await AuthenticateUser(context, token); else - await AuthenticateApiKey(context, token); + await AuthenticateApiKey(context, token);*/ } private async Task AuthenticateUser(HttpContext context, string jwt) @@ -70,13 +148,13 @@ public class AuthenticationMiddleware var jwtHelper = context.RequestServices.GetRequiredService(); var configService = context.RequestServices.GetRequiredService>(); var secret = configService.Get().Authentication.Secret; - - if(!await jwtHelper.Validate(secret, jwt, "login")) + + if (!await jwtHelper.Validate(secret, jwt, "login")) return; var data = await jwtHelper.Decode(secret, jwt); - - if(!data.TryGetValue("iat", out var issuedAtString) || !data.TryGetValue("userId", out var userIdString)) + + if (!data.TryGetValue("iat", out var issuedAtString) || !data.TryGetValue("userId", out var userIdString)) return; var userId = int.Parse(userIdString); @@ -84,12 +162,12 @@ public class AuthenticationMiddleware var userRepo = context.RequestServices.GetRequiredService>(); var user = userRepo.Get().FirstOrDefault(x => x.Id == userId); - - if(user == null) + + if (user == null) return; - + // Check if token is in the past - if(user.TokenValidTimestamp > issuedAt) + if (user.TokenValidTimestamp > issuedAt) return; // Load permissions, handle empty values @@ -103,6 +181,5 @@ public class AuthenticationMiddleware private async Task AuthenticateApiKey(HttpContext context, string apiKey) { - } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Moonlight.ApiServer.csproj b/Moonlight.ApiServer/Moonlight.ApiServer.csproj index 250d4104..e0d46530 100644 --- a/Moonlight.ApiServer/Moonlight.ApiServer.csproj +++ b/Moonlight.ApiServer/Moonlight.ApiServer.csproj @@ -12,13 +12,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + diff --git a/Moonlight.ApiServer/Program.cs b/Moonlight.ApiServer/Program.cs index 0263b092..58d2016c 100644 --- a/Moonlight.ApiServer/Program.cs +++ b/Moonlight.ApiServer/Program.cs @@ -1,5 +1,6 @@ using Microsoft.OpenApi.Models; using MoonCore.Extended.Abstractions; +using MoonCore.Extended.Extensions; using MoonCore.Extended.Helpers; using MoonCore.Extensions; using MoonCore.Helpers; @@ -20,6 +21,8 @@ var configService = new ConfigService( PathBuilder.File("storage", "config.json") ); +var config = configService.Get(); + ApplicationStateHelper.SetConfiguration(configService); // Build pre run logger @@ -79,6 +82,52 @@ builder.Services.AddSingleton(configService); builder.Services.AddSingleton(); builder.Services.AutoAddServices(); +// OAuth2 +builder.Services.AddSingleton(); + +builder.Services.AddHttpClient(); +builder.Services.AddOAuth2Consumer(configuration => +{ + configuration.ClientId = config.Authentication.ClientId; + configuration.ClientSecret = config.Authentication.ClientSecret; + configuration.AuthorizationRedirect = + config.Authentication.AuthorizationRedirect ?? $"{config.PublicUrl}/api/auth/handle"; + + configuration.AccessEndpoint = config.Authentication.AccessEndpoint ?? $"{config.PublicUrl}/oauth2/access"; + configuration.RefreshEndpoint = config.Authentication.RefreshEndpoint ?? $"{config.PublicUrl}/oauth2/refresh"; + + if (config.Authentication.UseLocalOAuth2Service) + { + configuration.AuthorizationEndpoint = config.Authentication.AuthorizationRedirect ?? $"{config.PublicUrl}/oauth2/authorize"; + } + else + { + if(config.Authentication.AuthorizationUri == null) + logger.LogWarning("The 'AuthorizationUri' for the oauth2 client is not set. If you want to use an external oauth2 provider, you need to specify this url. If you want to use the local oauth2 service, set 'UseLocalOAuth2Service' to true"); + + configuration.AuthorizationEndpoint = config.Authentication.AuthorizationUri!; + } +}); + +if (config.Authentication.UseLocalOAuth2Service) +{ + logger.LogInformation("Using local oauth2 provider"); + + builder.Services.AddOAuth2Provider(configuration => + { + configuration.AccessSecret = config.Authentication.AccessSecret; + configuration.RefreshSecret = config.Authentication.RefreshSecret; + + configuration.ClientId = config.Authentication.ClientId; + configuration.ClientId = config.Authentication.ClientSecret; + configuration.CodeSecret = config.Authentication.CodeSecret; + configuration.AuthorizationRedirect = + config.Authentication.AuthorizationRedirect ?? $"{config.PublicUrl}/api/auth/handle"; + configuration.AccessTokenDuration = 60; + configuration.RefreshTokenDuration = 3600; + }); +} + // Database var databaseHelper = new DatabaseHelper( loggerFactory.CreateLogger() @@ -110,7 +159,7 @@ using (var scope = app.Services.CreateScope()) await databaseHelper.EnsureMigrated(scope.ServiceProvider); } -if(app.Environment.IsDevelopment()) +if (app.Environment.IsDevelopment()) app.UseWebAssemblyDebugging(); app.UseBlazorFrameworkFiles(); diff --git a/Moonlight.Client/Moonlight.Client.csproj b/Moonlight.Client/Moonlight.Client.csproj index bc029be0..7909ccdc 100644 --- a/Moonlight.Client/Moonlight.Client.csproj +++ b/Moonlight.Client/Moonlight.Client.csproj @@ -10,9 +10,10 @@ - + + @@ -29,10 +30,4 @@ - - - ..\..\..\..\GitHub\Marcel-Baumgartner\MoonCore\MoonCore\MoonCore.Blazor.Tailwind\bin\Debug\net8.0\MoonCore.Blazor.Tailwind.dll - - - diff --git a/Moonlight.Client/UI/Screens/AuthenticationScreen.razor b/Moonlight.Client/UI/Screens/AuthenticationScreen.razor index 9d6c6584..2626df4b 100644 --- a/Moonlight.Client/UI/Screens/AuthenticationScreen.razor +++ b/Moonlight.Client/UI/Screens/AuthenticationScreen.razor @@ -1,141 +1,24 @@ -@page "/auth/register" -@page "/auth/login" - -@using MoonCore.Blazor.Tailwind.Forms.Components @using MoonCore.Helpers -@using Moonlight.Client.Services -@using Moonlight.Client.UI.Layouts -@using Moonlight.Shared.Http.Requests.Auth @using Moonlight.Shared.Http.Responses.Auth -@implements IDisposable - @inject NavigationManager Navigation -@inject HttpApiClient ApiClient -@inject IdentityService IdentityService - -@{ - var url = new Uri(Navigation.Uri); - var isRegister = url.LocalPath.StartsWith("/auth/register"); -} - -
- - @if (isRegister) - { -
-
- Moonlight -

Register your account

-
- - - - - -
- Register -

- Already registered? - Login -

-
-
- } - else - { -
-
- Moonlight -

Sign in to your account

-
- - - - - -
- Login -

- Need an account registered? - Register -

-
-
- } +@inject HttpApiClient HttpApiClient +
+ Authenticate
@code { - [CascadingParameter] public MainLayout Layout { get; set; } - - // Page change handling - protected override void OnInitialized() + private async Task StartAuth(WButton _) { - Navigation.LocationChanged += NavigationOnLocationChanged; - } + var authStartData = await HttpApiClient.GetJson("api/auth/start"); - private async void NavigationOnLocationChanged(object? sender, LocationChangedEventArgs e) - => await InvokeAsync(StateHasChanged); - - public void Dispose() - { - Navigation.LocationChanged -= NavigationOnLocationChanged; - } - - // Register - private RegisterRequest RegisterRequest = new(); - private HandleForm RegisterForm; - - private async Task OnSubmitRegister() - { - var response = await ApiClient.PostJson("api/auth/register", RegisterRequest); - await IdentityService.Login(response.Token); - - await HandleAfterAuthPage(); - } - - private void OnConfigureRegister(FormConfiguration configuration) - { - configuration.WithField(x => x.Username, fieldConfiguration => { fieldConfiguration.Columns = 6; }); - - configuration.WithField(x => x.Email, fieldConfiguration => { fieldConfiguration.Columns = 6; }); - - configuration - .WithField(x => x.Password, fieldConfiguration => { fieldConfiguration.Columns = 6; }) - .WithComponent(component => { component.Type = "password"; }); - } - - // Login - private LoginRequest LoginRequest = new(); - private HandleForm LoginForm; - - private async Task OnSubmitLogin() - { - var response = await ApiClient.PostJson("api/auth/login", LoginRequest); - await IdentityService.Login(response.Token); - - await HandleAfterAuthPage(); - } - - private void OnConfigureLogin(FormConfiguration configuration) - { - configuration.WithField(x => x.Email, fieldConfiguration => { fieldConfiguration.Columns = 6; }); - - configuration - .WithField(x => x.Password, fieldConfiguration => { fieldConfiguration.Columns = 6; }) - .WithComponent(component => { component.Type = "password"; }); - } - - // Navigation handling - private async Task HandleAfterAuthPage() - { - var url = new Uri(Navigation.Uri); - - if (url.LocalPath.StartsWith("/auth/login") || url.LocalPath.StartsWith("/auth/register")) - Navigation.NavigateTo("/"); + var uri = authStartData.Endpoint + + $"?client_id={authStartData.ClientId}" + + $"&redirect_uri={authStartData.RedirectUri}" + + $"&response_type=code"; - await Layout.Load(); + Navigation.NavigateTo(uri, true); } -} \ No newline at end of file +} diff --git a/Moonlight.Shared/Http/Responses/Auth/AuthStartResponse.cs b/Moonlight.Shared/Http/Responses/Auth/AuthStartResponse.cs new file mode 100644 index 00000000..a7be320b --- /dev/null +++ b/Moonlight.Shared/Http/Responses/Auth/AuthStartResponse.cs @@ -0,0 +1,8 @@ +namespace Moonlight.Shared.Http.Responses.Auth; + +public class AuthStartResponse +{ + public string Endpoint { get; set; } + public string ClientId { get; set; } + public string RedirectUri { get; set; } +} \ No newline at end of file