Cleaned up pagination in user and apikey controller. Extracted login start and start url generation to modular IOAuth2Provider interface. Improved login and register local oauth2 page
This commit is contained in:
@@ -28,8 +28,8 @@ public class ApiKeysController : Controller
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.apikeys.get")]
|
||||
public async Task<IPagedData<ApiKeyResponse>> Get(
|
||||
[FromQuery] int page,
|
||||
[FromQuery] [Range(1, 100)] int pageSize = 50
|
||||
[FromQuery] [Range(0, int.MaxValue)] int page,
|
||||
[FromQuery] [Range(1, 100)] int pageSize
|
||||
)
|
||||
{
|
||||
var count = await ApiKeyRepository.Get().CountAsync();
|
||||
|
||||
@@ -27,7 +27,7 @@ public class UsersController : Controller
|
||||
[Authorize(Policy = "permissions:admin.users.get")]
|
||||
public async Task<IPagedData<UserResponse>> Get(
|
||||
[FromQuery] [Range(0, int.MaxValue)] int page,
|
||||
[FromQuery] [Range(1, 100)] int pageSize = 50
|
||||
[FromQuery] [Range(1, 100)] int pageSize
|
||||
)
|
||||
{
|
||||
var count = await UserRepository.Get().CountAsync();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
@@ -20,16 +20,11 @@ namespace Moonlight.ApiServer.Http.Controllers.Auth;
|
||||
public class AuthController : Controller
|
||||
{
|
||||
private readonly AppConfiguration Configuration;
|
||||
private readonly ILogger<AuthController> Logger;
|
||||
private readonly DatabaseRepository<User> UserRepository;
|
||||
private readonly IOAuth2Provider OAuth2Provider;
|
||||
|
||||
private readonly string RedirectUri;
|
||||
private readonly string EndpointUri;
|
||||
|
||||
public AuthController(
|
||||
AppConfiguration configuration,
|
||||
ILogger<AuthController> logger,
|
||||
DatabaseRepository<User> userRepository,
|
||||
IOAuth2Provider oAuth2Provider
|
||||
)
|
||||
@@ -37,36 +32,25 @@ public class AuthController : Controller
|
||||
UserRepository = userRepository;
|
||||
OAuth2Provider = oAuth2Provider;
|
||||
Configuration = configuration;
|
||||
Logger = logger;
|
||||
|
||||
RedirectUri = string.IsNullOrEmpty(Configuration.Authentication.OAuth2.AuthorizationRedirect)
|
||||
? Configuration.PublicUrl
|
||||
: Configuration.Authentication.OAuth2.AuthorizationRedirect;
|
||||
|
||||
EndpointUri = string.IsNullOrEmpty(Configuration.Authentication.OAuth2.AuthorizationEndpoint)
|
||||
? Configuration.PublicUrl + "/oauth2/authorize"
|
||||
: Configuration.Authentication.OAuth2.AuthorizationEndpoint;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("start")]
|
||||
public Task<LoginStartResponse> Start()
|
||||
public async Task<LoginStartResponse> Start()
|
||||
{
|
||||
var response = new LoginStartResponse()
|
||||
{
|
||||
ClientId = Configuration.Authentication.OAuth2.ClientId,
|
||||
RedirectUri = RedirectUri,
|
||||
Endpoint = EndpointUri
|
||||
};
|
||||
var url = await OAuth2Provider.Start();
|
||||
|
||||
return Task.FromResult(response);
|
||||
return new LoginStartResponse()
|
||||
{
|
||||
Url = url
|
||||
};
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("complete")]
|
||||
public async Task<LoginCompleteResponse> Complete([FromBody] LoginCompleteRequest request)
|
||||
{
|
||||
var user = await OAuth2Provider.Sync(request.Code);
|
||||
var user = await OAuth2Provider.Complete(request.Code);
|
||||
|
||||
if (user == null)
|
||||
throw new HttpApiException("Unable to load user data", 500);
|
||||
@@ -113,8 +97,8 @@ public class AuthController : Controller
|
||||
[HttpGet("check")]
|
||||
public async Task<CheckResponse> Check()
|
||||
{
|
||||
var userIdClaim = User.Claims.First(x => x.Type == "userId");
|
||||
var userId = int.Parse(userIdClaim.Value);
|
||||
var userIdStr = User.FindFirstValue("userId")!;
|
||||
var userId = int.Parse(userIdStr);
|
||||
var user = await UserRepository.Get().FirstAsync(x => x.Id == userId);
|
||||
|
||||
return new()
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>@Configuration.Title</title>
|
||||
<title>@Title</title>
|
||||
<base href="/"/>
|
||||
|
||||
@foreach (var style in Configuration.Styles)
|
||||
@foreach (var style in Styles)
|
||||
{
|
||||
<link rel="stylesheet" href="@style"/>
|
||||
}
|
||||
@@ -86,7 +86,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
@foreach (var script in Configuration.Scripts)
|
||||
@foreach (var script in Scripts)
|
||||
{
|
||||
<script src="@script"></script>
|
||||
}
|
||||
@@ -99,6 +99,8 @@
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public FrontendConfiguration Configuration { get; set; }
|
||||
[Parameter] public string Title { get; set; }
|
||||
[Parameter] public string[] Scripts { get; set; }
|
||||
[Parameter] public string[] Styles { get; set; }
|
||||
[Parameter] public Theme? Theme { get; set; }
|
||||
}
|
||||
|
||||
@@ -27,7 +27,27 @@ public class LocalOAuth2Provider : IOAuth2Provider
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public async Task<User?> Sync(string code)
|
||||
public Task<string> Start()
|
||||
{
|
||||
var redirectUri = string.IsNullOrEmpty(Configuration.Authentication.OAuth2.AuthorizationRedirect)
|
||||
? Configuration.PublicUrl
|
||||
: Configuration.Authentication.OAuth2.AuthorizationRedirect;
|
||||
|
||||
var endpoint = string.IsNullOrEmpty(Configuration.Authentication.OAuth2.AuthorizationEndpoint)
|
||||
? Configuration.PublicUrl + "/oauth2/authorize"
|
||||
: Configuration.Authentication.OAuth2.AuthorizationEndpoint;
|
||||
|
||||
var clientId = Configuration.Authentication.OAuth2.ClientId;
|
||||
|
||||
var url = $"{endpoint}" +
|
||||
$"?client_id={clientId}" +
|
||||
$"&redirect_uri={redirectUri}" +
|
||||
$"&response_type=code";
|
||||
|
||||
return Task.FromResult(url);
|
||||
}
|
||||
|
||||
public async Task<User?> Complete(string code)
|
||||
{
|
||||
// Create http client to call the auth provider
|
||||
var httpClient = new HttpClient();
|
||||
@@ -70,6 +90,10 @@ public class LocalOAuth2Provider : IOAuth2Provider
|
||||
throw new HttpApiException("Unable to request user data", 500);
|
||||
}
|
||||
|
||||
// Notice: We just look up the user id here
|
||||
// which works as our oauth2 provider is using the same db.
|
||||
// a real oauth2 provider would create a user here
|
||||
|
||||
// Handle the returned data
|
||||
var userId = handleData.UserId;
|
||||
|
||||
|
||||
@@ -4,5 +4,7 @@ namespace Moonlight.ApiServer.Interfaces;
|
||||
|
||||
public interface IOAuth2Provider
|
||||
{
|
||||
public Task<User?> Sync(string code);
|
||||
public Task<string> Start();
|
||||
|
||||
public Task<User?> Complete(string code);
|
||||
}
|
||||
@@ -40,53 +40,44 @@ public class FrontendService
|
||||
ThemeRepository = themeRepository;
|
||||
}
|
||||
|
||||
public async Task<FrontendConfiguration> GetConfiguration()
|
||||
public Task<FrontendConfiguration> GetConfiguration()
|
||||
{
|
||||
var configuration = new FrontendConfiguration()
|
||||
{
|
||||
Title = "Moonlight", // TODO: CONFIG
|
||||
ApiUrl = Configuration.PublicUrl,
|
||||
HostEnvironment = "ApiServer"
|
||||
};
|
||||
|
||||
// Load theme.json if it exists
|
||||
var themePath = Path.Combine("storage", "theme.json");
|
||||
|
||||
if (File.Exists(themePath))
|
||||
{
|
||||
var variablesJson = await File.ReadAllTextAsync(themePath);
|
||||
|
||||
configuration.Theme.Variables = JsonSerializer
|
||||
.Deserialize<Dictionary<string, string>>(variablesJson) ?? new();
|
||||
}
|
||||
|
||||
// Collect scripts to execute
|
||||
configuration.Scripts = ConfigurationOptions
|
||||
.SelectMany(x => x.Scripts)
|
||||
.ToArray();
|
||||
|
||||
// Collect styles
|
||||
configuration.Styles = ConfigurationOptions
|
||||
.SelectMany(x => x.Styles)
|
||||
.ToArray();
|
||||
|
||||
return configuration;
|
||||
return Task.FromResult(configuration);
|
||||
}
|
||||
|
||||
public async Task<string> GenerateIndexHtml() // TODO: Cache
|
||||
{
|
||||
var configuration = await GetConfiguration();
|
||||
|
||||
// Load requested theme
|
||||
var theme = await ThemeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.IsEnabled);
|
||||
|
||||
// Load configured javascript files
|
||||
var scripts = ConfigurationOptions
|
||||
.SelectMany(x => x.Scripts)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
// Load configured css files
|
||||
var styles = ConfigurationOptions
|
||||
.SelectMany(x => x.Styles)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
return await ComponentHelper.RenderComponent<FrontendPage>(
|
||||
ServiceProvider,
|
||||
parameters =>
|
||||
{
|
||||
parameters["Configuration"] = configuration;
|
||||
parameters["Theme"] = theme!;
|
||||
parameters["Styles"] = styles;
|
||||
parameters["Scripts"] = scripts;
|
||||
parameters["Title"] = "Moonlight"; // TODO: Config
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -83,12 +83,7 @@ public class RemoteAuthStateManager : AuthenticationStateManager
|
||||
{
|
||||
var loginStartData = await HttpApiClient.GetJson<LoginStartResponse>("api/auth/start");
|
||||
|
||||
var url = $"{loginStartData.Endpoint}" +
|
||||
$"?client_id={loginStartData.ClientId}" +
|
||||
$"&redirect_uri={loginStartData.RedirectUri}" +
|
||||
$"&response_type=code";
|
||||
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
NavigationManager.NavigateTo(loginStartData.Url, true);
|
||||
}
|
||||
|
||||
private async Task<AuthenticationState> LoadAuthState()
|
||||
|
||||
@@ -2,7 +2,5 @@ namespace Moonlight.Shared.Http.Responses.Auth;
|
||||
|
||||
public class LoginStartResponse
|
||||
{
|
||||
public string ClientId { get; set; }
|
||||
public string Endpoint { get; set; }
|
||||
public string RedirectUri { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
@@ -2,16 +2,6 @@ namespace Moonlight.Shared.Misc;
|
||||
|
||||
public class FrontendConfiguration
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string ApiUrl { get; set; }
|
||||
public string HostEnvironment { get; set; }
|
||||
public ThemeData Theme { get; set; } = new();
|
||||
public string[] Scripts { get; set; }
|
||||
public string[] Styles { get; set; }
|
||||
public string[] Assemblies { get; set; }
|
||||
|
||||
public class ThemeData
|
||||
{
|
||||
public Dictionary<string, string> Variables { get; set; } = new();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user