Preparations for plugin/module development
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using MoonCore.Authentication;
|
||||
using MoonCore.Exceptions;
|
||||
@@ -7,9 +8,12 @@ using MoonCore.Extended.Helpers;
|
||||
using MoonCore.Extended.OAuth2.Consumer;
|
||||
using MoonCore.Extensions;
|
||||
using MoonCore.Helpers;
|
||||
using MoonCore.PluginFramework.Extensions;
|
||||
using Moonlight.ApiServer.Configuration;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using Moonlight.ApiServer.Helpers;
|
||||
using Moonlight.ApiServer.Http.Middleware;
|
||||
using Moonlight.ApiServer.Interfaces.Auth;
|
||||
using Moonlight.ApiServer.Interfaces.OAuth2;
|
||||
using Moonlight.ApiServer.Interfaces.Startup;
|
||||
using Moonlight.Shared.Http.Responses.OAuth2;
|
||||
@@ -18,6 +22,202 @@ namespace Moonlight.ApiServer;
|
||||
|
||||
public static class Startup
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
=> await Run(args, []);
|
||||
|
||||
public static async Task Run(string[] args, Assembly[]? pluginAssemblies = null)
|
||||
{
|
||||
// Cry about it
|
||||
#pragma warning disable ASP0000
|
||||
|
||||
// Fancy start console output... yes very fancy :>
|
||||
var rainbow = new Crayon.Rainbow(0.5);
|
||||
foreach (var c in "Moonlight")
|
||||
{
|
||||
Console.Write(
|
||||
rainbow
|
||||
.Next()
|
||||
.Bold()
|
||||
.Text(c.ToString())
|
||||
);
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// Storage i guess
|
||||
Directory.CreateDirectory(PathBuilder.Dir("storage"));
|
||||
|
||||
// TODO: Load plugin/module assemblies
|
||||
|
||||
// Configure startup logger
|
||||
var startupLoggerFactory = new LoggerFactory();
|
||||
|
||||
// TODO: Add direct extension method
|
||||
var providers = LoggerBuildHelper.BuildFromConfiguration(configuration =>
|
||||
{
|
||||
configuration.Console.Enable = true;
|
||||
configuration.Console.EnableAnsiMode = true;
|
||||
configuration.FileLogging.Enable = false;
|
||||
});
|
||||
|
||||
startupLoggerFactory.AddProviders(providers);
|
||||
|
||||
var startupLogger = startupLoggerFactory.CreateLogger("Startup");
|
||||
|
||||
// Configure startup interfaces
|
||||
var startupServiceCollection = new ServiceCollection();
|
||||
|
||||
startupServiceCollection.AddConfiguration(options =>
|
||||
{
|
||||
options.UsePath(PathBuilder.Dir("storage"));
|
||||
options.UseEnvironmentPrefix("MOONLIGHT");
|
||||
|
||||
options.AddConfiguration<AppConfiguration>("app");
|
||||
});
|
||||
|
||||
startupServiceCollection.AddLogging(loggingBuilder => { loggingBuilder.AddProviders(providers); });
|
||||
|
||||
startupServiceCollection.AddPlugins(configuration =>
|
||||
{
|
||||
// Configure startup interfaces
|
||||
configuration.AddInterface<IAppStartup>();
|
||||
configuration.AddInterface<IDatabaseStartup>();
|
||||
configuration.AddInterface<IEndpointStartup>();
|
||||
|
||||
// Configure assemblies to scan
|
||||
configuration.AddAssembly(typeof(Startup).Assembly);
|
||||
|
||||
if(pluginAssemblies != null)
|
||||
configuration.AddAssemblies(pluginAssemblies);
|
||||
|
||||
//TODO: Load plugins from file
|
||||
});
|
||||
|
||||
|
||||
var startupServiceProvider = startupServiceCollection.BuildServiceProvider();
|
||||
var appStartupInterfaces = startupServiceProvider.GetRequiredService<IAppStartup[]>();
|
||||
|
||||
var config = startupServiceProvider.GetRequiredService<AppConfiguration>();
|
||||
ApplicationStateHelper.SetConfiguration(config);
|
||||
|
||||
// Start the actual app
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
await ConfigureLogging(builder);
|
||||
|
||||
await ConfigureDatabase(
|
||||
builder,
|
||||
startupLoggerFactory,
|
||||
startupServiceProvider.GetRequiredService<IDatabaseStartup[]>()
|
||||
);
|
||||
|
||||
// Call interfaces
|
||||
foreach (var startupInterface in appStartupInterfaces)
|
||||
{
|
||||
try
|
||||
{
|
||||
await startupInterface.BuildApp(builder);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
startupLogger.LogCritical(
|
||||
"An unhandled error occured while processing BuildApp call for interface '{interfaceName}': {e}",
|
||||
startupInterface.GetType().FullName,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddSingleton(config);
|
||||
builder.Services.AutoAddServices(typeof(Startup).Assembly);
|
||||
builder.Services.AddHttpClient();
|
||||
|
||||
await ConfigureTokenAuthentication(builder, config);
|
||||
await ConfigureOAuth2(builder, startupLogger, config);
|
||||
|
||||
// Implementation service
|
||||
builder.Services.AddPlugins(configuration =>
|
||||
{
|
||||
configuration.AddInterface<IOAuth2Provider>();
|
||||
configuration.AddInterface<IAuthInterceptor>();
|
||||
|
||||
configuration.AddAssembly(typeof(Startup).Assembly);
|
||||
|
||||
if(pluginAssemblies != null)
|
||||
configuration.AddAssemblies(pluginAssemblies);
|
||||
|
||||
//TODO: Load plugins from file
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
await PrepareDatabase(app);
|
||||
|
||||
if (config.Client.Enable)
|
||||
{
|
||||
if (app.Environment.IsDevelopment())
|
||||
app.UseWebAssemblyDebugging();
|
||||
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseStaticFiles();
|
||||
}
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseApiErrorHandling();
|
||||
|
||||
await UseTokenAuthentication(app);
|
||||
await UseOAuth2(app);
|
||||
|
||||
// Call interfaces
|
||||
foreach (var startupInterface in appStartupInterfaces)
|
||||
{
|
||||
try
|
||||
{
|
||||
await startupInterface.ConfigureApp(app);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
startupLogger.LogCritical(
|
||||
"An unhandled error occured while processing ConfigureApp call for interface '{interfaceName}': {e}",
|
||||
startupInterface.GetType().FullName,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
app.UseMiddleware<ApiAuthenticationMiddleware>();
|
||||
|
||||
app.UseMiddleware<AuthorizationMiddleware>();
|
||||
|
||||
// Call interfaces
|
||||
var endpointStartupInterfaces = startupServiceProvider.GetRequiredService<IEndpointStartup[]>();
|
||||
|
||||
foreach (var endpointStartup in endpointStartupInterfaces)
|
||||
{
|
||||
try
|
||||
{
|
||||
await endpointStartup.ConfigureEndpoints(app);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
startupLogger.LogCritical(
|
||||
"An unhandled error occured while processing ConfigureEndpoints call for interface '{interfaceName}': {e}",
|
||||
endpointStartup.GetType().FullName,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
if (config.Client.Enable)
|
||||
app.MapFallbackToFile("index.html");
|
||||
|
||||
await app.RunAsync();
|
||||
}
|
||||
|
||||
#region Logging
|
||||
|
||||
public static async Task ConfigureLogging(IHostApplicationBuilder builder)
|
||||
@@ -132,7 +332,7 @@ public static class Startup
|
||||
authenticationConfig.ProcessRefresh = async (oldData, newData, serviceProvider) =>
|
||||
{
|
||||
var oauth2Providers = serviceProvider.GetRequiredService<IOAuth2Provider[]>();
|
||||
|
||||
|
||||
// Find oauth2 provider
|
||||
var provider = oauth2Providers.FirstOrDefault();
|
||||
|
||||
@@ -145,10 +345,10 @@ public static class Startup
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Load user from database if existent
|
||||
var userRepo = serviceProvider.GetRequiredService<DatabaseRepository<User>>();
|
||||
|
||||
|
||||
var user = userRepo
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.Id == userId);
|
||||
@@ -159,12 +359,12 @@ public static class Startup
|
||||
// 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)
|
||||
{
|
||||
var oAuth2Service = serviceProvider.GetRequiredService<OAuth2ConsumerService>();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// It's time to refresh the access to the external oauth2 provider
|
||||
@@ -194,7 +394,7 @@ public static class Startup
|
||||
{
|
||||
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger("OAuth2 Refresh");
|
||||
|
||||
|
||||
// We are handling this error more softly, because it will occur when a user hasn't logged in a long period of time
|
||||
logger.LogTrace("An error occured while refreshing external oauth2 access: {e}", e);
|
||||
return false;
|
||||
@@ -213,7 +413,7 @@ public static class Startup
|
||||
public static Task UseTokenAuthentication(WebApplication builder)
|
||||
{
|
||||
builder.UseTokenAuthentication();
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -289,13 +489,13 @@ public static class Startup
|
||||
configuration.ProcessComplete = async (serviceProvider, accessData) =>
|
||||
{
|
||||
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);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var user = await provider.Sync(serviceProvider, accessData.AccessToken);
|
||||
@@ -313,14 +513,14 @@ public static class Startup
|
||||
|
||||
return new Dictionary<string, object>()
|
||||
{
|
||||
{"userId", user.Id}
|
||||
{ "userId", user.Id }
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger(provider.GetType());
|
||||
|
||||
|
||||
logger.LogTrace("An error occured while syncing user with oauth2 provider: {e}", e);
|
||||
throw new HttpApiException("Unable to synchronize with oauth2 provider", 400);
|
||||
}
|
||||
@@ -351,9 +551,9 @@ public static class Startup
|
||||
public static Task UseOAuth2(WebApplication application)
|
||||
{
|
||||
application.UseOAuth2Consumer();
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user