using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Server.IISIntegration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using MudBlazor.Services; using TrafagSalesExporter.Data; using TrafagSalesExporter.Models; using TrafagSalesExporter.Security; using TrafagSalesExporter.Services; using TrafagSalesExporter.Services.DataSources; var builder = WebApplication.CreateBuilder(args); builder.Logging.ClearProviders(); builder.Logging.AddConsole(); builder.Logging.AddDebug(); builder.Logging.AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Warning); builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddHttpContextAccessor(); var securitySettings = builder.Configuration.GetSection(SecurityOptions.SectionName).Get() ?? new SecurityOptions(); var useDevelopmentAuthentication = builder.Environment.IsDevelopment() && securitySettings.DevelopmentBypass; if (useDevelopmentAuthentication) { builder.Services .AddAuthentication(DevelopmentAuthenticationHandler.SchemeName) .AddScheme( DevelopmentAuthenticationHandler.SchemeName, options => { }); } else { builder.Services.AddAuthentication(IISDefaults.AuthenticationScheme); } builder.Services.AddAuthorization(options => { options.FallbackPolicy = SecurityPolicyFactory.BuildAccessPolicy(securitySettings, useDevelopmentAuthentication); options.AddPolicy(SecurityPolicies.AdminOnly, SecurityPolicyFactory.BuildAdminPolicy(securitySettings, useDevelopmentAuthentication)); }); builder.Services.AddMudServices(); builder.Services.AddHttpClient(nameof(ExchangeRateImportService)); builder.Services.Configure(builder.Configuration.GetSection(HrKpiDataSourceOptions.SectionName)); builder.Services.Configure(builder.Configuration.GetSection(HrKpiAccessOptions.SectionName)); builder.Services.Configure(builder.Configuration.GetSection(FinanceCockpitAccessOptions.SectionName)); builder.Services.Configure(builder.Configuration.GetSection(AdminAccessOptions.SectionName)); builder.Services.Configure(builder.Configuration.GetSection(LandingPageOptions.SectionName)); builder.Services.AddDbContextFactory(options => options.UseSqlite("Data Source=trafag_exporter.db;Default Timeout=60")); // Stateless Infrastruktur- und Connector-Services: Singleton. 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(); 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(); 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(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); // Datenquellen-Adapter (Strategy per ConnectionKind). builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); // Orchestrator mit gemeinsamem Status ueber alle Circuits. builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); // UI-/Page-Services: Scoped = pro Blazor-Circuit. 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(); var app = builder.Build(); var pathBase = app.Configuration["ASPNETCORE_PATHBASE"]; if (!string.IsNullOrWhiteSpace(pathBase)) { app.UsePathBase(pathBase.Trim()); } using (var scope = app.Services.CreateScope()) { var databaseInitialization = scope.ServiceProvider.GetRequiredService(); await databaseInitialization.InitializeAsync(); } if (!app.Environment.IsDevelopment()) { app.UseHsts(); } app.UseStaticFiles(); app.UseAuthentication(); app.UseAuthorization(); app.UseAntiforgery(); app.MapPost("/access/finance", async (HttpContext httpContext, IOptions options) => { var form = await httpContext.Request.ReadFormAsync(); var settings = options.Value; var username = form["username"].ToString(); var password = form["password"].ToString(); if (MatchesAccess(settings.Enabled, settings.Username, settings.PasswordHash, settings.Password, username, password)) AccessUnlockCookie.SetUnlocked(httpContext, AccessUnlockCookie.FinanceCookieName, settings.PasswordHash); return Results.Redirect(ResolveReturnUrl(httpContext, form["returnUrl"].ToString())); }).DisableAntiforgery(); app.MapPost("/access/admin", async (HttpContext httpContext, IOptions options) => { var form = await httpContext.Request.ReadFormAsync(); var settings = options.Value; var username = form["username"].ToString(); var password = form["password"].ToString(); if (MatchesAccess(settings.Enabled, settings.Username, settings.PasswordHash, settings.Password, username, password)) AccessUnlockCookie.SetUnlocked(httpContext, AccessUnlockCookie.AdminCookieName, settings.PasswordHash); return Results.Redirect(ResolveReturnUrl(httpContext, form["returnUrl"].ToString())); }).DisableAntiforgery(); app.MapPost("/access/hr", async (HttpContext httpContext, IOptions options) => { var form = await httpContext.Request.ReadFormAsync(); var settings = options.Value; var username = form["username"].ToString(); var password = form["password"].ToString(); if (MatchesAccess(settings.Enabled, settings.Username, settings.PasswordHash, settings.Password, username, password)) AccessUnlockCookie.SetUnlocked(httpContext, AccessUnlockCookie.HrCookieName, settings.PasswordHash); return Results.Redirect(ResolveReturnUrl(httpContext, form["returnUrl"].ToString())); }).DisableAntiforgery(); app.MapRazorComponents() .AddInteractiveServerRenderMode(); app.Run(); static bool MatchesAccess(bool enabled, string configuredUsername, string configuredHash, string configuredPassword, string username, string password) { if (!enabled) return true; if (string.IsNullOrWhiteSpace(username) || string.IsNullOrEmpty(password) || !string.Equals(username.Trim(), configuredUsername.Trim(), StringComparison.Ordinal)) { return false; } return !string.IsNullOrWhiteSpace(configuredHash) ? string.Equals(AccessPasswordSettingsWriter.HashPassword(password), configuredHash.Trim(), StringComparison.Ordinal) : string.Equals(password, configuredPassword, StringComparison.Ordinal); } static string ResolveReturnUrl(HttpContext httpContext, string returnUrl) { if (Uri.TryCreate(returnUrl, UriKind.Absolute, out var absolute) && string.Equals(absolute.Host, httpContext.Request.Host.Host, StringComparison.OrdinalIgnoreCase)) { return absolute.PathAndQuery; } if (Uri.TryCreate(returnUrl, UriKind.Relative, out _)) return returnUrl; return $"{httpContext.Request.PathBase}/"; }