[Infra] Persist DataProtection keys in the DB (fixes logout/antiforgery on deploy)
Add Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; AppDbContext implements IDataProtectionKeyContext with a DataProtectionKeys set; PersistKeysToDbContext + SetApplicationName(hamkadr). Now the key ring is shared across restarts/replicas, so auth cookies, antiforgery tokens and the captcha no longer break on every deploy (the root cause of the earlier admin lock-out). Migration: DataProtectionKeys table. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
using JobsMedical.Web.Models;
|
||||
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace JobsMedical.Web.Data;
|
||||
|
||||
public class AppDbContext : DbContext
|
||||
public class AppDbContext : DbContext, IDataProtectionKeyContext
|
||||
{
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
|
||||
|
||||
/// <summary>DataProtection key ring — persisted so antiforgery/cookies survive deploys & replicas.</summary>
|
||||
public DbSet<DataProtectionKey> DataProtectionKeys => Set<DataProtectionKey>();
|
||||
|
||||
public DbSet<City> Cities => Set<City>();
|
||||
public DbSet<District> Districts => Set<District>();
|
||||
public DbSet<Role> Roles => Set<Role>();
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="10.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
|
||||
<PackageReference Include="WebPush" Version="1.0.12" />
|
||||
</ItemGroup>
|
||||
|
||||
+1267
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace JobsMedical.Web.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class DataProtectionKeys : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DataProtectionKeys",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
FriendlyName = table.Column<string>(type: "text", nullable: true),
|
||||
Xml = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DataProtectionKeys", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DataProtectionKeys");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -970,6 +970,25 @@ namespace JobsMedical.Web.Migrations
|
||||
b.ToTable("WebPushSubscriptions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Xml")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("DataProtectionKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JobsMedical.Web.Models.Application", b =>
|
||||
{
|
||||
b.HasOne("JobsMedical.Web.Models.User", "Doctor")
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Text.Unicode;
|
||||
using JobsMedical.Web.Data;
|
||||
using JobsMedical.Web.Models;
|
||||
using JobsMedical.Web.Services;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -77,6 +78,13 @@ builder.Services.AddSingleton(HtmlEncoder.Create(
|
||||
builder.Services.AddDbContext<AppDbContext>(opt =>
|
||||
opt.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
|
||||
|
||||
// Persist the DataProtection key ring in the DB so antiforgery tokens, auth cookies and the
|
||||
// captcha survive deploys/restarts (otherwise a new key ring each boot logs everyone out and
|
||||
// breaks antiforgery — the cause of the earlier admin lock-out).
|
||||
builder.Services.AddDataProtection()
|
||||
.PersistKeysToDbContext<AppDbContext>()
|
||||
.SetApplicationName("hamkadr");
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Apply migrations + seed on startup (fine for MVP single-instance deploy).
|
||||
|
||||
Reference in New Issue
Block a user