diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2022-06-16 19:04:29 +0200 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2022-06-16 20:47:49 +0200 |
| commit | a19e31557f6ef33ed33d694968abe7416e878c60 (patch) | |
| tree | 7de1ac12c4fea95e0a6909e1e5d05e5c490858de | |
| parent | e86e98b2f96e91f583e8c3849f06ce0cc12fe8da (diff) | |
| download | greatoffice-a19e31557f6ef33ed33d694968abe7416e878c60.tar.xz greatoffice-a19e31557f6ef33ed33d694968abe7416e878c60.zip | |
feat: Add support for db-backed data protection key persistence
Also update packages
| -rw-r--r-- | server/src/Data/AppDbContext.cs | 5 | ||||
| -rw-r--r-- | server/src/IOL.GreatOffice.Api.csproj | 5 | ||||
| -rw-r--r-- | server/src/Migrations/20220616170311_DataProtectionKeys.Designer.cs | 533 | ||||
| -rw-r--r-- | server/src/Migrations/20220616170311_DataProtectionKeys.cs | 33 | ||||
| -rw-r--r-- | server/src/Migrations/AppDbContextModelSnapshot.cs | 25 | ||||
| -rw-r--r-- | server/src/Program.cs | 3 |
6 files changed, 598 insertions, 6 deletions
diff --git a/server/src/Data/AppDbContext.cs b/server/src/Data/AppDbContext.cs index ae34861..a5ef36e 100644 --- a/server/src/Data/AppDbContext.cs +++ b/server/src/Data/AppDbContext.cs @@ -1,6 +1,8 @@ +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; + namespace IOL.GreatOffice.Api.Data; -public class AppDbContext : DbContext +public class AppDbContext : DbContext, IDataProtectionKeyContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<User> Users { get; set; } @@ -11,6 +13,7 @@ public class AppDbContext : DbContext public DbSet<GithubUserMapping> GithubUserMappings { get; set; } public DbSet<ApiAccessToken> AccessTokens { get; set; } public DbSet<Tenant> Tenants { get; set; } + public DbSet<DataProtectionKey> DataProtectionKeys { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<User>(e => { diff --git a/server/src/IOL.GreatOffice.Api.csproj b/server/src/IOL.GreatOffice.Api.csproj index 44570c0..dde3aaa 100644 --- a/server/src/IOL.GreatOffice.Api.csproj +++ b/server/src/IOL.GreatOffice.Api.csproj @@ -15,10 +15,11 @@ <PackageReference Include="Duende.IdentityServer" Version="6.1.0" /> <PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="6.1.0" /> <PackageReference Include="EFCore.NamingConventions" Version="6.0.0" /> - <PackageReference Include="IOL.Helpers" Version="3.0.0" /> + <PackageReference Include="IOL.Helpers" Version="3.1.0" /> + <PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="6.0.6" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5"> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.6"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> diff --git a/server/src/Migrations/20220616170311_DataProtectionKeys.Designer.cs b/server/src/Migrations/20220616170311_DataProtectionKeys.Designer.cs new file mode 100644 index 0000000..b333f23 --- /dev/null +++ b/server/src/Migrations/20220616170311_DataProtectionKeys.Designer.cs @@ -0,0 +1,533 @@ +// <auto-generated /> +using System; +using IOL.GreatOffice.Api.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 IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20220616170311_DataProtectionKeys")] + partial class DataProtectionKeys + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<bool>("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property<bool>("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property<bool>("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property<bool>("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime>("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.Property<string>("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property<string>("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property<string>("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<string>("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<Guid>("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property<string>("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<string>("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<Guid?>("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property<DateTime>("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property<DateTime>("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<string>("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("TimeEntryId") + .HasColumnType("uuid") + .HasColumnName("time_entry_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<string>("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property<string>("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property<string>("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<string>("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property<string>("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<string>("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property<string>("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.Property<Guid>("TenantsId") + .HasColumnType("uuid") + .HasColumnName("tenants_id"); + + b.Property<Guid>("UsersId") + .HasColumnType("uuid") + .HasColumnName("users_id"); + + b.HasKey("TenantsId", "UsersId") + .HasName("pk_tenant_user"); + + b.HasIndex("UsersId") + .HasDatabaseName("ix_tenant_user_users_id"); + + b.ToTable("tenant_user", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany() + .HasForeignKey("TenantsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_tenants_tenants_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_users_users_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/server/src/Migrations/20220616170311_DataProtectionKeys.cs b/server/src/Migrations/20220616170311_DataProtectionKeys.cs new file mode 100644 index 0000000..bc3c673 --- /dev/null +++ b/server/src/Migrations/20220616170311_DataProtectionKeys.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class DataProtectionKeys : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "data_protection_keys", + columns: table => new + { + id = table.Column<int>(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + friendly_name = table.Column<string>(type: "text", nullable: true), + xml = table.Column<string>(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_data_protection_keys", x => x.id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "data_protection_keys"); + } + } +} diff --git a/server/src/Migrations/AppDbContextModelSnapshot.cs b/server/src/Migrations/AppDbContextModelSnapshot.cs index 70582b5..128d3b6 100644 --- a/server/src/Migrations/AppDbContextModelSnapshot.cs +++ b/server/src/Migrations/AppDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace IOL.GreatOffice.Api.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("ProductVersion", "6.0.6") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -412,6 +412,29 @@ namespace IOL.GreatOffice.Api.Migrations b.ToTable("users", (string)null); }); + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<string>("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property<string>("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + modelBuilder.Entity("TenantUser", b => { b.Property<Guid>("TenantsId") diff --git a/server/src/Program.cs b/server/src/Program.cs index 4499b1a..b7e6ce6 100644 --- a/server/src/Program.cs +++ b/server/src/Program.cs @@ -89,8 +89,7 @@ public static class Program }); } - builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(AppPaths.DataProtectionKeys.HostPath)); - + builder.Services.AddDataProtection().PersistKeysToDbContext<AppDbContext>(); builder.Services.Configure(JsonSettings.Default); builder.Services.AddQuartz(options => { options.UsePersistentStore(o => { |
