using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using PlotLine.Data; using PlotLine.Models; using PlotLine.Services; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.FileProviders; using PlotLine.Hubs; namespace PlotLine; public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Logging.ClearProviders(); builder.Logging.AddConsole(); builder.Logging.AddDebug(); builder.Services.AddControllersWithViews(options => { options.Filters.Add(); }); builder.Services.AddSignalR(options => { options.MaximumReceiveMessageSize = 1024 * 1024; }); builder.Services.AddHttpContextAccessor(); builder.Services.AddMemoryCache(); builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(10); options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.SameSite = SameSiteMode.Lax; }); builder.Services.Configure(builder.Configuration.GetSection("EmailSettings")); builder.Services.Configure(builder.Configuration.GetSection("Stripe")); builder.Services.Configure(builder.Configuration.GetSection("Storage")); builder.Services.Configure(options => { builder.Configuration.GetSection("OpenAI").Bind(options); builder.Configuration.GetSection("StoryIntelligence").Bind(options); }); builder.Services.Configure(builder.Configuration.GetSection("StoryIntelligence:Pricing")); builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(builder.Environment.ContentRootPath, "App_Data", "DataProtectionKeys"))); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.Name = "PlotLine.Auth"; options.LoginPath = "/Account/Login"; options.LogoutPath = "/Account/Logout"; options.AccessDeniedPath = "/Account/AccessDenied"; options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromDays(14); options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.SameSite = SameSiteMode.Lax; options.Events = new CookieAuthenticationEvents { OnValidatePrincipal = async context => { var userIdValue = context.Principal?.FindFirstValue(ClaimTypes.NameIdentifier); var securityStampValue = context.Principal?.FindFirstValue("PlotLine.SecurityStamp"); if (!int.TryParse(userIdValue, out var userId) || !Guid.TryParse(securityStampValue, out var securityStamp)) { context.RejectPrincipal(); await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return; } using var scope = context.HttpContext.RequestServices.CreateScope(); var users = scope.ServiceProvider.GetRequiredService(); var user = await users.GetByIdAsync(userId); var isLocked = user?.IsLocked == true && (!user.LockoutEndUtc.HasValue || user.LockoutEndUtc > DateTime.UtcNow); if (user is null || user.SecurityStamp != securityStamp || isLocked) { context.RejectPrincipal(); await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); } } }; }); builder.Services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => { policy.RequireAuthenticatedUser(); policy.RequireAssertion(context => { var email = context.User.FindFirstValue(ClaimTypes.Email); var allowedEmails = builder.Configuration.GetSection("Admin:AllowedEmails").Get() ?? []; return !string.IsNullOrWhiteSpace(email) && allowedEmails.Any(allowed => string.Equals(allowed, email, StringComparison.OrdinalIgnoreCase)); }); }); }); 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.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.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.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.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.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); 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.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHttpClient(); 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.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); 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.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); var uploadStorage = app.Services.GetRequiredService(); app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(uploadStorage.UploadsRootPath), RequestPath = "/uploads" }); app.UseRouting(); app.UseSession(); app.UseAuthentication(); app.UseAuthorization(); app.MapStaticAssets(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}") .WithStaticAssets(); app.MapHub("/hubs/word-companion-follow"); app.MapHub("/hubs/story-intelligence"); app.Run(); } }