using System.Reflection; using Autofac; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Diagnostics; using Microsoft.OpenApi.Models; using Newtonsoft.Json; using PluralKit.Core; using Serilog; namespace PluralKit.API; public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddCors(); services.AddControllers() // sorry MS, this just does *more* .AddNewtonsoftJson(opts => { // ... though by default it messes up timestamps in JSON opts.SerializerSettings.DateParseHandling = DateParseHandling.None; }) .ConfigureApiBehaviorOptions(options => options.InvalidModelStateResponseFactory = context => throw Errors.GenericBadRequest ); services.AddSwaggerGen(c => { c.SwaggerDoc("v1.0", new OpenApiInfo { Title = "PluralKit", Version = "1.0" }); c.EnableAnnotations(); c.AddSecurityDefinition("TokenAuth", new OpenApiSecurityScheme { Name = "Authorization", Type = SecuritySchemeType.ApiKey }); // Exclude routes without a version, then fall back to group name matching (default behavior) c.DocInclusionPredicate((docName, apiDesc) => { if (!apiDesc.RelativePath.StartsWith("v1/")) return false; return apiDesc.GroupName == docName; }); // Set the comments path for the Swagger JSON and UI. // https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-3.1&tabs=visual-studio#customize-and-extend var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); }); services.AddSwaggerGenNewtonsoftSupport(); // metrics services.AddMetricsTrackingMiddleware(); services.AddAppMetricsCollectors(); } public void ConfigureContainer(ContainerBuilder builder) { builder.RegisterInstance(InitUtils.BuildConfiguration(Environment.GetCommandLineArgs()).Build()) .As(); builder.RegisterModule(new ConfigModule("API")); builder.RegisterModule(new LoggingModule("api", cfg: new LoggerConfiguration().Filter.ByExcluding( exc => exc.Exception is PKError || exc.Exception.IsUserError() ))); // builder.RegisterModule(new MetricsModule("API")); builder.RegisterModule(); builder.RegisterModule(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); // Only enable Swagger stuff when ASPNETCORE_ENVIRONMENT=Development (for now) app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1.0/swagger.json", "PluralKit (v1)"); }); } // add X-PluralKit-Version header app.Use((ctx, next) => { ctx.Response.Headers.Add("X-PluralKit-Version", BuildInfoService.FullVersion); return next(); }); app.UseExceptionHandler(handler => handler.Run(async ctx => { var exc = ctx.Features.Get(); // handle common ISEs that are generated by invalid user input if (exc.Error.IsUserError()) await ctx.Response.WriteJSON(400, "{\"message\":\"400: Bad Request\",\"code\":0}"); else if (exc.Error is not PKError) await ctx.Response.WriteJSON(500, "{\"message\":\"500: Internal Server Error\",\"code\":0}"); // for some reason, if we don't specifically cast to ModelParseError, it uses the base's ToJson method else if (exc.Error is ModelParseError fe) await ctx.Response.WriteJSON(fe.ResponseCode, JsonConvert.SerializeObject(fe.ToJson())); else { var err = (PKError)exc.Error; await ctx.Response.WriteJSON(err.ResponseCode, JsonConvert.SerializeObject(err.ToJson())); } await ctx.Response.CompleteAsync(); })); app.UseMiddleware(); //app.UseHttpsRedirection(); app.UseCors(opts => opts.AllowAnyMethod().AllowAnyOrigin().WithHeaders("Content-Type", "Authorization", "sentry-trace")); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { // register base / legacy routes endpoints.MapMethods("", new string[] { }, (context) => { context.Response.Redirect("https://pluralkit.me/api"); return Task.CompletedTask; }); endpoints.MapMethods("v1/{*_}", new string[] { }, (context) => context.Response.WriteJSON(410, "{\"message\":\"Unsupported API version\",\"code\":0}")); // register controllers endpoints.MapControllers(); }); // metrics app.UseMetricsAllMiddleware(); } }