626a9f8454
Backend security fixes from the Phase 1 register / Phase 2 roadmap (PR1 + V-01): - V-01 SSRF: new SafeHttpGuard validates outbound unsubscribe URLs (scheme allowlist + DNS-resolve-and-block private/loopback/link-local/ULA/metadata ranges), wired into UnsubscribeService; the "unsubscribe" HttpClient now disables auto-redirect so a validated external URL can't 3xx into an internal target. +33 unit tests. - V-04: session cookie SecurePolicy=Always in non-dev (SameAsRequest in dev). - V-06: UseExceptionHandler/ProblemDetails in prod; Cleanup/Unsubscribe no longer echo ex.Message to clients (logged server-side, generic message returned). - V-08: ForwardedHeaders trusted only from configurable KnownNetworks (default private ranges) + ForwardLimit, instead of trusting any client. New ForwardedHeaders config. - V-09: returnUrl validated with Url.IsLocalUrl (no open redirect via OAuth flow). - V-10: SearchService clamps Page/PageSize (<=200); Analytics clamps take/days. - V-11: baseline security headers (nosniff, X-Frame-Options DENY, Referrer-Policy, COOP) + HSTS in prod. - V-13: /app/info discloses only devMode to anonymous callers unless dev mode is on. - V-12: API container runs as non-root 'app' user (keys dir pre-owned). - V-03: Postgres + API ports bound to 127.0.0.1; POSTGRES_PASSWORD now required (no weak default fallback). API compatibility preserved (clamps not rejections; error-body shape changes only on failure paths). No DB migrations. Build + all 33 unit tests green. V-15 (MailKit NU1902) persists across versions and the SMTP path is default-off — tracked, not bumped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
3.8 KiB
C#
86 lines
3.8 KiB
C#
using InboxIntel.Application.Abstractions;
|
|
using InboxIntel.Domain.Enums;
|
|
using InboxIntel.Infrastructure.Ai;
|
|
using InboxIntel.Infrastructure.Analytics;
|
|
using InboxIntel.Infrastructure.Cleanup;
|
|
using InboxIntel.Infrastructure.Configuration;
|
|
using InboxIntel.Infrastructure.Export;
|
|
using InboxIntel.Infrastructure.Gmail;
|
|
using InboxIntel.Infrastructure.Notifications;
|
|
using InboxIntel.Infrastructure.Persistence;
|
|
using InboxIntel.Infrastructure.Search;
|
|
using InboxIntel.Infrastructure.Security;
|
|
using InboxIntel.Infrastructure.Sync;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace InboxIntel.Infrastructure;
|
|
|
|
public static class DependencyInjection
|
|
{
|
|
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration config)
|
|
{
|
|
// EF Core / PostgreSQL
|
|
services.AddDbContext<AppDbContext>(opt =>
|
|
opt.UseNpgsql(config.GetConnectionString("Postgres"),
|
|
npg => npg.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName)));
|
|
services.AddScoped<IAppDbContext>(sp => sp.GetRequiredService<AppDbContext>());
|
|
|
|
// Options
|
|
services.Configure<GoogleOAuthOptions>(config.GetSection(GoogleOAuthOptions.SectionName));
|
|
services.Configure<GmailSyncOptions>(config.GetSection(GmailSyncOptions.SectionName));
|
|
services.Configure<AiOptions>(config.GetSection(AiOptions.SectionName));
|
|
services.Configure<SmtpOptions>(config.GetSection(SmtpOptions.SectionName));
|
|
services.Configure<DigestOptions>(config.GetSection(DigestOptions.SectionName));
|
|
|
|
// Security
|
|
services.AddSingleton<ITokenProtector, DataProtectionTokenProtector>();
|
|
|
|
// Gmail
|
|
services.AddScoped<GmailClientFactory>();
|
|
services.AddScoped<IGmailService, GmailApiService>();
|
|
|
|
// Background sync queue (singleton) + its worker.
|
|
services.AddSingleton<SyncQueue>();
|
|
services.AddSingleton<ISyncQueue>(sp => sp.GetRequiredService<SyncQueue>());
|
|
services.AddHostedService<SyncQueueWorker>();
|
|
|
|
// Core services
|
|
services.AddScoped<ISyncService, SyncService>();
|
|
services.AddScoped<IAnalyticsService, AnalyticsService>();
|
|
services.AddScoped<ISearchService, SearchService>();
|
|
services.AddScoped<ICleanupService, CleanupService>();
|
|
services.AddScoped<IUnsubscribeService, UnsubscribeService>();
|
|
services.AddScoped<IExportService, ExportService>();
|
|
|
|
// Notifications (digest emails)
|
|
services.AddScoped<IEmailSender, SmtpEmailSender>();
|
|
services.AddScoped<IDigestService, DigestService>();
|
|
services.AddHostedService<DigestWorker>();
|
|
|
|
// HTTP clients
|
|
// V-01: do NOT follow redirects — a validated external URL must not be able to
|
|
// 3xx-redirect into an internal target after SafeHttpGuard has checked it.
|
|
services.AddHttpClient("unsubscribe", c => c.Timeout = TimeSpan.FromSeconds(15))
|
|
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false });
|
|
services.AddHttpClient("ollama");
|
|
services.AddHttpClient("openai");
|
|
|
|
// AI provider selected by configured mode.
|
|
var aiMode = config.GetSection(AiOptions.SectionName).GetValue<AiProviderMode>("Mode");
|
|
switch (aiMode)
|
|
{
|
|
case AiProviderMode.LocalOllama: services.AddScoped<IAiProvider, OllamaProvider>(); break;
|
|
case AiProviderMode.CloudOpenAi: services.AddScoped<IAiProvider, OpenAiProvider>(); break;
|
|
default: services.AddScoped<IAiProvider, NullAiProvider>(); break;
|
|
}
|
|
services.AddScoped<IAiService, AiService>();
|
|
|
|
// Background worker (daily incremental sync + aggregate refresh)
|
|
services.AddHostedService<GmailSyncWorker>();
|
|
|
|
return services;
|
|
}
|
|
}
|