Added components, authentication and authorization

This commit is contained in:
2026-05-04 16:53:19 +05:00
parent 493cd71d17
commit fb1cb8e834
37 changed files with 3545 additions and 21 deletions
+78
View File
@@ -1,22 +1,100 @@
using Htmx.ApiDemo;
using Htmx.ApiDemo.Data;
using Immediate.Apis;
using Immediate.Apis.Shared;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Identity;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver;
// ── Explicit BsonClassMap — no AutoMap() reflection, fully AOT-safe ───────
BsonClassMap.RegisterClassMap<AppUser>(cm =>
{
cm.MapIdProperty(u => u.Id).SetSerializer(new ObjectIdSerializer());
cm.MapProperty(u => u.Email).SetElementName("email");
cm.MapProperty(u => u.NormalizedEmail).SetElementName("normalizedEmail");
cm.MapProperty(u => u.PasswordHash).SetElementName("passwordHash");
cm.MapProperty(u => u.DisplayName).SetElementName("displayName");
cm.MapProperty(u => u.CreatedAtUtc).SetElementName("createdAtUtc");
cm.SetIgnoreExtraElements(true);
});
var builder = WebApplication.CreateSlimBuilder(args);
// ── Antiforgery ───────────────────────────────────────────────────────────
builder.Services.AddAntiforgery();
// ── JSON ──────────────────────────────────────────────────────────────────
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});
// ── MongoDB ───────────────────────────────────────────────────────────────
builder.Services.AddSingleton<IMongoClient>(
new MongoClient(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<MongoDbService>();
// ── Cookie Authentication ─────────────────────────────────────────────────
builder.Services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/login";
options.LogoutPath = "/logout";
options.AccessDeniedPath = "/login";
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromHours(8);
});
builder.Services.AddScoped<IPasswordHasher<AppUser>, PasswordHasher<AppUser>>();
builder.Services.AddScoped<AuthService>();
// ── App services ──────────────────────────────────────────────────────────
builder.Services.AddHttpContextAccessor();
builder.Services
.AddHtmxApiDemoBehaviors()
.AddHtmxApiDemoHandlers();
builder.Services.AddOpenApi();
builder.Services.AddAuthorization();
var app = builder.Build();
// Ensure the unique index on NormalizedEmail exists (runs once on startup, idempotent).
using (var scope = app.Services.CreateScope())
await scope.ServiceProvider.GetRequiredService<MongoDbService>().EnsureIndexesAsync();
if (app.Environment.IsDevelopment())
app.MapOpenApi();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
// ── Guard: redirect unauthenticated users to /login ───────────────────────
app.Use(async (context, next) =>
{
var path = context.Request.Path.Value ?? "";
bool isPublic = path.StartsWith("/login", StringComparison.OrdinalIgnoreCase)
|| path.StartsWith("/register", StringComparison.OrdinalIgnoreCase)
|| path.StartsWith("/logout", StringComparison.OrdinalIgnoreCase)
|| path.StartsWith("/css/", StringComparison.OrdinalIgnoreCase)
|| path.StartsWith("/js/", StringComparison.OrdinalIgnoreCase);
if (!isPublic && context.User.Identity?.IsAuthenticated != true)
{
context.Response.Redirect("/login");
return;
}
await next();
});
app.MapHtmxApiDemoEndpoints();
app.Run();