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