using Data; using Microsoft.EntityFrameworkCore; using MudBlazor.Services; using Serilog; using WebApp; using WebApp.Authentication; using WebApp.Components; var builder = WebApplication.CreateBuilder(args); // Configure Serilog builder.Host.UseSerilog((context, configuration) => configuration .ReadFrom.Configuration(context.Configuration) .Enrich.FromLogContext() .WriteTo.Console( outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}") .WriteTo.File( path: "logs/webapp-.txt", rollingInterval: RollingInterval.Day, retainedFileCountLimit: 30, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")); // Configure authentication secrets for production (Docker, etc.) if (builder.Environment.IsProduction()) { // Option 1: Load from volume-mounted secrets file var secretsPath = "/app/secrets/auth-secrets.json"; if (File.Exists(secretsPath)) { builder.Configuration.AddJsonFile(secretsPath, optional: false, reloadOnChange: true); } // Option 2: Environment variables with prefix builder.Configuration.AddEnvironmentVariables(prefix: "TSA_"); } // Add services to the container. builder.Services.AddControllersWithViews(); builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddMudServices(); // Configure SQLite var connectionString = builder.Configuration.GetConnectionString("SQLiteDefault"); builder.Services.AddDbContext(options => options.UseSqlite(connectionString)); builder.Services.AddQuickGridEntityFrameworkAdapter(); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddScoped(); // State container for maintaining state per user connection (Blazor Server) builder.Services.AddScoped(); // Add authentication services builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); // Add authentication options builder.Services.AddAuthentication("Auth") .AddCookie("Auth", options => { options.ExpireTimeSpan = TimeSpan.FromMinutes(20); options.SlidingExpiration = true; options.LoginPath = "/login"; // Enhanced security settings options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.SameSite = SameSiteMode.Strict; options.Cookie.Name = "TSA.Auth"; }); builder.Services.AddCascadingAuthenticationState(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); app.UseMigrationsEndPoint(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents() .AddInteractiveServerRenderMode(); // Used for AuthController app.MapControllerRoute("default", "{controller}/{action}"); // Development-only password hash generator endpoint if (app.Environment.IsDevelopment()) { app.MapGet("/dev/hash-password", (string password) => { var hash = PasswordHashGenerator.GenerateHash(password); return Results.Ok(new { password, hash, message = "Copy the hash value to your User Secrets configuration" }); }).WithName("GeneratePasswordHash"); } app.Run();