using MemberCenter.Infrastructure.Configuration; using MemberCenter.Infrastructure.Identity; using MemberCenter.Infrastructure.Persistence; using MemberCenter.Infrastructure.Services; using MemberCenter.Application.Abstractions; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using OpenIddict.Abstractions; using OpenIddict.Server.AspNetCore; using OpenIddict.Validation.AspNetCore; EnvLoader.LoadDotEnvIfDevelopment(); var builder = WebApplication.CreateBuilder(args); var pathBase = NormalizePathBase(builder.Configuration["PathBase"]); builder.Services.AddDbContext(options => { var connectionString = builder.Configuration.GetConnectionString("Default") ?? "Host=localhost;Database=member_center;Username=postgres;Password=postgres"; options.UseNpgsql(connectionString); options.UseOpenIddict(); }); builder.Services .AddIdentity(options => { options.User.RequireUniqueEmail = true; options.Password.RequireDigit = true; options.Password.RequireLowercase = true; options.Password.RequireUppercase = true; options.Password.RequireNonAlphanumeric = false; options.Password.RequiredLength = 8; }) .AddEntityFrameworkStores() .AddDefaultTokenProviders(); builder.Services.AddAuthentication(options => { options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; }); builder.Services.AddOpenIddict() .AddCore(options => { options.UseEntityFrameworkCore() .UseDbContext(); }) .AddServer(options => { options.SetAuthorizationEndpointUris(WithPathBase(pathBase, "/oauth/authorize")); options.SetTokenEndpointUris( WithPathBase(pathBase, "/oauth/token"), WithPathBase(pathBase, "/auth/login"), WithPathBase(pathBase, "/auth/refresh")); options.SetLogoutEndpointUris(WithPathBase(pathBase, "/auth/logout")); var issuer = builder.Configuration["Auth:Issuer"]; if (!string.IsNullOrWhiteSpace(issuer)) { if (!Uri.TryCreate(issuer, UriKind.Absolute, out var issuerUri)) { throw new InvalidOperationException("Auth:Issuer must be an absolute URI."); } options.SetIssuer(issuerUri); } options.AllowAuthorizationCodeFlow() .RequireProofKeyForCodeExchange(); options.AllowRefreshTokenFlow(); options.AllowPasswordFlow(); options.AllowClientCredentialsFlow(); options.AcceptAnonymousClients(); options.RegisterScopes( OpenIddictConstants.Scopes.OpenId, OpenIddictConstants.Scopes.Email, OpenIddictConstants.Scopes.Profile, "newsletter:list.read", "newsletter:send.write", "newsletter:send.read", "newsletter:events.read", "newsletter:events.write", "newsletter:events.write.global"); options.AddDevelopmentEncryptionCertificate(); options.AddDevelopmentSigningCertificate(); options.DisableAccessTokenEncryption(); var aspNetCore = options.UseAspNetCore() .EnableAuthorizationEndpointPassthrough() .EnableTokenEndpointPassthrough() .EnableLogoutEndpointPassthrough() .EnableStatusCodePagesIntegration(); if (builder.Environment.IsDevelopment()) { // TEST/LOCAL ONLY: allow HTTP for local Docker integration testing. aspNetCore.DisableTransportSecurityRequirement(); } }) .AddValidation(options => { options.UseLocalServer(); options.UseAspNetCore(); }); builder.Services.AddAuthorization(options => { options.AddPolicy("Admin", policy => policy.RequireRole("admin")); }); builder.Services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.KnownNetworks.Clear(); options.KnownProxies.Clear(); }); builder.Services.AddControllers(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.Configure(builder.Configuration.GetSection("SendEngine")); builder.Services.AddHttpClient(); builder.Services.AddScoped(); var app = builder.Build(); app.UseForwardedHeaders(); if (!string.IsNullOrWhiteSpace(pathBase)) { app.UsePathBase(pathBase); } app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run(); static string? NormalizePathBase(string? pathBase) { if (string.IsNullOrWhiteSpace(pathBase)) { return null; } var normalized = pathBase.StartsWith('/') ? pathBase : $"/{pathBase}"; return normalized.Length > 1 ? normalized.TrimEnd('/') : normalized; } static string WithPathBase(string? pathBase, string relativePath) { var normalizedRelativePath = relativePath.StartsWith('/') ? relativePath : $"/{relativePath}"; return string.IsNullOrWhiteSpace(pathBase) ? normalizedRelativePath : $"{pathBase}{normalizedRelativePath}"; }