using Microsoft.IdentityModel.Tokens; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authentication.JwtBearer; using ScrapperAPI.Bus; using ScrapperAPI.Factories; using ScrapperAPI.Hub; using ScrapperAPI.Interfaces; using ScrapperAPI.Options; using ScrapperAPI.Repositories; using ScrapperAPI.Services; using ScrapperAPI.Utils; using ScrapperAPI.Workers; using ScrapperAPI.AgentGrpc; var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); builder.Services.AddSignalR(); builder.Services.AddControllers(); builder.Services.AddGrpc(); // Authentik (OIDC) - JWT Bearer validation for API + SignalR builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // Example: https://auth.seu-dominio.com/application/o/seu-app/ options.Authority = builder.Configuration["Authentication:Authority"]; options.RequireHttpsMetadata = builder.Configuration.GetValue("Authentication:RequireHttpsMetadata", true); // Usually the SPA client_id options.Audience = builder.Configuration["Authentication:Audience"]; // SignalR sends the token via the query string (access_token) for WebSockets options.Events = new JwtBearerEvents { OnMessageReceived = context => { var accessToken = context.Request.Query["access_token"]; var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/ws/scrape")) { context.Token = accessToken; } return System.Threading.Tasks.Task.CompletedTask; } }; // If you want stricter token validation, uncomment: // options.TokenValidationParameters = new TokenValidationParameters // { // ValidateIssuer = true, // ValidateAudience = true, // }; }); builder.Services.AddAuthorization(options => { // Require authentication by default for ALL endpoints (controllers + hub) options.FallbackPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); }); builder.Services.Configure(builder.Configuration.GetSection("Scraper")); builder.Services.Configure(builder.Configuration.GetSection("Extraction")); builder.Services.Configure(builder.Configuration.GetSection("Workers")); builder.Services.AddSingleton(sp => { var opts = sp.GetRequiredService>().Value; return new DomainRateLimiter(opts.RateLimit.PerDomainMinDelayMs); }); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Extraction builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddHttpClient("scraper", c => c.Timeout = TimeSpan.FromSeconds(30)); builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => (ScrapeCoordinator)sp.GetRequiredService()); builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => (ExtractionCoordinator)sp.GetRequiredService()); builder.Services.AddCors(options => { options.AddPolicy("AllowReact", policy => { policy.WithOrigins("http://localhost:3000") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); var app = builder.Build(); app.UseCors("AllowReact"); app.UseAuthentication(); app.UseAuthorization(); if (app.Environment.IsDevelopment()) { app.MapOpenApi().AllowAnonymous(); } app.MapGrpcService(); app.MapControllers(); app.MapHub("/ws/scrape").RequireAuthorization(); app.MapGrpcService().AllowAnonymous(); // app.UseHttpsRedirection(); app.Run();