using GmRelay.Web.Components; using GmRelay.Web.Services; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.DataProtection; using System.Security.Claims; using Telegram.Bot; using Npgsql; var builder = WebApplication.CreateBuilder(args); // Add Aspire service defaults builder.AddServiceDefaults(); // Add Data Protection builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo("/app/dataprotection-keys")); // Add Npgsql builder.AddNpgsqlDataSource("gmrelaydb"); // Add Services builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddScoped(); // Add Bot Client builder.Services.AddSingleton(sp => { var config = sp.GetRequiredService(); var token = config["Telegram__BotToken"] ?? config["Telegram:BotToken"] ?? throw new InvalidOperationException("Telegram__BotToken is required."); return new TelegramBotClient(token); }); // Add Authentication with hardened cookie settings builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/login"; options.AccessDeniedPath = "/access-denied"; options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.SameSite = SameSiteMode.Strict; options.ExpireTimeSpan = TimeSpan.FromDays(7); options.SlidingExpiration = true; }); builder.Services.AddAuthorization(); builder.Services.AddCascadingAuthenticationState(); // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseHsts(); } app.UseHttpsRedirection(); // Security headers middleware app.Use(async (context, next) => { context.Response.Headers["X-Content-Type-Options"] = "nosniff"; context.Response.Headers["X-Frame-Options"] = "DENY"; context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin"; context.Response.Headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=()"; await next(); }); app.UseAuthentication(); app.UseAuthorization(); app.UseAntiforgery(); app.MapStaticAssets(); app.MapRazorComponents() .AddInteractiveServerRenderMode(); // Endpoint to handle Telegram Login callback app.MapGet("/auth/telegram", async (HttpContext context, TelegramAuthService authService) => { if (authService.Verify(context.Request.Query, out var telegramId, out var name)) { var claims = new List { new Claim(ClaimTypes.NameIdentifier, telegramId.ToString()), new Claim(ClaimTypes.Name, name), new Claim("TelegramId", telegramId.ToString()) }; var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties { IsPersistent = true }; await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); return Results.Redirect("/"); } return Results.Redirect("/login?error=auth_failed"); }); app.MapPost("/auth/logout", async (HttpContext context) => { await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Results.Redirect("/"); }); app.Run();