using System.Text; using System.Threading.RateLimiting; using MicCheck.Api.Common.Security.ApiKeys; using MicCheck.Api.Audit; using MicCheck.Api.Common.Security.Authentication; using MicCheck.Api.Common.Security.Authorization; using MicCheck.Api.Common.Validation; using MicCheck.Api.Data; using MicCheck.Api.Environments; using MicCheck.Api.Features; using MicCheck.Api.Identities; using MicCheck.Api.Organizations; using MicCheck.Api.Projects; using MicCheck.Api.Segments; using MicCheck.Api.Users; using MicCheck.Api.Webhooks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using Scalar.AspNetCore; using Serilog; Log.Logger = new LoggerConfiguration() .WriteTo.Console() .CreateBootstrapLogger(); try { var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); builder.Host.UseSerilog((context, services, config) => config.ReadFrom.Configuration(context.Configuration) .ReadFrom.Services(services) .WriteTo.Console()); builder.Services.AddOpenApi(); builder.Services.AddControllers(options => options.Filters.Add()) .AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add( new System.Text.Json.Serialization.JsonStringEnumConverter())); builder.Services.AddAuthentication() .AddScheme( EnvironmentKeyAuthenticationHandler.SchemeName, _ => { }) .AddScheme( ApiKeyAuthenticationHandler.SchemeName, _ => { }) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]!)) }; }); builder.Services.AddAuthorization(options => { options.AddPolicy(AuthorizationPolicies.FlagsApiAccess, policy => policy.AddAuthenticationSchemes(EnvironmentKeyAuthenticationHandler.SchemeName) .RequireClaim("EnvironmentId")); options.AddPolicy(AuthorizationPolicies.AdminApiAccess, policy => policy.AddAuthenticationSchemes(ApiKeyAuthenticationHandler.SchemeName, JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser()); options.AddPolicy(AuthorizationPolicies.OrganizationAdmin, policy => policy.AddAuthenticationSchemes(ApiKeyAuthenticationHandler.SchemeName, JwtBearerDefaults.AuthenticationScheme) .RequireClaim("OrganizationRole", "Admin")); }); builder.Services.AddHttpContextAccessor(); builder.Services.AddRateLimiter(options => options.AddFixedWindowLimiter("AdminApi", limiter => { limiter.PermitLimit = 500; limiter.Window = TimeSpan.FromMinutes(1); limiter.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; limiter.QueueLimit = 0; })); builder.Services.AddModelValidatorsFromAssemblyContaining(); builder.Services.Configure(options => { options.InvalidModelStateResponseFactory = context => ValidationProblemResponseFactory.Create(context.ModelState); }); builder.Services.AddMemoryCache(); builder.Services.AddMetrics(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped, PasswordHasher>(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddHostedService(); builder.Services.AddHttpClient("Webhooks", client => client.DefaultRequestHeaders.Add("User-Agent", "MicCheck-Webhook/1.0")); var connectionString = builder.Configuration.GetConnectionString("miccheck") ?? (System.Environment.GetEnvironmentVariable("DATABASE_URL") is { } databaseUrl ? DatabaseUrlParser.ToNpgsqlConnectionString(databaseUrl) : builder.Configuration.GetConnectionString("DefaultConnection")!); builder.Services.AddDbContext(options => options.UseNpgsql(connectionString)); builder.Services.AddScoped(sp => sp.GetRequiredService()); var app = builder.Build(); await app.ApplyMigrationsAsync(); if (app.Environment.IsDevelopment()) { app.MapOpenApi(); app.MapScalarApiReference(); app.MapGet("/", () => Results.Redirect("/scalar/v1")).ExcludeFromDescription(); await app.SeedDevelopmentDataAsync(); } app.UseSerilogRequestLogging(); app.UseRateLimiter(); app.UseAuthentication(); app.UseAuthorization(); app.MapDefaultEndpoints(); app.MapControllers(); app.MapAuthEndpoints(); app.MapApiKeyEndpoints(); app.Run(); } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }