using System.Security.Cryptography; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using SIGCM2.Application.Abstractions; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Abstractions.Security; using SIGCM2.Application.Audit; using SIGCM2.Application.Auth; using SIGCM2.Application.Rubros; using SIGCM2.Infrastructure.Http; using SIGCM2.Infrastructure.Messaging; using SIGCM2.Infrastructure.Persistence; using SIGCM2.Infrastructure.Security; namespace SIGCM2.Infrastructure; public static class DependencyInjection { public static IServiceCollection AddInfrastructure( this IServiceCollection services, IConfiguration configuration) { // Database var connectionString = configuration.GetConnectionString("SqlServer") ?? throw new InvalidOperationException("Missing ConnectionStrings:SqlServer"); services.AddSingleton(new SqlConnectionFactory(connectionString)); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); // PRD-002: replaces NullProductQueryRepository from Application DI services.AddScoped(); // JWT Options — bound lazily via IOptions so tests can override via ConfigureWebHost services.Configure(configuration.GetSection("Jwt")); // Also expose as JwtOptions directly for convenience (resolves via IOptions) services.AddSingleton(sp => sp.GetRequiredService>().Value); // AuthOptions (Application layer) — populated from the same Jwt config section services.AddSingleton(sp => { var opts = sp.GetRequiredService(); return new AuthOptions { AccessTokenMinutes = opts.AccessTokenMinutes, RefreshTokenDays = opts.RefreshTokenDays, }; }); // RSA key pair — loaded lazily as singletons from the fully-resolved JwtOptions services.AddSingleton(sp => { var opts = sp.GetRequiredService(); return RsaKeyLoader.LoadPrivateKey(opts); }); services.AddSingleton(sp => { var opts = sp.GetRequiredService(); return new RsaSecurityKey(RsaKeyLoader.LoadPublicKey(opts)); }); services.AddScoped(sp => new JwtService( sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService())); services.AddScoped(); services.AddSingleton(); services.AddHttpContextAccessor(); services.AddScoped(); // CAT-001: Rubros options (MaxDepth) — overridable via appsettings "Rubros". services.Configure(configuration.GetSection(RubrosOptions.SectionName)); // UDT-010: Audit options (SanitizedKeys blacklist) — overridable via appsettings "Audit". services.Configure(configuration.GetSection(AuditOptions.SectionName)); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); // Dispatcher services.AddScoped(); // JWT Bearer authentication services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(); // Post-configure JWT Bearer — wire RSA public key + validation params from resolved options. // MapInboundClaims=false: preserve JWT claim names as-is ("sub", "rol", etc.). // Without this, the middleware maps "sub" → ClaimTypes.NameIdentifier and breaks User.FindFirst("sub"). services.AddOptions(JwtBearerDefaults.AuthenticationScheme) .PostConfigure((jwtBearerOpts, rsaKey, jwtOpts) => { jwtBearerOpts.MapInboundClaims = false; jwtBearerOpts.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = rsaKey, ValidateIssuer = true, ValidIssuer = jwtOpts.Issuer, ValidateAudience = true, ValidAudience = jwtOpts.Audience, ValidateLifetime = true, ClockSkew = TimeSpan.Zero, RoleClaimType = "rol", NameClaimType = "name" }; }); return services; } }