yavsc/src/Yavsc.Blogs/Program.cs

102 lines
3.2 KiB
C#
Raw Normal View History

2026-03-01 02:13:26 +00:00
using IdentityModel;
2026-06-10 00:23:17 +01:00
using Microsoft.AspNetCore.Authorization;
2026-03-01 02:13:26 +00:00
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Yavsc;
2026-03-01 02:13:26 +00:00
using Yavsc.Interface;
using Yavsc.Interfaces;
2026-03-01 02:13:26 +00:00
using Yavsc.Models;
using Yavsc.Services;
2026-03-09 02:07:09 +00:00
using Yavsc.Server.Helpers;
2026-06-19 23:09:43 +01:00
using Microsoft.AspNetCore.Identity;
2026-06-10 16:59:23 +01:00
namespace Yavsc.Blogs;
2026-03-01 02:13:26 +00:00
internal class Program
{
private static async Task Main(string[] args)
2026-06-04 22:04:20 +01:00
{
Console.Title = "Yavsc.Blogs";
2026-03-09 02:07:09 +00:00
2026-06-04 22:04:20 +01:00
var builder = WebApplication.CreateBuilder(args);
2026-06-15 00:33:38 +01:00
2026-06-15 00:11:48 +01:00
builder.AddConfiguration("blogs");
2026-03-09 02:07:09 +00:00
Split Site:Audience into Site:ExternalUrl + Site:CorsAllowedOrigins The Site:Audience setting was conflating two distinct concepts: an OAuth JWT audience (a single resource identifier) and a CORS allow-list (an array of origins). Collapsing them caused several latent bugs: - OAuth/JWT validation expected a single string while CORS WithOrigins accepts an array. - Password-reset callback URLs and OAuth client RedirectUri/Origin were being built from what was meant to be an audience identifier, not a base URL. - Yavsc.Org's main CORS policy was hardcoded to '*', with no way to restrict it without code changes. Changes: - SiteSettings.Audience (string) replaced with CorsAllowedOrigins (IList<string>). - OAuth JWT Authority still reads Site:Authority; Audience now reads Site:ExternalUrl (Org only; Api/Blogs use ValidateAudience=false). - MailSender and AccountController build reset-callback URLs from Site:ExternalUrl. - ClientController uses Site:ExternalUrl for OAuth RedirectUri/Origin defaults on newly created clients. - Yavsc.Api and Yavsc.Blogs now read CORS origins from Site:CorsAllowedOrigins instead of hardcoded URLs. Add shared AddYavscCors / AddYavscJwtBearer extension methods in Yavsc.Server/Helpers/ServiceExtensions.cs to enforce a single configuration contract across all runtime services (Api, Blogs, Org). Fails closed when CorsAllowedOrigins is empty; fails fast at startup when Site:Authority is missing. Remove obsolete ConfigurationHelpers.GetAudience (no remaining callers). Local appsettings-*.json files (which carry deployment-specific values and are gitignored) must be updated to add Site:CorsAllowedOrigins.
2026-06-19 13:15:21 +01:00
var services = builder.Services;
2026-03-09 02:07:09 +00:00
2026-06-19 23:09:43 +01:00
// MvcBuilder
builder.Services
2026-06-04 22:04:20 +01:00
.AddAuthorization(options =>
{
2026-06-06 21:30:41 +01:00
options.AddPolicy("BlogScope", policy =>
2026-06-04 22:04:20 +01:00
{
policy
2026-03-09 02:07:09 +00:00
.RequireAuthenticatedUser()
2026-06-19 23:09:43 +01:00
.RequireClaim(JwtClaimTypes.Scope, new string[] { "blog" });
2026-06-04 22:04:20 +01:00
});
})
Split Site:Audience into Site:ExternalUrl + Site:CorsAllowedOrigins The Site:Audience setting was conflating two distinct concepts: an OAuth JWT audience (a single resource identifier) and a CORS allow-list (an array of origins). Collapsing them caused several latent bugs: - OAuth/JWT validation expected a single string while CORS WithOrigins accepts an array. - Password-reset callback URLs and OAuth client RedirectUri/Origin were being built from what was meant to be an audience identifier, not a base URL. - Yavsc.Org's main CORS policy was hardcoded to '*', with no way to restrict it without code changes. Changes: - SiteSettings.Audience (string) replaced with CorsAllowedOrigins (IList<string>). - OAuth JWT Authority still reads Site:Authority; Audience now reads Site:ExternalUrl (Org only; Api/Blogs use ValidateAudience=false). - MailSender and AccountController build reset-callback URLs from Site:ExternalUrl. - ClientController uses Site:ExternalUrl for OAuth RedirectUri/Origin defaults on newly created clients. - Yavsc.Api and Yavsc.Blogs now read CORS origins from Site:CorsAllowedOrigins instead of hardcoded URLs. Add shared AddYavscCors / AddYavscJwtBearer extension methods in Yavsc.Server/Helpers/ServiceExtensions.cs to enforce a single configuration contract across all runtime services (Api, Blogs, Org). Fails closed when CorsAllowedOrigins is empty; fails fast at startup when Site:Authority is missing. Remove obsolete ConfigurationHelpers.GetAudience (no remaining callers). Local appsettings-*.json files (which carry deployment-specific values and are gitignored) must be updated to add Site:CorsAllowedOrigins.
2026-06-19 13:15:21 +01:00
.AddYavscCors(builder.Configuration)
2026-06-04 22:04:20 +01:00
.AddControllers();
2026-06-19 23:09:43 +01:00
// AuthenticationBuilder
Split Site:Audience into Site:ExternalUrl + Site:CorsAllowedOrigins The Site:Audience setting was conflating two distinct concepts: an OAuth JWT audience (a single resource identifier) and a CORS allow-list (an array of origins). Collapsing them caused several latent bugs: - OAuth/JWT validation expected a single string while CORS WithOrigins accepts an array. - Password-reset callback URLs and OAuth client RedirectUri/Origin were being built from what was meant to be an audience identifier, not a base URL. - Yavsc.Org's main CORS policy was hardcoded to '*', with no way to restrict it without code changes. Changes: - SiteSettings.Audience (string) replaced with CorsAllowedOrigins (IList<string>). - OAuth JWT Authority still reads Site:Authority; Audience now reads Site:ExternalUrl (Org only; Api/Blogs use ValidateAudience=false). - MailSender and AccountController build reset-callback URLs from Site:ExternalUrl. - ClientController uses Site:ExternalUrl for OAuth RedirectUri/Origin defaults on newly created clients. - Yavsc.Api and Yavsc.Blogs now read CORS origins from Site:CorsAllowedOrigins instead of hardcoded URLs. Add shared AddYavscCors / AddYavscJwtBearer extension methods in Yavsc.Server/Helpers/ServiceExtensions.cs to enforce a single configuration contract across all runtime services (Api, Blogs, Org). Fails closed when CorsAllowedOrigins is empty; fails fast at startup when Site:Authority is missing. Remove obsolete ConfigurationHelpers.GetAudience (no remaining callers). Local appsettings-*.json files (which carry deployment-specific values and are gitignored) must be updated to add Site:CorsAllowedOrigins.
2026-06-19 13:15:21 +01:00
services.AddAuthentication("Bearer")
.AddYavscJwtBearer(builder.Configuration);
2026-06-04 22:04:20 +01:00
2026-06-19 23:09:43 +01:00
// DbContextBuilder
2026-06-04 22:04:20 +01:00
services.AddDbContext<ApplicationDbContext>(options =>
2026-06-20 18:43:01 +01:00
options.UseNpgsql(builder.Configuration.GetConnectionString(
YavscConstants.YavscConnectionStringName)));
2026-06-04 22:04:20 +01:00
2026-06-19 23:09:43 +01:00
// other services
services
.AddTransient<ITrueEmailSender, MailSender>()
.AddTransient<IEmailSender<ApplicationUser>, MailSender>()
.TryAddSingleton<ISmtpClientFactory, SmtpClientFactory>();
services
2026-06-19 23:09:43 +01:00
.AddTransient<IBillingService, BillingService>()
.AddTransient<ICalendarManager, CalendarManager>()
.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>()
.AddTransient<BlogSpotService>()
.AddScoped<IAuthorizationHandler, PermissionHandler>()
.AddLocalization(options =>
2026-06-04 22:04:20 +01:00
{
options.ResourcesPath = "Resources";
2026-06-19 23:09:43 +01:00
})
.AddDistributedMemoryCache()
.AddSession(options =>
2026-06-04 22:04:20 +01:00
{
options.IdleTimeout = TimeSpan.FromMinutes(30);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = false;
2026-06-19 23:09:43 +01:00
}).Configure<RequestLocalizationOptions>(options =>
2026-06-04 22:04:20 +01:00
{
var supportedCultures = new[] { "fr", "en", "pt" };
options.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
});
2026-06-19 23:09:43 +01:00
// App startup
2026-06-04 22:04:20 +01:00
using (var app = builder.Build())
{
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
app
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseCors("default")
;
2026-06-19 23:09:43 +01:00
app.MapIdentityApi<ApplicationUser>().RequireAuthorization("blog");
2026-06-04 22:04:20 +01:00
app.MapGet("/identity", (HttpContext context) =>
new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value }))
);
app.UseSession();
2026-06-04 22:04:20 +01:00
await app.RunAsync();
}
2026-03-01 02:13:26 +00:00
}
2026-03-09 02:07:09 +00:00
2026-03-01 02:13:26 +00:00
}