using MareSynchronos.API; using Microsoft.EntityFrameworkCore; using MareSynchronosServer.Hubs; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.Authorization; using AspNetCoreRateLimit; using MareSynchronosShared.Authentication; using MareSynchronosShared.Data; using MareSynchronosShared.Protos; using Grpc.Net.Client.Configuration; using MareSynchronosShared.Metrics; using MareSynchronosServer.Services; using MareSynchronosServer.Utils; using MareSynchronosServer.RequirementHandlers; using MareSynchronosShared.Utils; using MareSynchronosServer.Identity; using MareSynchronosShared.Services; using Prometheus; using Microsoft.Extensions.Options; using Grpc.Net.ClientFactory; namespace MareSynchronosServer; public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddHttpContextAccessor(); services.AddTransient(_ => Configuration); var mareConfig = Configuration.GetRequiredSection("MareSynchronos"); // configure metrics ConfigureMetrics(services); // configure file service grpc connection ConfigureFileServiceGrpcClient(services, mareConfig); // configure database ConfigureDatabase(services, mareConfig); // configure authentication and authorization ConfigureAuthorization(services); // configure rate limiting ConfigureIpRateLimiting(services); // configure SignalR ConfigureSignalR(services, mareConfig); // configure mare specific services ConfigureMareServices(services, mareConfig); } private static void ConfigureMareServices(IServiceCollection services, IConfigurationSection mareConfig) { bool isMainServer = mareConfig.GetValue(nameof(ServerConfiguration.MainServerGrpcAddress), defaultValue: null) == null; services.Configure(mareConfig); services.Configure(mareConfig); services.Configure(mareConfig); services.AddSingleton(); services.AddSingleton(); services.AddHostedService(provider => provider.GetService()); // configure services based on main server status ConfigureIdentityServices(services, mareConfig, isMainServer); if (isMainServer) { services.AddSingleton(); services.AddHostedService(provider => provider.GetService()); } } private static void ConfigureSignalR(IServiceCollection services, IConfigurationSection mareConfig) { var signalRServiceBuilder = services.AddSignalR(hubOptions => { hubOptions.MaximumReceiveMessageSize = long.MaxValue; hubOptions.EnableDetailedErrors = true; hubOptions.MaximumParallelInvocationsPerClient = 10; hubOptions.StreamBufferCapacity = 200; hubOptions.AddFilter(); }); // configure redis for SignalR var redis = mareConfig.GetValue(nameof(ServerConfiguration.RedisConnectionString), string.Empty); if (!string.IsNullOrEmpty(redis)) { signalRServiceBuilder.AddStackExchangeRedis(redis, options => { options.Configuration.ChannelPrefix = "MareSynchronos"; }); } } private void ConfigureIpRateLimiting(IServiceCollection services) { services.Configure(Configuration.GetSection("IpRateLimiting")); services.Configure(Configuration.GetSection("IpRateLimitPolicies")); services.AddSingleton(); services.AddMemoryCache(); services.AddInMemoryRateLimiting(); } private static void ConfigureAuthorization(IServiceCollection services) { services.AddSingleton(); services.AddTransient(); services.AddAuthentication(SecretKeyAuthenticationHandler.AuthScheme) .AddScheme(SecretKeyAuthenticationHandler.AuthScheme, options => { options.Validate(); }); services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(SecretKeyAuthenticationHandler.AuthScheme) .RequireAuthenticatedUser().Build(); options.AddPolicy("Authenticated", policy => { policy.AddAuthenticationSchemes(SecretKeyAuthenticationHandler.AuthScheme); policy.RequireAuthenticatedUser(); }); options.AddPolicy("Identified", policy => { policy.AddRequirements(new UserRequirement(UserRequirements.Identified)); }); options.AddPolicy("Admin", policy => { policy.AddRequirements(new UserRequirement(UserRequirements.Identified | UserRequirements.Administrator)); }); options.AddPolicy("Moderator", policy => { policy.AddRequirements(new UserRequirement(UserRequirements.Identified | UserRequirements.Moderator | UserRequirements.Administrator)); }); }); } private void ConfigureDatabase(IServiceCollection services, IConfigurationSection mareConfig) { services.AddDbContextPool(options => { options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), builder => { builder.MigrationsHistoryTable("_efmigrationshistory", "public"); builder.MigrationsAssembly("MareSynchronosShared"); }).UseSnakeCaseNamingConvention(); options.EnableThreadSafetyChecks(false); }, mareConfig.GetValue(nameof(MareConfigurationBase.DbContextPoolSize), 1024)); } private static void ConfigureMetrics(IServiceCollection services) { services.AddSingleton(m => new MareMetrics(m.GetService>(), new List { MetricsAPI.CounterInitializedConnections, MetricsAPI.CounterUserPushData, MetricsAPI.CounterUserPushDataTo, MetricsAPI.CounterUsersRegisteredDeleted, MetricsAPI.CounterAuthenticationCacheHits, MetricsAPI.CounterAuthenticationFailures, MetricsAPI.CounterAuthenticationRequests, MetricsAPI.CounterAuthenticationSuccesses, }, new List { MetricsAPI.GaugeAuthorizedConnections, MetricsAPI.GaugeConnections, MetricsAPI.GaugePairs, MetricsAPI.GaugePairsPaused, MetricsAPI.GaugeAvailableIOWorkerThreads, MetricsAPI.GaugeAvailableWorkerThreads, MetricsAPI.GaugeGroups, MetricsAPI.GaugeGroupPairs, MetricsAPI.GaugeGroupPairsPaused, MetricsAPI.GaugeUsersRegistered })); } private static void ConfigureIdentityServices(IServiceCollection services, IConfigurationSection mareConfig, bool isMainServer) { if (!isMainServer) { var noRetryConfig = new MethodConfig { Names = { MethodName.Default }, RetryPolicy = null }; services.AddGrpcClient(c => { c.Address = new Uri(mareConfig.GetValue(nameof(ServerConfiguration.MainServerGrpcAddress))); }).ConfigureChannel(c => { c.ServiceConfig = new ServiceConfig { MethodConfigs = { noRetryConfig } }; c.HttpHandler = new SocketsHttpHandler() { EnableMultipleHttp2Connections = true }; }); services.AddGrpcClient("MainServer", c => { c.Address = new Uri(mareConfig.GetValue(nameof(ServerConfiguration.MainServerGrpcAddress))); }).ConfigureChannel(c => { c.ServiceConfig = new ServiceConfig { MethodConfigs = { noRetryConfig } }; c.HttpHandler = new SocketsHttpHandler() { EnableMultipleHttp2Connections = true }; }); services.AddSingleton(); services.AddHostedService(p => p.GetService()); services.AddSingleton>(c => new MareConfigurationServiceClient( c.GetService>>(), c.GetService>(), c.GetService(), "MainServer")); services.AddSingleton>(c => new MareConfigurationServiceClient( c.GetService>>(), c.GetService>(), c.GetService(), "MainServer")); } else { services.AddSingleton(); services.AddSingleton(); services.AddSingleton, MareConfigurationServiceServer>(); services.AddSingleton, MareConfigurationServiceServer>(); services.AddGrpc(); } } private static void ConfigureFileServiceGrpcClient(IServiceCollection services, IConfigurationSection mareConfig) { var defaultMethodConfig = new MethodConfig { Names = { MethodName.Default }, RetryPolicy = new RetryPolicy { MaxAttempts = 1000, InitialBackoff = TimeSpan.FromSeconds(1), MaxBackoff = TimeSpan.FromSeconds(5), BackoffMultiplier = 1.5, RetryableStatusCodes = { Grpc.Core.StatusCode.Unavailable } } }; services.AddGrpcClient((serviceProvider, c) => { c.Address = serviceProvider.GetRequiredService>() .GetValue(nameof(ServerConfiguration.StaticFileServiceAddress)); }).ConfigureChannel(c => { c.ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } }; }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger logger) { logger.LogInformation("Running Configure"); var config = app.ApplicationServices.GetRequiredService>(); app.UseIpRateLimiting(); app.UseRouting(); app.UseWebSockets(); var metricServer = new KestrelMetricServer(config.GetValueOrDefault(nameof(MareConfigurationBase.MetricsPort), 4980)); metricServer.Start(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapHub(IMareHub.Path, options => { options.ApplicationMaxBufferSize = 5242880; options.TransportMaxBufferSize = 5242880; options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling; }); if (config.IsMain) { endpoints.MapGrpcService().AllowAnonymous(); endpoints.MapGrpcService>().AllowAnonymous(); } }); } }