From ee7dc8409e0e6fe2cdf3eb6967b01ff18910955a Mon Sep 17 00:00:00 2001 From: Daniel Harvey Date: Mon, 18 May 2026 08:37:18 -0400 Subject: [PATCH] Wire AuthBlocks 10.3.30 JWT auth; gate /cms (InteractiveAuto) behind Admin role; add CreatedByUserId migration --- DeepDrftCli/DeepDrftCli.csproj | 15 ++-- DeepDrftCms/DeepDrftCms.csproj | 1 + DeepDrftCms/Pages/Cms/Index.razor | 4 +- DeepDrftModels/DeepDrftModels.csproj | 2 +- DeepDrftModels/Entities/TrackEntity.cs | 1 + DeepDrftWeb.Client/DeepDrftWeb.Client.csproj | 7 +- DeepDrftWeb.Client/Program.cs | 3 + DeepDrftWeb.Client/Routes.razor | 2 +- DeepDrftWeb.Client/_Imports.razor | 1 + .../Data/Configurations/TrackConfiguration.cs | 4 + .../Data/DeepDrftContextFactory.cs | 8 +- .../DeepDrftWeb.Services.csproj | 6 +- ...60518035137_AddCreatedByUserId.Designer.cs | 85 +++++++++++++++++++ .../20260518035137_AddCreatedByUserId.cs | 28 ++++++ .../DeepDrftContextModelSnapshot.cs | 6 +- DeepDrftWeb/DeepDrftWeb.csproj | 12 +-- DeepDrftWeb/Program.cs | 40 ++++++++- NuGet.Config | 7 ++ docker-compose.yml | 2 +- 19 files changed, 205 insertions(+), 29 deletions(-) create mode 100644 DeepDrftWeb.Services/Migrations/20260518035137_AddCreatedByUserId.Designer.cs create mode 100644 DeepDrftWeb.Services/Migrations/20260518035137_AddCreatedByUserId.cs create mode 100644 NuGet.Config diff --git a/DeepDrftCli/DeepDrftCli.csproj b/DeepDrftCli/DeepDrftCli.csproj index d7dfa06..6846a07 100644 --- a/DeepDrftCli/DeepDrftCli.csproj +++ b/DeepDrftCli/DeepDrftCli.csproj @@ -8,15 +8,16 @@ - + + - - - - - - + + + + + + diff --git a/DeepDrftCms/DeepDrftCms.csproj b/DeepDrftCms/DeepDrftCms.csproj index e9a4616..65b0747 100644 --- a/DeepDrftCms/DeepDrftCms.csproj +++ b/DeepDrftCms/DeepDrftCms.csproj @@ -16,6 +16,7 @@ + diff --git a/DeepDrftCms/Pages/Cms/Index.razor b/DeepDrftCms/Pages/Cms/Index.razor index e667f21..6c6b688 100644 --- a/DeepDrftCms/Pages/Cms/Index.razor +++ b/DeepDrftCms/Pages/Cms/Index.razor @@ -1,5 +1,7 @@ @page "/cms" -@rendermode InteractiveServer +@rendermode InteractiveAuto +@using AuthBlocksWeb.HierarchicalAuthorize +@attribute [HierarchicalRoleAuthorize("Admin")] DeepDrft CMS diff --git a/DeepDrftModels/DeepDrftModels.csproj b/DeepDrftModels/DeepDrftModels.csproj index f7f66b9..b1a2a1c 100644 --- a/DeepDrftModels/DeepDrftModels.csproj +++ b/DeepDrftModels/DeepDrftModels.csproj @@ -7,7 +7,7 @@ - + diff --git a/DeepDrftModels/Entities/TrackEntity.cs b/DeepDrftModels/Entities/TrackEntity.cs index 7e96bf8..042e5ff 100644 --- a/DeepDrftModels/Entities/TrackEntity.cs +++ b/DeepDrftModels/Entities/TrackEntity.cs @@ -10,4 +10,5 @@ public class TrackEntity public string? Genre { get; set; } public DateOnly? ReleaseDate { get; set; } public string? ImagePath { get; set; } + public long? CreatedByUserId { get; set; } } \ No newline at end of file diff --git a/DeepDrftWeb.Client/DeepDrftWeb.Client.csproj b/DeepDrftWeb.Client/DeepDrftWeb.Client.csproj index 2dde35f..5d752eb 100644 --- a/DeepDrftWeb.Client/DeepDrftWeb.Client.csproj +++ b/DeepDrftWeb.Client/DeepDrftWeb.Client.csproj @@ -9,12 +9,13 @@ - + - - + + + diff --git a/DeepDrftWeb.Client/Program.cs b/DeepDrftWeb.Client/Program.cs index 3075f84..6edf5fa 100644 --- a/DeepDrftWeb.Client/Program.cs +++ b/DeepDrftWeb.Client/Program.cs @@ -14,6 +14,9 @@ Startup.ConfigureApiHttpClient(builder.Services, builder.HostEnvironment.BaseAdd Startup.ConfigureContentServices(builder.Services, contentApiUrl); Startup.ConfigureDomainServices(builder.Services); +// AuthBlocks WASM: auth state deserialization bridge (prerender → WASM handoff). +AuthBlocksWeb.Client.Startup.ConfigureServices(builder.Services); + var app = builder.Build(); await app.RunAsync(); diff --git a/DeepDrftWeb.Client/Routes.razor b/DeepDrftWeb.Client/Routes.razor index cba3397..4d2d73b 100644 --- a/DeepDrftWeb.Client/Routes.razor +++ b/DeepDrftWeb.Client/Routes.razor @@ -1,5 +1,5 @@  - + diff --git a/DeepDrftWeb.Client/_Imports.razor b/DeepDrftWeb.Client/_Imports.razor index 3371bf6..95d93af 100644 --- a/DeepDrftWeb.Client/_Imports.razor +++ b/DeepDrftWeb.Client/_Imports.razor @@ -1,5 +1,6 @@ @using System.Net.Http @using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web diff --git a/DeepDrftWeb.Services/Data/Configurations/TrackConfiguration.cs b/DeepDrftWeb.Services/Data/Configurations/TrackConfiguration.cs index 03e920d..a896df4 100644 --- a/DeepDrftWeb.Services/Data/Configurations/TrackConfiguration.cs +++ b/DeepDrftWeb.Services/Data/Configurations/TrackConfiguration.cs @@ -45,5 +45,9 @@ public class TrackConfiguration : IEntityTypeConfiguration builder.Property(x => x.ImagePath) .HasColumnName("image_path") .HasMaxLength(500); + + builder.Property(x => x.CreatedByUserId) + .HasColumnName("created_by_user_id") + .IsRequired(false); } } \ No newline at end of file diff --git a/DeepDrftWeb.Services/Data/DeepDrftContextFactory.cs b/DeepDrftWeb.Services/Data/DeepDrftContextFactory.cs index 97084ab..02e57e5 100644 --- a/DeepDrftWeb.Services/Data/DeepDrftContextFactory.cs +++ b/DeepDrftWeb.Services/Data/DeepDrftContextFactory.cs @@ -7,12 +7,12 @@ public class DeepDrftContextFactory : IDesignTimeDbContextFactory(); optionsBuilder.UseNpgsql(connectionString); diff --git a/DeepDrftWeb.Services/DeepDrftWeb.Services.csproj b/DeepDrftWeb.Services/DeepDrftWeb.Services.csproj index 26da6f1..9eb7741 100644 --- a/DeepDrftWeb.Services/DeepDrftWeb.Services.csproj +++ b/DeepDrftWeb.Services/DeepDrftWeb.Services.csproj @@ -7,12 +7,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/DeepDrftWeb.Services/Migrations/20260518035137_AddCreatedByUserId.Designer.cs b/DeepDrftWeb.Services/Migrations/20260518035137_AddCreatedByUserId.Designer.cs new file mode 100644 index 0000000..4df868a --- /dev/null +++ b/DeepDrftWeb.Services/Migrations/20260518035137_AddCreatedByUserId.Designer.cs @@ -0,0 +1,85 @@ +// +using System; +using DeepDrftWeb.Services.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DeepDrftWeb.Migrations +{ + [DbContext(typeof(DeepDrftContext))] + [Migration("20260518035137_AddCreatedByUserId")] + partial class AddCreatedByUserId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DeepDrftModels.Entities.TrackEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Album") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("album"); + + b.Property("Artist") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("artist"); + + b.Property("CreatedByUserId") + .HasColumnType("bigint") + .HasColumnName("created_by_user_id"); + + b.Property("EntryKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("entry_key"); + + b.Property("Genre") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("genre"); + + b.Property("ImagePath") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("image_path"); + + b.Property("ReleaseDate") + .HasColumnType("date") + .HasColumnName("release_date"); + + b.Property("TrackName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("track_name"); + + b.HasKey("Id"); + + b.ToTable("track", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DeepDrftWeb.Services/Migrations/20260518035137_AddCreatedByUserId.cs b/DeepDrftWeb.Services/Migrations/20260518035137_AddCreatedByUserId.cs new file mode 100644 index 0000000..d1a16a7 --- /dev/null +++ b/DeepDrftWeb.Services/Migrations/20260518035137_AddCreatedByUserId.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DeepDrftWeb.Migrations +{ + /// + public partial class AddCreatedByUserId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "created_by_user_id", + table: "track", + type: "bigint", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "created_by_user_id", + table: "track"); + } + } +} diff --git a/DeepDrftWeb.Services/Migrations/DeepDrftContextModelSnapshot.cs b/DeepDrftWeb.Services/Migrations/DeepDrftContextModelSnapshot.cs index e795600..76ae3af 100644 --- a/DeepDrftWeb.Services/Migrations/DeepDrftContextModelSnapshot.cs +++ b/DeepDrftWeb.Services/Migrations/DeepDrftContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace DeepDrftWeb.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.4") + .HasAnnotation("ProductVersion", "10.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -42,6 +42,10 @@ namespace DeepDrftWeb.Migrations .HasColumnType("character varying(200)") .HasColumnName("artist"); + b.Property("CreatedByUserId") + .HasColumnType("bigint") + .HasColumnName("created_by_user_id"); + b.Property("EntryKey") .IsRequired() .HasMaxLength(100) diff --git a/DeepDrftWeb/DeepDrftWeb.csproj b/DeepDrftWeb/DeepDrftWeb.csproj index bb1e128..5dd4e98 100644 --- a/DeepDrftWeb/DeepDrftWeb.csproj +++ b/DeepDrftWeb/DeepDrftWeb.csproj @@ -8,23 +8,25 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive + + - + diff --git a/DeepDrftWeb/Program.cs b/DeepDrftWeb/Program.cs index ff5eaca..0a9a483 100644 --- a/DeepDrftWeb/Program.cs +++ b/DeepDrftWeb/Program.cs @@ -1,3 +1,5 @@ +using AuthBlocksLib; +using AuthBlocksLib.Options; using DeepDrftCms; using DeepDrftWeb; using MudBlazor.Services; @@ -17,6 +19,32 @@ builder.Services.AddCmsServices(); var baseUrl = builder.GetKestrelUrl(); var contentApiUrl = builder.Configuration["ApiUrls:ContentApi"] ?? throw new Exception("Content API URL is not configured"); +// AuthBlocks: JWT Bearer auth, Identity, EF schema, admin seeding. +// Auth schema runs in its own database (separate from DefaultConnection by design). +builder.Services.AddAuthBlocks(options => +{ + options.ConnectionString = builder.Configuration.GetConnectionString("Auth")!; + options.ApplicationName = "DeepDrft"; + options.SupportEmail = builder.Configuration["AuthBlocks:SupportEmail"] ?? "admin@deepdrft.com"; + + options.JwtSettings.Secret = builder.Configuration["AuthBlocks:Jwt:Secret"]!; + options.JwtSettings.Issuer = builder.Configuration["AuthBlocks:Jwt:Issuer"]!; + options.JwtSettings.Audience = builder.Configuration["AuthBlocks:Jwt:Audience"]!; + + options.EmailConnection.Host = builder.Configuration["AuthBlocks:Email:Host"]!; + options.EmailConnection.Token = builder.Configuration["AuthBlocks:Email:Token"]!; + + options.AdminUserSettings = new AdminUserSettings + { + UserName = builder.Configuration["AuthBlocks:Admin:UserName"]!, + Email = builder.Configuration["AuthBlocks:Admin:Email"]!, + Password = builder.Configuration["AuthBlocks:Admin:Password"]! + }; +}); + +// AuthBlocksWeb: Blazor JWT client services (auth API is mounted on this same host via MapAuthBlocks). +AuthBlocksWeb.Startup.ConfigureAuthServices(builder.Services, baseUrl); + DeepDrftWeb.Client.Startup.ConfigureApiHttpClient(builder.Services, baseUrl); DeepDrftWeb.Client.Startup.ConfigureDomainServices(builder.Services); DeepDrftWeb.Client.Startup.ConfigureContentServices(builder.Services, contentApiUrl); @@ -52,6 +80,9 @@ builder.Services.Configure(options => var app = builder.Build(); +// Apply AuthBlocks EF migrations, seed system roles, seed admin user on first boot. +await app.Services.UseAuthBlocksStartupAsync(); + // Configure the HTTP request pipeline. // Use forwarded headers before other middleware app.UseForwardedHeaders(); @@ -65,7 +96,7 @@ else app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); - + // Only use HTTPS redirection if not behind a reverse proxy var disableHttpsRedirection = app.Configuration.GetValue("ForwardedHeaders:DisableHttpsRedirection"); if (!disableHttpsRedirection) @@ -74,6 +105,9 @@ else } } +app.UseAuthentication(); +app.UseAuthorization(); + app.UseAntiforgery(); // Configure cache headers for Blazor WebAssembly assets @@ -106,12 +140,14 @@ if (app.Environment.IsDevelopment()) } app.MapControllers(); +app.MapAuthBlocks(); // registers /api/auth/*, /api/users/*, /api/roles/*, /api/user-roles/*, /api/pending-registrations/* app.MapRazorComponents() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode() .AddAdditionalAssemblies( typeof(DeepDrftWeb.Client._Imports).Assembly, - typeof(DeepDrftCms._Imports).Assembly); + typeof(DeepDrftCms._Imports).Assembly, + typeof(AuthBlocksWeb._Imports).Assembly); // exposes /account/login, /account/logout app.Run(); diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..7a1970a --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docker-compose.yml b/docker-compose.yml index 9f7d2ac..8abd963 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: postgres: - image: postgres:17 + image: postgres:18 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres