using System.Security.Claims; using System.Text; using System.Text.Json; using System.Threading.RateLimiting; using CareFix.Api.Ai; using CareFix.Api.Auth; using CareFix.Api.Endpoints; using CareFix.Api.Fixes; using CareFix.Api.Hospitals; using CareFix.Api.Infrastructure; using CareFix.Api.Integration; using CareFix.Api.Learning; using CareFix.Api.Knowledge; using CareFix.Api.Options; using CareFix.Api.Security; using CareFix.Core; using CareFix.Api.Tickets; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; var builder = WebApplication.CreateBuilder(args); var section = builder.Configuration.GetSection("CareFix"); builder.Services.Configure(section); var cfg = section.Get() ?? new CareFixOptions(); if (cfg.Jwt.SigningKey.Length < 32 || cfg.Jwt.SigningKey.StartsWith("SET-IN-ENV")) throw new InvalidOperationException("Set CareFix:Jwt:SigningKey to a random value of at least 32 characters."); builder.Services.ConfigureHttpJsonOptions(o => o.SerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase); builder.Services.AddMemoryCache(); builder.Services.AddHttpClient(ClaudeClient.HttpClientName, c => c.Timeout = TimeSpan.FromSeconds(300)); builder.Services.AddHttpClient("webhook", c => c.Timeout = TimeSpan.FromSeconds(15)); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => sp.GetRequiredService()); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o => { o.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = cfg.Jwt.Issuer, ValidateAudience = true, ValidAudience = cfg.Jwt.Audience, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(cfg.Jwt.SigningKey)), ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(1), NameClaimType = ClaimTypes.Name, RoleClaimType = ClaimTypes.Role, }; }); builder.Services.AddAuthorization(o => { o.AddPolicy("Admin", p => p.RequireRole(Roles.Admin)); o.AddPolicy("Manager", p => p.RequireRole(Roles.Lead, Roles.ProductOwner, Roles.Head, Roles.Admin)); }); builder.Services.AddRateLimiter(o => { o.RejectionStatusCode = StatusCodes.Status429TooManyRequests; o.AddPolicy("login", ctx => RateLimitPartition.GetFixedWindowLimiter( ctx.Connection.RemoteIpAddress?.ToString() ?? "unknown", _ => new FixedWindowRateLimiterOptions { PermitLimit = 10, Window = TimeSpan.FromMinutes(1) })); o.AddPolicy("integration", ctx => RateLimitPartition.GetFixedWindowLimiter( "ip:" + ctx.Connection.RemoteIpAddress, _ => new FixedWindowRateLimiterOptions { PermitLimit = 120, Window = TimeSpan.FromMinutes(1) })); o.AddPolicy("agent", ctx => RateLimitPartition.GetFixedWindowLimiter( ctx.Request.Headers[AgentProtocol.HospitalHeader].ToString() is { Length: > 0 } code ? "agent:" + code : "ip:" + ctx.Connection.RemoteIpAddress, _ => new FixedWindowRateLimiterOptions { PermitLimit = 600, Window = TimeSpan.FromMinutes(1) })); }); var app = builder.Build(); app.Use(async (ctx, next) => { ctx.Response.Headers["X-Content-Type-Options"] = "nosniff"; ctx.Response.Headers["X-Frame-Options"] = "DENY"; ctx.Response.Headers["Referrer-Policy"] = "no-referrer"; await next(); }); app.UseMiddleware(); if (!app.Environment.IsDevelopment()) app.UseHsts(); app.UseDefaultFiles(); app.UseStaticFiles(); app.UseRateLimiter(); app.UseAuthentication(); app.UseAuthorization(); app.MapCareFix(); app.MapFallbackToFile("index.html"); await app.Services.GetRequiredService().EnsureBootstrapAdminAsync(CancellationToken.None); app.Run();