From 4e9e428ab52e6f77eeabe1171548fd0fe098dee2 Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Sun, 14 Jun 2026 09:57:24 +0200 Subject: [PATCH] Added data protection database based support --- LiteCharms.Features/Extensions/Api.cs | 17 ++----- LiteCharms.Features/Extensions/Postgres.cs | 25 +++++++++- .../LiteCharms.Features.csproj | 2 + .../Postgres/DataProtectionDbContext.cs | 13 +++++ .../DataProtectionDbContextFactory.cs | 20 ++++++++ .../20260614075149_Init.Designer.cs | 48 +++++++++++++++++++ .../Migrations/20260614075149_Init.cs | 41 ++++++++++++++++ .../DataProtectionDbContextModelSnapshot.cs | 45 +++++++++++++++++ 8 files changed, 196 insertions(+), 15 deletions(-) create mode 100644 LiteCharms.Features/Postgres/DataProtectionDbContext.cs create mode 100644 LiteCharms.Features/Postgres/DataProtectionDbContextFactory.cs create mode 100644 LiteCharms.Features/Postgres/Migrations/20260614075149_Init.Designer.cs create mode 100644 LiteCharms.Features/Postgres/Migrations/20260614075149_Init.cs create mode 100644 LiteCharms.Features/Postgres/Migrations/DataProtectionDbContextModelSnapshot.cs diff --git a/LiteCharms.Features/Extensions/Api.cs b/LiteCharms.Features/Extensions/Api.cs index 28d2875..77b1968 100644 --- a/LiteCharms.Features/Extensions/Api.cs +++ b/LiteCharms.Features/Extensions/Api.cs @@ -2,6 +2,7 @@ using LiteCharms.Features.Api; using LiteCharms.Features.Api.Configuration; using LiteCharms.Features.Api.Sdk; +using LiteCharms.Features.Postgres; using Microsoft.AspNetCore.Hosting; using System.Runtime.InteropServices; @@ -53,20 +54,10 @@ public static class Api return services; } - public static IServiceCollection AddLiteCharmsWebSecurity(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment environment) + public static IServiceCollection AddLiteCharmsWebSecurity(this IServiceCollection services, IConfiguration configuration) { - string keysFolderPath; - - if (OperatingSystem.IsLinux()) - keysFolderPath = "/app/shared-keys"; - else - keysFolderPath = Path.Combine(environment.ContentRootPath, "obj", "DeveloperDataProtectionKeys"); - - if (!Directory.Exists(keysFolderPath)) Directory.CreateDirectory(keysFolderPath); - - services.AddDataProtection() - .PersistKeysToFileSystem(new DirectoryInfo(keysFolderPath)) - .SetApplicationName("MidrandBookshop"); + services.AddDataProtection().PersistKeysToDbContext() + .SetApplicationName("LiteCharmsApp"); var configSection = configuration.GetSection(nameof(LiteCharmsSettings)); diff --git a/LiteCharms.Features/Extensions/Postgres.cs b/LiteCharms.Features/Extensions/Postgres.cs index 8a9541a..cba918b 100644 --- a/LiteCharms.Features/Extensions/Postgres.cs +++ b/LiteCharms.Features/Extensions/Postgres.cs @@ -1,6 +1,27 @@ -namespace LiteCharms.Features.Extensions; +using LiteCharms.Features.Postgres; + +namespace LiteCharms.Features.Extensions; public static class Postgres { - public const string SchedulerDbConfigName = "PostgresScheduler"; + public const string SchedulerDbConfigName = "PostgresScheduler"; + public const string DataProtectionDbConfigName = "PostgresDataProtection"; + + public static IServiceCollection AddDataProtectionDatabase(this IServiceCollection services, IConfiguration configuration) + { + var connectionString = configuration.GetConnectionString(DataProtectionDbConfigName); + + var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString); + + dataSourceBuilder.ConfigureTypeLoading(options => { options.EnableTypeLoading(false); }); + + var dataSource = dataSourceBuilder.Build(); + + services.AddSingleton(dataSource); + + services.AddPooledDbContextFactory(options => + options.UseNpgsql(dataSource)); + + return services; + } } diff --git a/LiteCharms.Features/LiteCharms.Features.csproj b/LiteCharms.Features/LiteCharms.Features.csproj index 4ff6ca2..3d04244 100644 --- a/LiteCharms.Features/LiteCharms.Features.csproj +++ b/LiteCharms.Features/LiteCharms.Features.csproj @@ -153,9 +153,11 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/LiteCharms.Features/Postgres/DataProtectionDbContext.cs b/LiteCharms.Features/Postgres/DataProtectionDbContext.cs new file mode 100644 index 0000000..3c44486 --- /dev/null +++ b/LiteCharms.Features/Postgres/DataProtectionDbContext.cs @@ -0,0 +1,13 @@ +namespace LiteCharms.Features.Postgres; + +public class DataProtectionDbContext(DbContextOptions options) : DbContext(options), IDataProtectionKeyContext +{ + public DbSet DataProtectionKeys { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(entity => entity.ToTable(nameof(DataProtectionKeys), schema: "security")); + } +} diff --git a/LiteCharms.Features/Postgres/DataProtectionDbContextFactory.cs b/LiteCharms.Features/Postgres/DataProtectionDbContextFactory.cs new file mode 100644 index 0000000..f76c93f --- /dev/null +++ b/LiteCharms.Features/Postgres/DataProtectionDbContextFactory.cs @@ -0,0 +1,20 @@ +using static LiteCharms.Features.Extensions.Postgres; + +namespace LiteCharms.Features.Postgres; + +public class DataProtectionDbContextFactory : IDesignTimeDbContextFactory +{ + public DataProtectionDbContext CreateDbContext(string[] args) + { + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddUserSecrets(typeof(DataProtectionDbContext).Assembly) + .AddEnvironmentVariables() + .Build(); + + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseNpgsql(configuration.GetConnectionString(DataProtectionDbConfigName)); + + return new DataProtectionDbContext(optionsBuilder.Options); + } +} diff --git a/LiteCharms.Features/Postgres/Migrations/20260614075149_Init.Designer.cs b/LiteCharms.Features/Postgres/Migrations/20260614075149_Init.Designer.cs new file mode 100644 index 0000000..6948788 --- /dev/null +++ b/LiteCharms.Features/Postgres/Migrations/20260614075149_Init.Designer.cs @@ -0,0 +1,48 @@ +// +using LiteCharms.Features.Postgres; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace LiteCharms.Features.Postgres.Migrations +{ + [DbContext(typeof(DataProtectionDbContext))] + [Migration("20260614075149_Init")] + partial class Init + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys", "security"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LiteCharms.Features/Postgres/Migrations/20260614075149_Init.cs b/LiteCharms.Features/Postgres/Migrations/20260614075149_Init.cs new file mode 100644 index 0000000..7b3d87c --- /dev/null +++ b/LiteCharms.Features/Postgres/Migrations/20260614075149_Init.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace LiteCharms.Features.Postgres.Migrations +{ + /// + public partial class Init : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "security"); + + migrationBuilder.CreateTable( + name: "DataProtectionKeys", + schema: "security", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + FriendlyName = table.Column(type: "text", nullable: true), + Xml = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DataProtectionKeys", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DataProtectionKeys", + schema: "security"); + } + } +} diff --git a/LiteCharms.Features/Postgres/Migrations/DataProtectionDbContextModelSnapshot.cs b/LiteCharms.Features/Postgres/Migrations/DataProtectionDbContextModelSnapshot.cs new file mode 100644 index 0000000..f816a15 --- /dev/null +++ b/LiteCharms.Features/Postgres/Migrations/DataProtectionDbContextModelSnapshot.cs @@ -0,0 +1,45 @@ +// +using LiteCharms.Features.Postgres; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace LiteCharms.Features.Postgres.Migrations +{ + [DbContext(typeof(DataProtectionDbContext))] + partial class DataProtectionDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys", "security"); + }); +#pragma warning restore 612, 618 + } + } +}