Started testing oauth2 handler from mooncore

This commit is contained in:
Masu Baumgartner
2024-11-05 22:46:26 +01:00
parent 69e5e1c75b
commit 288b0c8d97
11 changed files with 337 additions and 45 deletions

View File

@@ -1,19 +1,7 @@
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using MoonCore.Attributes;
using MoonCore.Authentication;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers;
using MoonCore.Extended.OAuth2.ApiServer;
using MoonCore.Extensions;
using MoonCore.Helpers;
using MoonCore.Services;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Database.Entities;
using Moonlight.ApiServer.Interfaces.Auth;
using Moonlight.ApiServer.Interfaces.OAuth2;
using Moonlight.Shared.Http.Requests.Auth;
using Moonlight.Shared.Http.Responses.Auth;
namespace Moonlight.ApiServer.Http.Controllers.Auth;
@@ -22,6 +10,7 @@ namespace Moonlight.ApiServer.Http.Controllers.Auth;
[Route("api/auth")]
public class AuthController : Controller
{
/*
private readonly OAuth2Service OAuth2Service;
private readonly TokenHelper TokenHelper;
private readonly DatabaseRepository<User> UserRepository;
@@ -205,7 +194,7 @@ public class AuthController : Controller
// All checks have passed, allow refresh
newData.Add("userId", user.Id);
return true;
}
}*/
[HttpGet("check")]
[RequirePermission("meta.authenticated")]

View File

@@ -3,8 +3,8 @@ using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Mvc;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.OAuth2.AuthServer;
using MoonCore.Extended.OAuth2.Models;
using MoonCore.Extended.OAuth2.Provider;
using Moonlight.ApiServer.Database.Entities;
using Moonlight.ApiServer.Http.Controllers.OAuth2.Pages;
using Moonlight.ApiServer.Services;
@@ -16,11 +16,11 @@ namespace Moonlight.ApiServer.Http.Controllers.OAuth2;
[Microsoft.AspNetCore.Mvc.Route("oauth2")]
public class OAuth2Controller : Controller
{
private readonly OAuth2Service OAuth2Service;
private readonly OAuth2ProviderService OAuth2Service;
private readonly AuthService AuthService;
private readonly DatabaseRepository<User> UserRepository;
public OAuth2Controller(OAuth2Service oAuth2Service,
public OAuth2Controller(OAuth2ProviderService oAuth2Service,
AuthService authService, DatabaseRepository<User> userRepository)
{
OAuth2Service = oAuth2Service;

View File

@@ -0,0 +1,65 @@
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Interfaces;
using MoonCore.Extended.OAuth2.Models;
using MoonCore.Extensions;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Database.Entities;
using Moonlight.Shared.Http.Responses.OAuth2;
namespace Moonlight.ApiServer.Implementations;
public class TestyOuth2Provider : IOAuth2Provider
{
public async Task<Dictionary<string, object>> Sync(IServiceProvider provider, AccessData accessData)
{
var logger = provider.GetRequiredService<ILogger<TestyOuth2Provider>>();
try
{
var configuration = provider.GetRequiredService<AppConfiguration>();
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("Authorization", accessData.AccessToken);
var response = await httpClient.GetAsync($"{configuration.PublicUrl}/oauth2/info");
await response.HandlePossibleApiError();
var info = await response.ParseAsJson<InfoResponse>();
var userRepo = provider.GetRequiredService<DatabaseRepository<User>>();
var user = userRepo.Get().FirstOrDefault(x => x.Email == info.Email);
if (user == null) // User not found, register a new one
{
user = userRepo.Add(new User()
{
Email = info.Email,
Username = info.Username
});
}
else if (user.Username != info.Username) // Username updated?
{
// Username not used by another user?
if (!userRepo.Get().Any(x => x.Username == info.Username))
{
// Update username
user.Username = info.Username;
userRepo.Update(user);
}
}
return new()
{
{
"userId",
user.Id
}
};
}
catch (Exception e)
{
logger.LogCritical("Unable to sync user: {e}", e);
return new();
}
}
}

View File

@@ -8,13 +8,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.7"/>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.10"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MoonCore" Version="1.7.1" />
<PackageReference Include="MoonCore.Extended" Version="1.1.3" />
<PackageReference Include="MoonCore" Version="1.7.3" />
<PackageReference Include="MoonCore.Extended" Version="1.1.4" />
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.4" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>

View File

@@ -9,6 +9,7 @@ using Moonlight.ApiServer;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Helpers;
using Moonlight.ApiServer.Http.Middleware;
using Moonlight.ApiServer.Implementations;
using Moonlight.ApiServer.Interfaces.Auth;
using Moonlight.ApiServer.Interfaces.OAuth2;
using Moonlight.ApiServer.Interfaces.Startup;
@@ -112,7 +113,6 @@ foreach (var startupInterface in appStartupInterfaces)
builder.Services.AddControllers();
builder.Services.AddSingleton(config);
builder.Services.AutoAddServices<Program>();
builder.Services.AddSingleton<TokenHelper>();
builder.Services.AddHttpClient();
await Startup.ConfigureTokenAuthentication(builder, config);
@@ -127,6 +127,8 @@ builder.Services.AddPlugins(configuration =>
configuration.AddAssembly(Assembly.GetEntryAssembly()!);
});
builder.Services.AddSingleton<MoonCore.Extended.Interfaces.IOAuth2Provider>(new TestyOuth2Provider());
var app = builder.Build();
await Startup.PrepareDatabase(app);
@@ -145,6 +147,7 @@ app.UseRouting();
app.UseMiddleware<ApiErrorMiddleware>();
await Startup.UseTokenAuthentication(app);
await Startup.UseOAuth2(app);
// Call interfaces
foreach (var startupInterface in appStartupInterfaces)

View File

@@ -1,5 +1,6 @@
using System.Text.Json;
using MoonCore.Authentication;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Extensions;
using MoonCore.Extended.Helpers;
@@ -8,6 +9,7 @@ using MoonCore.Helpers;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Database.Entities;
using Moonlight.ApiServer.Helpers;
using Moonlight.ApiServer.Interfaces.OAuth2;
using Moonlight.ApiServer.Interfaces.Startup;
namespace Moonlight.ApiServer;
@@ -20,7 +22,7 @@ public static class Startup
{
// Create logging path
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
// Configure application logging
builder.Logging.ClearProviders();
@@ -60,8 +62,9 @@ public static class Startup
#region Token Authentication
public static Task ConfigureTokenAuthentication(IHostApplicationBuilder builder, AppConfiguration config)
public static Task ConfigureTokenAuthentication(WebApplicationBuilder builder, AppConfiguration config)
{
/*
builder.Services.AddTokenAuthentication(configuration =>
{
configuration.AccessSecret = config.Authentication.AccessSecret;
@@ -89,14 +92,123 @@ public static class Startup
return true;
};
});*/
builder.AddTokenAuthentication(authenticationConfig =>
{
authenticationConfig.AccessSecret = config.Authentication.AccessSecret;
authenticationConfig.RefreshSecret = config.Authentication.RefreshSecret;
authenticationConfig.AccessDuration = config.Authentication.AccessDuration;
authenticationConfig.RefreshDuration = config.Authentication.RefreshDuration;
authenticationConfig.ProcessAccess = (accessData, provider, httpContext) =>
{
if (!accessData.TryGetValue("userId", out var userIdStr) || !userIdStr.TryGetInt32(out var userId))
return Task.FromResult(false);
var userRepo = provider.GetRequiredService<DatabaseRepository<User>>();
var user = userRepo.Get().FirstOrDefault(x => x.Id == userId);
if (user == null)
return Task.FromResult(false);
// Load permissions, handle empty values
var permissions = JsonSerializer.Deserialize<string[]>(
string.IsNullOrEmpty(user.PermissionsJson) ? "[]" : user.PermissionsJson
) ?? [];
// Save permission state
httpContext.User = new PermClaimsPrinciple(permissions)
{
IdentityModel = user
};
return Task.FromResult(true);
};
authenticationConfig.ProcessRefresh = (oldData, newData, serviceProvider) =>
{
var oauth2Providers = serviceProvider.GetRequiredService<IOAuth2Provider[]>();
// Find oauth2 provider
var provider = oauth2Providers.FirstOrDefault();
if (provider == null)
throw new HttpApiException("No oauth2 provider has been registered", 500);
// Check if the userId is present in the refresh token
if (!oldData.TryGetValue("userId", out var userIdStr) ||
!userIdStr.TryGetInt32(out var userId))
{
return Task.FromResult(false);
}
// Load user from database if existent
var userRepo = serviceProvider.GetRequiredService<DatabaseRepository<User>>();
var user = userRepo
.Get()
.FirstOrDefault(x => x.Id == userId);
if (user == null)
return Task.FromResult(false);
// Allow plugins to intercept the refresh call
//if (AuthInterceptors.Any(interceptor => !interceptor.AllowRefresh(user, serviceProvider)))
// return false;
/*
// Check if it's time to resync with the oauth2 provider
if (DateTime.UtcNow >= user.RefreshTimestamp)
{
try
{
// It's time to refresh the access to the external oauth2 provider
var refreshData = OAuth2Service.RefreshAccess(user.RefreshToken).Result;
// Sync user with oauth2 provider
var syncedUser = provider.Sync(serviceProvider, refreshData.AccessToken).Result;
if (syncedUser == null) // User sync has failed. No refresh allowed
return false;
// Save oauth2 refresh and access tokens for later use (re-authentication etc.).
// Fetch user model in current db context, just in case the oauth2 provider
// uses a different db context or smth
var userModel = UserRepository
.Get()
.First(x => x.Id == syncedUser.Id);
userModel.AccessToken = refreshData.AccessToken;
userModel.RefreshToken = refreshData.RefreshToken;
userModel.RefreshTimestamp = DateTime.UtcNow.AddSeconds(refreshData.ExpiresIn);
UserRepository.Update(userModel);
}
catch (Exception e)
{
// We are handling this error more softly, because it will occur when a user hasn't logged in a long period of time
Logger.LogDebug("An error occured while refreshing external oauth2 access: {e}", e);
return false;
}
}*/
// All checks have passed, allow refresh
newData.Add("userId", user.Id);
return Task.FromResult(true);
};
});
return Task.CompletedTask;
}
public static Task UseTokenAuthentication(IApplicationBuilder builder)
public static Task UseTokenAuthentication(WebApplication builder)
{
builder.UseTokenAuthentication();
return Task.CompletedTask;
}
@@ -140,9 +252,9 @@ public static class Startup
#region OAuth2
public static Task ConfigureOAuth2(IHostApplicationBuilder builder, ILogger logger, AppConfiguration config)
public static Task ConfigureOAuth2(WebApplicationBuilder builder, ILogger logger, AppConfiguration config)
{
builder.Services.AddOAuth2Consumer(configuration =>
builder.AddOAuth2Consumer(configuration =>
{
configuration.ClientId = config.Authentication.OAuth2.ClientId;
configuration.ClientSecret = config.Authentication.OAuth2.ClientSecret;
@@ -168,6 +280,34 @@ public static class Startup
configuration.AuthorizationEndpoint = config.Authentication.OAuth2.AuthorizationUri!;
}
});
/*
builder.Services.AddOAuth2Consumer(configuration =>
{
configuration.ClientId = config.Authentication.OAuth2.ClientId;
configuration.ClientSecret = config.Authentication.OAuth2.ClientSecret;
configuration.AuthorizationRedirect =
config.Authentication.OAuth2.AuthorizationRedirect ?? $"{config.PublicUrl}/auth";
configuration.AccessEndpoint =
config.Authentication.OAuth2.AccessEndpoint ?? $"{config.PublicUrl}/oauth2/access";
configuration.RefreshEndpoint =
config.Authentication.OAuth2.RefreshEndpoint ?? $"{config.PublicUrl}/oauth2/refresh";
if (config.Authentication.UseLocalOAuth2)
{
configuration.AuthorizationEndpoint = config.Authentication.OAuth2.AuthorizationRedirect ??
$"{config.PublicUrl}/oauth2/authorize";
}
else
{
if (config.Authentication.OAuth2.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.OAuth2.AuthorizationUri!;
}
});*/
if (!config.Authentication.UseLocalOAuth2) return Task.CompletedTask;
@@ -190,5 +330,11 @@ public static class Startup
return Task.CompletedTask;
}
public static Task UseOAuth2(WebApplication application)
{
application.UseOAuth2Consumer();
return Task.CompletedTask;
}
#endregion
}