168 lines
5.7 KiB
C#

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<MemberCenterDbContext>(options =>
{
var connectionString = builder.Configuration.GetConnectionString("Default")
?? "Host=localhost;Database=member_center;Username=postgres;Password=postgres";
options.UseNpgsql(connectionString);
options.UseOpenIddict();
});
builder.Services
.AddIdentity<ApplicationUser, ApplicationRole>(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<MemberCenterDbContext>()
.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<MemberCenterDbContext>();
})
.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<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
builder.Services.AddControllers();
builder.Services.AddScoped<INewsletterService, NewsletterService>();
builder.Services.AddScoped<IEmailBlacklistService, EmailBlacklistService>();
builder.Services.AddScoped<ITenantService, TenantService>();
builder.Services.AddScoped<INewsletterListService, NewsletterListService>();
builder.Services.Configure<SendEngineWebhookOptions>(builder.Configuration.GetSection("SendEngine"));
builder.Services.AddHttpClient<SendEngineWebhookPublisher>();
builder.Services.AddScoped<ISendEngineWebhookPublisher, SendEngineWebhookPublisher>();
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}";
}