using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Http.Resilience; using Polly; using System.Net; using System.Security.Claims; using ShiftScheduler.Services; using ShiftScheduler.Shared; var builder = WebApplication.CreateBuilder(args); // Load configurations from appsettings.json var shifts = builder.Configuration.GetSection("Shifts").Get>() ?? new(); var transportConfig = builder.Configuration.GetSection("Transport").Get() ?? new(); var authorizedEmails = builder.Configuration.GetSection("Authentication:AuthorizedEmails").Get>() ?? new(); // Create application configuration var appConfiguration = new ApplicationConfiguration { Transport = transportConfig, Shifts = shifts }; // Register services builder.Services.AddSingleton(authorizedEmails); builder.Services.AddSingleton(new ConfigurationService(appConfiguration)); builder.Services.AddMemoryCache(); builder.Services.AddHttpClient(); builder.Services.AddHttpClient("nextcloud") .AddResilienceHandler("nextcloud-retry", pipeline => { pipeline.AddRetry(new HttpRetryStrategyOptions { MaxRetryAttempts = 3, Delay = TimeSpan.FromSeconds(2), BackoffType = DelayBackoffType.Exponential, ShouldHandle = args => ValueTask.FromResult( args.Outcome.Result?.StatusCode == HttpStatusCode.TooManyRequests) }); }); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Configure authentication builder.Services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme; }) .AddCookie(options => { options.LoginPath = "/api/auth/login"; options.LogoutPath = "/api/auth/logout"; options.AccessDeniedPath = "/"; options.ExpireTimeSpan = TimeSpan.FromDays(7); options.SlidingExpiration = true; options.Cookie.SameSite = SameSiteMode.Lax; options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; }) .AddGoogle(googleOptions => { googleOptions.ClientId = builder.Configuration["Authentication:Google:ClientId"] ?? ""; googleOptions.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"] ?? ""; googleOptions.CallbackPath = "/signin-google"; googleOptions.SaveTokens = true; googleOptions.Scope.Add("https://www.googleapis.com/auth/calendar"); googleOptions.Events.OnTicketReceived = async context => { var emailClaim = context.Principal?.FindFirst(ClaimTypes.Email) ?? context.Principal?.FindFirst("email"); if (emailClaim?.Value == null || !authorizedEmails.Contains(emailClaim.Value)) { context.Fail("Email not authorized"); context.Response.Redirect("/?error=unauthorized"); return; } await Task.CompletedTask; }; }); // Configure authorization policy for allowed emails builder.Services.AddAuthorization(options => { options.AddPolicy("AllowedEmails", policy => policy.RequireAssertion(context => { var emailClaim = context.User.FindFirst(ClaimTypes.Email) ?? context.User.FindFirst("email"); if (emailClaim?.Value != null) { return authorizedEmails.Contains(emailClaim.Value); } return false; })); }); builder.Services.AddControllersWithViews(); builder.Services.AddRazorPages(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseWebAssemblyDebugging(); } else { app.UseExceptionHandler("/Error"); // 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.UseHttpsRedirection(); app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); var iconsPath = Path.Combine(Directory.GetCurrentDirectory(), "config", "icons"); Directory.CreateDirectory(iconsPath); app.UseStaticFiles(new StaticFileOptions { FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(iconsPath), RequestPath = "/icons" }); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapRazorPages(); app.MapControllers(); app.MapFallbackToFile("index.html"); app.Run();