Add databucket history table, update encryption keys table (#2159)
This commit is contained in:
@@ -159,6 +159,12 @@ public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyCon
|
||||
/// </summary>
|
||||
public DbSet<VaultDataBucket> VaultDataBuckets { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the VaultDataBucketsHistory DbSet. Superseded bucket revisions kept for backup/rollback,
|
||||
/// pruned by the bucket retention policy.
|
||||
/// </summary>
|
||||
public DbSet<VaultDataBucketsHistory> VaultDataBucketsHistory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the VaultBlobObjects DbSet. These represent encrypted blobs referenced by one or more vault revisions.
|
||||
/// </summary>
|
||||
@@ -353,18 +359,30 @@ public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyCon
|
||||
.HasForeignKey(m => m.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// Configure VaultDataBucket - Id PK with a revision history per (UserId, Kind).
|
||||
// Configure VaultDataBucket - the current revision of each (user, category) bucket, updated in place.
|
||||
modelBuilder.Entity<VaultDataBucket>(builder =>
|
||||
{
|
||||
builder.HasKey(e => e.RevisionId);
|
||||
builder.HasKey(e => new { e.OwnerUserId, e.Category });
|
||||
builder.Property(e => e.OwnerUserId).HasMaxLength(255);
|
||||
builder.Property(e => e.Category).HasConversion<string>().HasMaxLength(50);
|
||||
builder.HasIndex(e => new { e.OwnerUserId, e.Category, e.RevisionNumber }).IsUnique();
|
||||
builder.HasOne(e => e.User)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.OwnerUserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// Configure VaultDataBucketsHistory - superseded bucket revisions, pruned by the bucket retention policy.
|
||||
modelBuilder.Entity<VaultDataBucketsHistory>(builder =>
|
||||
{
|
||||
builder.HasKey(e => new { e.OwnerUserId, e.Category, e.RevisionNumber });
|
||||
builder.Property(e => e.OwnerUserId).HasMaxLength(255);
|
||||
builder.Property(e => e.Category).HasConversion<string>().HasMaxLength(50);
|
||||
builder.HasOne(e => e.Bucket)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => new { e.OwnerUserId, e.Category })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// Configure VaultBlobObject - composite key (Hash, UserId).
|
||||
modelBuilder.Entity<VaultBlobObject>(builder =>
|
||||
{
|
||||
|
||||
Generated
-1263
File diff suppressed because it is too large
Load Diff
@@ -1,179 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddVaultV2Storage : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// --- Transform the existing "Vaults" table into "VaultManifests" IN PLACE (preserves every vault row) ---
|
||||
|
||||
// Storage-format columns. Add nullable, backfill every existing (legacy) vault explicitly, then enforce
|
||||
// NOT NULL. This avoids leaving a persistent column default — the storage format is always set explicitly
|
||||
// by the app.
|
||||
migrationBuilder.AddColumn<string>(name: "ManifestBlob", table: "Vaults", type: "text", nullable: true);
|
||||
migrationBuilder.AddColumn<string>(name: "ManifestCiphertextHash", table: "Vaults", type: "character varying(64)", maxLength: 64, nullable: true);
|
||||
migrationBuilder.AddColumn<string>(name: "StorageFormat", table: "Vaults", type: "character varying(20)", maxLength: 20, nullable: true);
|
||||
migrationBuilder.Sql("UPDATE \"Vaults\" SET \"StorageFormat\" = 'sqlite-blob' WHERE \"StorageFormat\" IS NULL;");
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "StorageFormat",
|
||||
table: "Vaults",
|
||||
type: "character varying(20)",
|
||||
maxLength: 20,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(20)",
|
||||
oldMaxLength: 20,
|
||||
oldNullable: true);
|
||||
|
||||
// Rename "Vaults" -> "VaultManifests" in place, including constraints and indexes.
|
||||
migrationBuilder.RenameTable(name: "Vaults", newName: "VaultManifests");
|
||||
migrationBuilder.RenameColumn(name: "Id", table: "VaultManifests", newName: "RevisionId");
|
||||
migrationBuilder.RenameColumn(name: "UserId", table: "VaultManifests", newName: "OwnerUserId");
|
||||
migrationBuilder.Sql(@"ALTER TABLE ""VaultManifests"" RENAME CONSTRAINT ""PK_Vaults"" TO ""PK_VaultManifests"";");
|
||||
migrationBuilder.Sql(@"ALTER TABLE ""VaultManifests"" RENAME CONSTRAINT ""FK_Vaults_AliasVaultUsers_UserId"" TO ""FK_VaultManifests_AliasVaultUsers_OwnerUserId"";");
|
||||
migrationBuilder.RenameIndex(name: "IX_Vaults_UserId", table: "VaultManifests", newName: "IX_VaultManifests_OwnerUserId");
|
||||
|
||||
// New manifest-lineage columns. Add nullable, backfill every existing row, then enforce NOT NULL.
|
||||
migrationBuilder.AddColumn<Guid>(name: "ManifestId", table: "VaultManifests", type: "uuid", nullable: true);
|
||||
migrationBuilder.AddColumn<string>(name: "Category", table: "VaultManifests", type: "character varying(20)", maxLength: 20, nullable: true);
|
||||
|
||||
// Every existing revision belongs to the owner's single "Main" manifest. All of an owner's rows must share
|
||||
// ONE ManifestId (revisions of the same logical manifest), so assign one new GUID per distinct owner.
|
||||
migrationBuilder.Sql(@"UPDATE ""VaultManifests"" v SET ""ManifestId"" = sub.gid FROM (SELECT DISTINCT ""OwnerUserId"", gen_random_uuid() AS gid FROM ""VaultManifests"") sub WHERE v.""OwnerUserId"" = sub.""OwnerUserId"";");
|
||||
migrationBuilder.Sql(@"UPDATE ""VaultManifests"" SET ""Category"" = 'Main' WHERE ""Category"" IS NULL;");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "ManifestId",
|
||||
table: "VaultManifests",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Category",
|
||||
table: "VaultManifests",
|
||||
type: "character varying(20)",
|
||||
maxLength: 20,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(20)",
|
||||
oldMaxLength: 20,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VaultManifests_ManifestId_RevisionNumber",
|
||||
table: "VaultManifests",
|
||||
columns: new[] { "ManifestId", "RevisionNumber" });
|
||||
|
||||
// --- New tables ---
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VaultBlobObjects",
|
||||
columns: table => new
|
||||
{
|
||||
Hash = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
OwnerUserId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Category = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
EncryptedData = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||
SizeBytes = table.Column<int>(type: "integer", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
LastReferencedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VaultBlobObjects", x => new { x.Hash, x.OwnerUserId });
|
||||
table.ForeignKey(
|
||||
name: "FK_VaultBlobObjects_AliasVaultUsers_OwnerUserId",
|
||||
column: x => x.OwnerUserId,
|
||||
principalTable: "AliasVaultUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VaultDataBuckets",
|
||||
columns: table => new
|
||||
{
|
||||
RevisionId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
OwnerUserId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Category = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
EncryptedData = table.Column<string>(type: "text", nullable: false),
|
||||
RevisionNumber = table.Column<long>(type: "bigint", nullable: false),
|
||||
CiphertextHash = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VaultDataBuckets", x => x.RevisionId);
|
||||
table.ForeignKey(
|
||||
name: "FK_VaultDataBuckets_AliasVaultUsers_OwnerUserId",
|
||||
column: x => x.OwnerUserId,
|
||||
principalTable: "AliasVaultUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VaultBlobReferences",
|
||||
columns: table => new
|
||||
{
|
||||
ManifestRevisionId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
BlobHash = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VaultBlobReferences", x => new { x.ManifestRevisionId, x.BlobHash });
|
||||
table.ForeignKey(
|
||||
name: "FK_VaultBlobReferences_VaultManifests_ManifestRevisionId",
|
||||
column: x => x.ManifestRevisionId,
|
||||
principalTable: "VaultManifests",
|
||||
principalColumn: "RevisionId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VaultBlobObjects_OwnerUserId_Category",
|
||||
table: "VaultBlobObjects",
|
||||
columns: new[] { "OwnerUserId", "Category" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VaultDataBuckets_OwnerUserId_Category_RevisionNumber",
|
||||
table: "VaultDataBuckets",
|
||||
columns: new[] { "OwnerUserId", "Category", "RevisionNumber" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Drop the new tables (VaultBlobReferences first — it FKs into VaultManifests).
|
||||
migrationBuilder.DropTable(name: "VaultBlobReferences");
|
||||
migrationBuilder.DropTable(name: "VaultBlobObjects");
|
||||
migrationBuilder.DropTable(name: "VaultDataBuckets");
|
||||
|
||||
// Reverse VaultManifests -> Vaults, in place.
|
||||
migrationBuilder.DropIndex(name: "IX_VaultManifests_ManifestId_RevisionNumber", table: "VaultManifests");
|
||||
migrationBuilder.DropColumn(name: "Category", table: "VaultManifests");
|
||||
migrationBuilder.DropColumn(name: "ManifestId", table: "VaultManifests");
|
||||
migrationBuilder.RenameIndex(name: "IX_VaultManifests_OwnerUserId", table: "VaultManifests", newName: "IX_Vaults_UserId");
|
||||
migrationBuilder.Sql(@"ALTER TABLE ""VaultManifests"" RENAME CONSTRAINT ""FK_VaultManifests_AliasVaultUsers_OwnerUserId"" TO ""FK_Vaults_AliasVaultUsers_UserId"";");
|
||||
migrationBuilder.Sql(@"ALTER TABLE ""VaultManifests"" RENAME CONSTRAINT ""PK_VaultManifests"" TO ""PK_Vaults"";");
|
||||
migrationBuilder.RenameColumn(name: "OwnerUserId", table: "VaultManifests", newName: "UserId");
|
||||
migrationBuilder.RenameColumn(name: "RevisionId", table: "VaultManifests", newName: "Id");
|
||||
migrationBuilder.RenameTable(name: "VaultManifests", newName: "Vaults");
|
||||
|
||||
// Drop the storage-format columns added above.
|
||||
migrationBuilder.DropColumn(name: "StorageFormat", table: "Vaults");
|
||||
migrationBuilder.DropColumn(name: "ManifestCiphertextHash", table: "Vaults");
|
||||
migrationBuilder.DropColumn(name: "ManifestBlob", table: "Vaults");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
-1351
File diff suppressed because it is too large
Load Diff
-270
@@ -1,270 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RestructureVaultManifests : Migration
|
||||
{
|
||||
/// <summary>
|
||||
/// Restructures vault manifest storage into a current + history model:
|
||||
/// - "VaultManifests" becomes one row per logical manifest (PK = ManifestId) holding the current revision.
|
||||
/// - Superseded revisions move to the new "VaultManifestsHistory" table (PK = ManifestId, RevisionNumber).
|
||||
/// - The Category enum column is replaced by an IsRoot boolean; exactly one root manifest per owner is
|
||||
/// enforced via a partial unique index.
|
||||
/// - "VaultBlobReferences" is re-keyed from the per-revision RevisionId GUID to (ManifestId, RevisionNumber).
|
||||
/// </summary>
|
||||
/// <param name="migrationBuilder">MigrationBuilder instance.</param>
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// 1) Replace Category ('Main' | 'SharedFolder') with the IsRoot boolean.
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsRoot",
|
||||
table: "VaultManifests",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.Sql("""UPDATE "VaultManifests" SET "IsRoot" = ("Category" = 'Main');""");
|
||||
|
||||
// 2) Re-key blob references from the per-revision RevisionId GUID to (ManifestId, RevisionNumber)
|
||||
// while the old VaultManifests revision rows (and their RevisionId column) still exist.
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_VaultBlobReferences_VaultManifests_ManifestRevisionId",
|
||||
table: "VaultBlobReferences");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_VaultBlobReferences",
|
||||
table: "VaultBlobReferences");
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "RevisionNumber",
|
||||
table: "VaultBlobReferences",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
|
||||
migrationBuilder.Sql("""
|
||||
UPDATE "VaultBlobReferences" r
|
||||
SET "ManifestRevisionId" = v."ManifestId", "RevisionNumber" = v."RevisionNumber"
|
||||
FROM "VaultManifests" v
|
||||
WHERE r."ManifestRevisionId" = v."RevisionId";
|
||||
""");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ManifestRevisionId",
|
||||
table: "VaultBlobReferences",
|
||||
newName: "ManifestId");
|
||||
|
||||
// 3) Create the history table (FK to VaultManifests is added after the current table gets its new PK).
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VaultManifestsHistory",
|
||||
columns: table => new
|
||||
{
|
||||
ManifestId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
RevisionNumber = table.Column<long>(type: "bigint", nullable: false),
|
||||
OwnerUserId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
VaultBlob = table.Column<string>(type: "text", nullable: false),
|
||||
StorageFormat = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
ManifestBlob = table.Column<string>(type: "text", nullable: true),
|
||||
ManifestCiphertextHash = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
||||
Version = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
FileSize = table.Column<int>(type: "integer", nullable: false),
|
||||
Salt = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
Verifier = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: false),
|
||||
CredentialsCount = table.Column<int>(type: "integer", nullable: false),
|
||||
EmailClaimsCount = table.Column<int>(type: "integer", nullable: false),
|
||||
EncryptionType = table.Column<string>(type: "text", nullable: false),
|
||||
EncryptionSettings = table.Column<string>(type: "text", nullable: false),
|
||||
Client = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VaultManifestsHistory", x => new { x.ManifestId, x.RevisionNumber });
|
||||
});
|
||||
|
||||
// 4) Move every superseded revision into history.
|
||||
migrationBuilder.Sql("""
|
||||
INSERT INTO "VaultManifestsHistory" ("ManifestId", "RevisionNumber", "OwnerUserId", "VaultBlob", "StorageFormat", "ManifestBlob", "ManifestCiphertextHash", "Version", "FileSize", "Salt", "Verifier", "CredentialsCount", "EmailClaimsCount", "EncryptionType", "EncryptionSettings", "Client", "CreatedAt", "UpdatedAt")
|
||||
SELECT "ManifestId", "RevisionNumber", "OwnerUserId", "VaultBlob", "StorageFormat", "ManifestBlob", "ManifestCiphertextHash", "Version", "FileSize", "Salt", "Verifier", "CredentialsCount", "EmailClaimsCount", "EncryptionType", "EncryptionSettings", "Client", "CreatedAt", "UpdatedAt"
|
||||
FROM (
|
||||
SELECT v.*, ROW_NUMBER() OVER (PARTITION BY "ManifestId" ORDER BY "RevisionNumber" DESC, "CreatedAt" DESC, "RevisionId" DESC) AS rn
|
||||
FROM "VaultManifests" v
|
||||
) ranked
|
||||
WHERE ranked.rn > 1
|
||||
ON CONFLICT ("ManifestId", "RevisionNumber") DO NOTHING;
|
||||
""");
|
||||
|
||||
migrationBuilder.Sql("""
|
||||
DELETE FROM "VaultManifests" v
|
||||
USING (
|
||||
SELECT "RevisionId", ROW_NUMBER() OVER (PARTITION BY "ManifestId" ORDER BY "RevisionNumber" DESC, "CreatedAt" DESC, "RevisionId" DESC) AS rn
|
||||
FROM "VaultManifests"
|
||||
) ranked
|
||||
WHERE v."RevisionId" = ranked."RevisionId" AND ranked.rn > 1;
|
||||
""");
|
||||
|
||||
// 5) VaultManifests now holds exactly one row per manifest: re-key it on ManifestId.
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_VaultManifests",
|
||||
table: "VaultManifests");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_VaultManifests_ManifestId_RevisionNumber",
|
||||
table: "VaultManifests");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_VaultManifests_OwnerUserId",
|
||||
table: "VaultManifests");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RevisionId",
|
||||
table: "VaultManifests");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Category",
|
||||
table: "VaultManifests");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_VaultManifests",
|
||||
table: "VaultManifests",
|
||||
column: "ManifestId");
|
||||
|
||||
// Every user has exactly one root manifest.
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_VaultManifests_OwnerUserId_Root",
|
||||
table: "VaultManifests",
|
||||
column: "OwnerUserId",
|
||||
unique: true,
|
||||
filter: "\"IsRoot\"");
|
||||
|
||||
// 6) Remaining keys, indexes and foreign keys.
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_VaultBlobReferences",
|
||||
table: "VaultBlobReferences",
|
||||
columns: new[] { "ManifestId", "RevisionNumber", "BlobHash" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VaultManifestsHistory_OwnerUserId",
|
||||
table: "VaultManifestsHistory",
|
||||
column: "OwnerUserId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_VaultManifestsHistory_VaultManifests_ManifestId",
|
||||
table: "VaultManifestsHistory",
|
||||
column: "ManifestId",
|
||||
principalTable: "VaultManifests",
|
||||
principalColumn: "ManifestId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_VaultBlobReferences_VaultManifests_ManifestId",
|
||||
table: "VaultBlobReferences",
|
||||
column: "ManifestId",
|
||||
principalTable: "VaultManifests",
|
||||
principalColumn: "ManifestId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_VaultBlobReferences_VaultManifests_ManifestId",
|
||||
table: "VaultBlobReferences");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_VaultManifestsHistory_VaultManifests_ManifestId",
|
||||
table: "VaultManifestsHistory");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_VaultManifests",
|
||||
table: "VaultManifests");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "UX_VaultManifests_OwnerUserId_Root",
|
||||
table: "VaultManifests");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_VaultBlobReferences",
|
||||
table: "VaultBlobReferences");
|
||||
|
||||
// Restore the per-revision RevisionId PK and the Category column on VaultManifests.
|
||||
migrationBuilder.Sql("""ALTER TABLE "VaultManifests" ADD COLUMN "RevisionId" uuid NOT NULL DEFAULT gen_random_uuid();""");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Category",
|
||||
table: "VaultManifests",
|
||||
type: "character varying(20)",
|
||||
maxLength: 20,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.Sql("""UPDATE "VaultManifests" SET "Category" = CASE WHEN "IsRoot" THEN 'Main' ELSE 'SharedFolder' END;""");
|
||||
|
||||
// Move history revisions back into VaultManifests as separate rows.
|
||||
migrationBuilder.Sql("""
|
||||
INSERT INTO "VaultManifests" ("RevisionId", "ManifestId", "Category", "IsRoot", "OwnerUserId", "VaultBlob", "StorageFormat", "ManifestBlob", "ManifestCiphertextHash", "Version", "RevisionNumber", "FileSize", "Salt", "Verifier", "CredentialsCount", "EmailClaimsCount", "EncryptionType", "EncryptionSettings", "Client", "CreatedAt", "UpdatedAt")
|
||||
SELECT gen_random_uuid(), h."ManifestId", m."Category", m."IsRoot", h."OwnerUserId", h."VaultBlob", h."StorageFormat", h."ManifestBlob", h."ManifestCiphertextHash", h."Version", h."RevisionNumber", h."FileSize", h."Salt", h."Verifier", h."CredentialsCount", h."EmailClaimsCount", h."EncryptionType", h."EncryptionSettings", h."Client", h."CreatedAt", h."UpdatedAt"
|
||||
FROM "VaultManifestsHistory" h
|
||||
INNER JOIN "VaultManifests" m ON m."ManifestId" = h."ManifestId";
|
||||
""");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "VaultManifestsHistory");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsRoot",
|
||||
table: "VaultManifests");
|
||||
|
||||
// Re-key blob references back to the per-revision RevisionId GUID.
|
||||
migrationBuilder.Sql("""
|
||||
UPDATE "VaultBlobReferences" r
|
||||
SET "ManifestId" = v."RevisionId"
|
||||
FROM "VaultManifests" v
|
||||
WHERE v."ManifestId" = r."ManifestId" AND v."RevisionNumber" = r."RevisionNumber";
|
||||
""");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RevisionNumber",
|
||||
table: "VaultBlobReferences");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ManifestId",
|
||||
table: "VaultBlobReferences",
|
||||
newName: "ManifestRevisionId");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_VaultManifests",
|
||||
table: "VaultManifests",
|
||||
column: "RevisionId");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_VaultBlobReferences",
|
||||
table: "VaultBlobReferences",
|
||||
columns: new[] { "ManifestRevisionId", "BlobHash" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VaultManifests_ManifestId_RevisionNumber",
|
||||
table: "VaultManifests",
|
||||
columns: new[] { "ManifestId", "RevisionNumber" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VaultManifests_OwnerUserId",
|
||||
table: "VaultManifests",
|
||||
column: "OwnerUserId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_VaultBlobReferences_VaultManifests_ManifestRevisionId",
|
||||
table: "VaultBlobReferences",
|
||||
column: "ManifestRevisionId",
|
||||
principalTable: "VaultManifests",
|
||||
principalColumn: "RevisionId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
-1420
File diff suppressed because it is too large
Load Diff
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddVaultKeys : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VaultKeys",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
UserId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
VaultManifestId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
KeyType = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
WrappedVek = table.Column<string>(type: "text", nullable: false),
|
||||
Salt = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
Verifier = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: false),
|
||||
EncryptionType = table.Column<string>(type: "text", nullable: false),
|
||||
EncryptionSettings = table.Column<string>(type: "text", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VaultKeys", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_VaultKeys_AliasVaultUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AliasVaultUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_VaultKeys_UserId_KeyType",
|
||||
table: "VaultKeys",
|
||||
columns: new[] { "UserId", "KeyType" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "VaultKeys");
|
||||
}
|
||||
}
|
||||
}
|
||||
apps/server/Databases/AliasServerDb/Migrations/20260722141816_ReshapeVaultKeysForSharing.Designer.cs
Generated
-1433
File diff suppressed because it is too large
Load Diff
-171
@@ -1,171 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ReshapeVaultKeysForSharing : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "UX_VaultKeys_UserId_KeyType",
|
||||
table: "VaultKeys");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Verifier",
|
||||
table: "VaultKeys",
|
||||
type: "character varying(1000)",
|
||||
maxLength: 1000,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(1000)",
|
||||
oldMaxLength: 1000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Salt",
|
||||
table: "VaultKeys",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(100)",
|
||||
oldMaxLength: 100);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "EncryptionType",
|
||||
table: "VaultKeys",
|
||||
type: "text",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "EncryptionSettings",
|
||||
table: "VaultKeys",
|
||||
type: "text",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastUsedAt",
|
||||
table: "VaultKeys",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Metadata",
|
||||
table: "VaultKeys",
|
||||
type: "jsonb",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "RecipientPublicKeyId",
|
||||
table: "VaultKeys",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
// No default value: WrapScheme is required and set explicitly on every insert. The VaultKeys table is
|
||||
// empty when this runs (fresh-migration baseline), so a NOT NULL column with no default is safe.
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WrapScheme",
|
||||
table: "VaultKeys",
|
||||
type: "character varying(30)",
|
||||
maxLength: 30,
|
||||
nullable: false);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VaultKeys_VaultManifestId",
|
||||
table: "VaultKeys",
|
||||
column: "VaultManifestId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_VaultKeys_UserId_KeyType_Manifest",
|
||||
table: "VaultKeys",
|
||||
columns: new[] { "UserId", "KeyType", "VaultManifestId" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_VaultKeys_VaultManifestId",
|
||||
table: "VaultKeys");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "UX_VaultKeys_UserId_KeyType_Manifest",
|
||||
table: "VaultKeys");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastUsedAt",
|
||||
table: "VaultKeys");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Metadata",
|
||||
table: "VaultKeys");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RecipientPublicKeyId",
|
||||
table: "VaultKeys");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WrapScheme",
|
||||
table: "VaultKeys");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Verifier",
|
||||
table: "VaultKeys",
|
||||
type: "character varying(1000)",
|
||||
maxLength: 1000,
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(1000)",
|
||||
oldMaxLength: 1000,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Salt",
|
||||
table: "VaultKeys",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(100)",
|
||||
oldMaxLength: 100,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "EncryptionType",
|
||||
table: "VaultKeys",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "EncryptionSettings",
|
||||
table: "VaultKeys",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_VaultKeys_UserId_KeyType",
|
||||
table: "VaultKeys",
|
||||
columns: new[] { "UserId", "KeyType" },
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddVaultManifestName : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Name",
|
||||
table: "VaultManifests",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Name",
|
||||
table: "VaultManifests");
|
||||
}
|
||||
}
|
||||
}
|
||||
+74
-16
@@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
[DbContext(typeof(AliasServerDbContext))]
|
||||
[Migration("20260722145437_AddVaultManifestName")]
|
||||
partial class AddVaultManifestName
|
||||
[Migration("20260726192459_AddManifestStorage")]
|
||||
partial class AddManifestStorage
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -746,6 +746,9 @@ namespace AliasServerDb.Migrations
|
||||
b.Property<bool>("Disabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("EncryptionKeyId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
@@ -758,6 +761,8 @@ namespace AliasServerDb.Migrations
|
||||
b.HasIndex("Address")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("EncryptionKeyId");
|
||||
|
||||
b.HasIndex("UserId", "CreatedAt");
|
||||
|
||||
b.HasIndex("UserId", "Disabled");
|
||||
@@ -790,9 +795,14 @@ namespace AliasServerDb.Migrations
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<Guid?>("VaultManifestId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
b.HasIndex("VaultManifestId");
|
||||
|
||||
b.HasIndex("UserId", "VaultManifestId", "IsPrimary");
|
||||
|
||||
b.ToTable("UserEncryptionKeys");
|
||||
});
|
||||
@@ -851,12 +861,11 @@ namespace AliasServerDb.Migrations
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.VaultDataBucket", b =>
|
||||
{
|
||||
b.Property<Guid>("RevisionId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
b.Property<string>("OwnerUserId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
@@ -871,25 +880,49 @@ namespace AliasServerDb.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("OwnerUserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<long>("RevisionNumber")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("RevisionId");
|
||||
|
||||
b.HasIndex("OwnerUserId", "Category", "RevisionNumber")
|
||||
.IsUnique();
|
||||
b.HasKey("OwnerUserId", "Category");
|
||||
|
||||
b.ToTable("VaultDataBuckets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.VaultDataBucketsHistory", b =>
|
||||
{
|
||||
b.Property<string>("OwnerUserId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<long>("RevisionNumber")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("CiphertextHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("EncryptedData")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("OwnerUserId", "Category", "RevisionNumber");
|
||||
|
||||
b.ToTable("VaultDataBucketsHistory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.VaultKey", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -1326,11 +1359,18 @@ namespace AliasServerDb.Migrations
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.UserEmailClaim", b =>
|
||||
{
|
||||
b.HasOne("AliasServerDb.UserEncryptionKey", "EncryptionKey")
|
||||
.WithMany()
|
||||
.HasForeignKey("EncryptionKeyId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("AliasServerDb.AliasVaultUser", "User")
|
||||
.WithMany("EmailClaims")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("EncryptionKey");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
@@ -1342,7 +1382,14 @@ namespace AliasServerDb.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("AliasServerDb.VaultManifest", "VaultManifest")
|
||||
.WithMany()
|
||||
.HasForeignKey("VaultManifestId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("User");
|
||||
|
||||
b.Navigation("VaultManifest");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.VaultBlobObject", b =>
|
||||
@@ -1378,6 +1425,17 @@ namespace AliasServerDb.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.VaultDataBucketsHistory", b =>
|
||||
{
|
||||
b.HasOne("AliasServerDb.VaultDataBucket", "Bucket")
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnerUserId", "Category")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Bucket");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.VaultKey", b =>
|
||||
{
|
||||
b.HasOne("AliasServerDb.AliasVaultUser", "User")
|
||||
@@ -0,0 +1,370 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the manifest storage model to the database.
|
||||
/// </summary>
|
||||
public partial class AddManifestStorage : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// --- 1. Rename "Vaults" -> "VaultManifests", including its constraints. ---
|
||||
migrationBuilder.DropIndex(name: "IX_Vaults_UserId", table: "Vaults");
|
||||
migrationBuilder.RenameTable(name: "Vaults", newName: "VaultManifests");
|
||||
migrationBuilder.RenameColumn(name: "UserId", table: "VaultManifests", newName: "OwnerUserId");
|
||||
migrationBuilder.Sql(@"ALTER TABLE ""VaultManifests"" RENAME CONSTRAINT ""PK_Vaults"" TO ""PK_VaultManifests"";");
|
||||
migrationBuilder.Sql(@"ALTER TABLE ""VaultManifests"" RENAME CONSTRAINT ""FK_Vaults_AliasVaultUsers_UserId"" TO ""FK_VaultManifests_AliasVaultUsers_OwnerUserId"";");
|
||||
|
||||
// --- 2. New manifest table columns. ---
|
||||
migrationBuilder.AddColumn<Guid>(name: "ManifestId", table: "VaultManifests", type: "uuid", nullable: true);
|
||||
migrationBuilder.AddColumn<string>(name: "Name", table: "VaultManifests", type: "character varying(255)", maxLength: 255, nullable: true);
|
||||
migrationBuilder.AddColumn<string>(name: "ManifestBlob", table: "VaultManifests", type: "text", nullable: true);
|
||||
migrationBuilder.AddColumn<string>(name: "ManifestCiphertextHash", table: "VaultManifests", type: "character varying(64)", maxLength: 64, nullable: true);
|
||||
migrationBuilder.AddColumn<string>(name: "StorageFormat", table: "VaultManifests", type: "character varying(20)", maxLength: 20, nullable: true);
|
||||
migrationBuilder.AddColumn<bool>(name: "IsRoot", table: "VaultManifests", type: "boolean", nullable: false, defaultValue: false);
|
||||
|
||||
migrationBuilder.Sql(@"UPDATE ""VaultManifests"" SET ""StorageFormat"" = 'sqlite-blob', ""IsRoot"" = TRUE;");
|
||||
migrationBuilder.Sql(@"ALTER TABLE ""VaultManifests"" ALTER COLUMN ""IsRoot"" DROP DEFAULT;");
|
||||
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE ""VaultManifests"" v SET ""ManifestId"" = sub.gid
|
||||
FROM (SELECT ""OwnerUserId"", gen_random_uuid() AS gid FROM ""VaultManifests"" GROUP BY ""OwnerUserId"") sub
|
||||
WHERE v.""OwnerUserId"" = sub.""OwnerUserId"";");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "StorageFormat",
|
||||
table: "VaultManifests",
|
||||
type: "character varying(20)",
|
||||
maxLength: 20,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(20)",
|
||||
oldMaxLength: 20,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "ManifestId",
|
||||
table: "VaultManifests",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
// --- 3. Manifest history table. ---
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VaultManifestsHistory",
|
||||
columns: table => new
|
||||
{
|
||||
RevisionNumber = table.Column<long>(type: "bigint", nullable: false),
|
||||
ManifestId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
OwnerUserId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
VaultBlob = table.Column<string>(type: "text", nullable: false),
|
||||
StorageFormat = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
ManifestBlob = table.Column<string>(type: "text", nullable: true),
|
||||
ManifestCiphertextHash = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
||||
Version = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
FileSize = table.Column<int>(type: "integer", nullable: false),
|
||||
Salt = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
Verifier = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: false),
|
||||
CredentialsCount = table.Column<int>(type: "integer", nullable: false),
|
||||
EmailClaimsCount = table.Column<int>(type: "integer", nullable: false),
|
||||
EncryptionType = table.Column<string>(type: "text", nullable: false),
|
||||
EncryptionSettings = table.Column<string>(type: "text", nullable: false),
|
||||
Client = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VaultManifestsHistory", x => new { x.ManifestId, x.RevisionNumber });
|
||||
});
|
||||
|
||||
// --- 4. Fill manifest history table based on non-current revisions of the old Vaults table. ---
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO ""VaultManifestsHistory"" (""ManifestId"", ""RevisionNumber"", ""OwnerUserId"", ""VaultBlob"", ""StorageFormat"", ""ManifestBlob"", ""ManifestCiphertextHash"", ""Version"", ""FileSize"", ""Salt"", ""Verifier"", ""CredentialsCount"", ""EmailClaimsCount"", ""EncryptionType"", ""EncryptionSettings"", ""Client"", ""CreatedAt"", ""UpdatedAt"")
|
||||
SELECT ""ManifestId"", ""RevisionNumber"", ""OwnerUserId"", ""VaultBlob"", ""StorageFormat"", ""ManifestBlob"", ""ManifestCiphertextHash"", ""Version"", ""FileSize"", ""Salt"", ""Verifier"", ""CredentialsCount"", ""EmailClaimsCount"", ""EncryptionType"", ""EncryptionSettings"", ""Client"", ""CreatedAt"", ""UpdatedAt""
|
||||
FROM (
|
||||
SELECT v.*, ROW_NUMBER() OVER (PARTITION BY ""ManifestId"" ORDER BY ""RevisionNumber"" DESC, ""CreatedAt"" DESC, ""Id"" DESC) AS rn
|
||||
FROM ""VaultManifests"" v
|
||||
) ranked
|
||||
WHERE ranked.rn > 1
|
||||
ON CONFLICT (""ManifestId"", ""RevisionNumber"") DO NOTHING;");
|
||||
|
||||
migrationBuilder.Sql(@"
|
||||
DELETE FROM ""VaultManifests"" v
|
||||
USING (
|
||||
SELECT ""Id"", ROW_NUMBER() OVER (PARTITION BY ""ManifestId"" ORDER BY ""RevisionNumber"" DESC, ""CreatedAt"" DESC, ""Id"" DESC) AS rn
|
||||
FROM ""VaultManifests""
|
||||
) ranked
|
||||
WHERE v.""Id"" = ranked.""Id"" AND ranked.rn > 1;");
|
||||
|
||||
// --- 5. Swap the per-revision key for the manifest key. ---
|
||||
migrationBuilder.DropPrimaryKey(name: "PK_VaultManifests", table: "VaultManifests");
|
||||
migrationBuilder.DropColumn(name: "Id", table: "VaultManifests");
|
||||
migrationBuilder.AddPrimaryKey(name: "PK_VaultManifests", table: "VaultManifests", column: "ManifestId");
|
||||
|
||||
// Every user has exactly one root manifest.
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_VaultManifests_OwnerUserId_Root",
|
||||
table: "VaultManifests",
|
||||
column: "OwnerUserId",
|
||||
unique: true,
|
||||
filter: "\"IsRoot\"");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VaultManifestsHistory_OwnerUserId",
|
||||
table: "VaultManifestsHistory",
|
||||
column: "OwnerUserId");
|
||||
|
||||
// Now that the referenced key exists, tie history rows to their manifest.
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_VaultManifestsHistory_VaultManifests_ManifestId",
|
||||
table: "VaultManifestsHistory",
|
||||
column: "ManifestId",
|
||||
principalTable: "VaultManifests",
|
||||
principalColumn: "ManifestId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
// --- 6. New tables. ---
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VaultBlobObjects",
|
||||
columns: table => new
|
||||
{
|
||||
Hash = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
OwnerUserId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Category = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
EncryptedData = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||
SizeBytes = table.Column<int>(type: "integer", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
LastReferencedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VaultBlobObjects", x => new { x.Hash, x.OwnerUserId });
|
||||
table.ForeignKey(
|
||||
name: "FK_VaultBlobObjects_AliasVaultUsers_OwnerUserId",
|
||||
column: x => x.OwnerUserId,
|
||||
principalTable: "AliasVaultUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VaultDataBuckets",
|
||||
columns: table => new
|
||||
{
|
||||
OwnerUserId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Category = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
EncryptedData = table.Column<string>(type: "text", nullable: false),
|
||||
RevisionNumber = table.Column<long>(type: "bigint", nullable: false),
|
||||
CiphertextHash = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VaultDataBuckets", x => new { x.OwnerUserId, x.Category });
|
||||
table.ForeignKey(
|
||||
name: "FK_VaultDataBuckets_AliasVaultUsers_OwnerUserId",
|
||||
column: x => x.OwnerUserId,
|
||||
principalTable: "AliasVaultUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VaultDataBucketsHistory",
|
||||
columns: table => new
|
||||
{
|
||||
RevisionNumber = table.Column<long>(type: "bigint", nullable: false),
|
||||
OwnerUserId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Category = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
EncryptedData = table.Column<string>(type: "text", nullable: false),
|
||||
CiphertextHash = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VaultDataBucketsHistory", x => new { x.OwnerUserId, x.Category, x.RevisionNumber });
|
||||
table.ForeignKey(
|
||||
name: "FK_VaultDataBucketsHistory_VaultDataBuckets_OwnerUserId_Catego~",
|
||||
columns: x => new { x.OwnerUserId, x.Category },
|
||||
principalTable: "VaultDataBuckets",
|
||||
principalColumns: new[] { "OwnerUserId", "Category" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VaultKeys",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
UserId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
VaultManifestId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
KeyType = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
WrapScheme = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
WrappedVek = table.Column<string>(type: "text", nullable: false),
|
||||
Salt = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
Verifier = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||
EncryptionType = table.Column<string>(type: "text", nullable: true),
|
||||
EncryptionSettings = table.Column<string>(type: "text", nullable: true),
|
||||
RecipientPublicKeyId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
Metadata = table.Column<string>(type: "jsonb", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
LastUsedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VaultKeys", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_VaultKeys_AliasVaultUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AliasVaultUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VaultBlobReferences",
|
||||
columns: table => new
|
||||
{
|
||||
ManifestId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
RevisionNumber = table.Column<long>(type: "bigint", nullable: false),
|
||||
BlobHash = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VaultBlobReferences", x => new { x.ManifestId, x.RevisionNumber, x.BlobHash });
|
||||
table.ForeignKey(
|
||||
name: "FK_VaultBlobReferences_VaultManifests_ManifestId",
|
||||
column: x => x.ManifestId,
|
||||
principalTable: "VaultManifests",
|
||||
principalColumn: "ManifestId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VaultBlobObjects_OwnerUserId_Category",
|
||||
table: "VaultBlobObjects",
|
||||
columns: new[] { "OwnerUserId", "Category" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VaultKeys_VaultManifestId",
|
||||
table: "VaultKeys",
|
||||
column: "VaultManifestId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_VaultKeys_UserId_KeyType_Manifest",
|
||||
table: "VaultKeys",
|
||||
columns: new[] { "UserId", "KeyType", "VaultManifestId" },
|
||||
unique: true);
|
||||
|
||||
// --- 7. Email encryption keys become per-manifest, and claims record which key they were issued under. ---
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_UserEncryptionKeys_UserId",
|
||||
table: "UserEncryptionKeys");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "VaultManifestId",
|
||||
table: "UserEncryptionKeys",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "EncryptionKeyId",
|
||||
table: "UserEmailClaims",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserEncryptionKeys_UserId_VaultManifestId_IsPrimary",
|
||||
table: "UserEncryptionKeys",
|
||||
columns: new[] { "UserId", "VaultManifestId", "IsPrimary" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserEncryptionKeys_VaultManifestId",
|
||||
table: "UserEncryptionKeys",
|
||||
column: "VaultManifestId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserEmailClaims_EncryptionKeyId",
|
||||
table: "UserEmailClaims",
|
||||
column: "EncryptionKeyId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_UserEmailClaims_UserEncryptionKeys_EncryptionKeyId",
|
||||
table: "UserEmailClaims",
|
||||
column: "EncryptionKeyId",
|
||||
principalTable: "UserEncryptionKeys",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_UserEncryptionKeys_VaultManifests_VaultManifestId",
|
||||
table: "UserEncryptionKeys",
|
||||
column: "VaultManifestId",
|
||||
principalTable: "VaultManifests",
|
||||
principalColumn: "ManifestId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Undo the email encryption key changes.
|
||||
migrationBuilder.DropForeignKey(name: "FK_UserEmailClaims_UserEncryptionKeys_EncryptionKeyId", table: "UserEmailClaims");
|
||||
migrationBuilder.DropForeignKey(name: "FK_UserEncryptionKeys_VaultManifests_VaultManifestId", table: "UserEncryptionKeys");
|
||||
migrationBuilder.DropIndex(name: "IX_UserEncryptionKeys_UserId_VaultManifestId_IsPrimary", table: "UserEncryptionKeys");
|
||||
migrationBuilder.DropIndex(name: "IX_UserEncryptionKeys_VaultManifestId", table: "UserEncryptionKeys");
|
||||
migrationBuilder.DropIndex(name: "IX_UserEmailClaims_EncryptionKeyId", table: "UserEmailClaims");
|
||||
migrationBuilder.DropColumn(name: "VaultManifestId", table: "UserEncryptionKeys");
|
||||
migrationBuilder.DropColumn(name: "EncryptionKeyId", table: "UserEmailClaims");
|
||||
migrationBuilder.CreateIndex(name: "IX_UserEncryptionKeys_UserId", table: "UserEncryptionKeys", column: "UserId");
|
||||
|
||||
// Drop the tables that had no pre-V2 equivalent (dependents first).
|
||||
migrationBuilder.DropTable(name: "VaultBlobReferences");
|
||||
migrationBuilder.DropTable(name: "VaultBlobObjects");
|
||||
migrationBuilder.DropTable(name: "VaultDataBucketsHistory");
|
||||
migrationBuilder.DropTable(name: "VaultDataBuckets");
|
||||
migrationBuilder.DropTable(name: "VaultKeys");
|
||||
|
||||
// Re-materialize the revision log: restore the per-revision key, then fold history rows back in.
|
||||
migrationBuilder.DropForeignKey(name: "FK_VaultManifestsHistory_VaultManifests_ManifestId", table: "VaultManifestsHistory");
|
||||
migrationBuilder.DropIndex(name: "UX_VaultManifests_OwnerUserId_Root", table: "VaultManifests");
|
||||
migrationBuilder.Sql(@"ALTER TABLE ""VaultManifests"" ADD COLUMN ""Id"" uuid NOT NULL DEFAULT gen_random_uuid();");
|
||||
migrationBuilder.DropPrimaryKey(name: "PK_VaultManifests", table: "VaultManifests");
|
||||
migrationBuilder.AddPrimaryKey(name: "PK_VaultManifests", table: "VaultManifests", column: "Id");
|
||||
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO ""VaultManifests"" (""Id"", ""ManifestId"", ""IsRoot"", ""Name"", ""OwnerUserId"", ""VaultBlob"", ""StorageFormat"", ""ManifestBlob"", ""ManifestCiphertextHash"", ""Version"", ""RevisionNumber"", ""FileSize"", ""Salt"", ""Verifier"", ""CredentialsCount"", ""EmailClaimsCount"", ""EncryptionType"", ""EncryptionSettings"", ""Client"", ""CreatedAt"", ""UpdatedAt"")
|
||||
SELECT gen_random_uuid(), h.""ManifestId"", m.""IsRoot"", m.""Name"", h.""OwnerUserId"", h.""VaultBlob"", h.""StorageFormat"", h.""ManifestBlob"", h.""ManifestCiphertextHash"", h.""Version"", h.""RevisionNumber"", h.""FileSize"", h.""Salt"", h.""Verifier"", h.""CredentialsCount"", h.""EmailClaimsCount"", h.""EncryptionType"", h.""EncryptionSettings"", h.""Client"", h.""CreatedAt"", h.""UpdatedAt""
|
||||
FROM ""VaultManifestsHistory"" h
|
||||
INNER JOIN ""VaultManifests"" m ON m.""ManifestId"" = h.""ManifestId"";");
|
||||
|
||||
migrationBuilder.DropTable(name: "VaultManifestsHistory");
|
||||
migrationBuilder.Sql(@"ALTER TABLE ""VaultManifests"" ALTER COLUMN ""Id"" DROP DEFAULT;");
|
||||
|
||||
migrationBuilder.DropColumn(name: "ManifestId", table: "VaultManifests");
|
||||
migrationBuilder.DropColumn(name: "IsRoot", table: "VaultManifests");
|
||||
migrationBuilder.DropColumn(name: "Name", table: "VaultManifests");
|
||||
migrationBuilder.DropColumn(name: "StorageFormat", table: "VaultManifests");
|
||||
migrationBuilder.DropColumn(name: "ManifestBlob", table: "VaultManifests");
|
||||
migrationBuilder.DropColumn(name: "ManifestCiphertextHash", table: "VaultManifests");
|
||||
|
||||
// Rename "VaultManifests" back to "Vaults" in place.
|
||||
migrationBuilder.Sql(@"ALTER TABLE ""VaultManifests"" RENAME CONSTRAINT ""FK_VaultManifests_AliasVaultUsers_OwnerUserId"" TO ""FK_Vaults_AliasVaultUsers_UserId"";");
|
||||
migrationBuilder.Sql(@"ALTER TABLE ""VaultManifests"" RENAME CONSTRAINT ""PK_VaultManifests"" TO ""PK_Vaults"";");
|
||||
migrationBuilder.RenameColumn(name: "OwnerUserId", table: "VaultManifests", newName: "UserId");
|
||||
migrationBuilder.RenameTable(name: "VaultManifests", newName: "Vaults");
|
||||
migrationBuilder.CreateIndex(name: "IX_Vaults_UserId", table: "Vaults", column: "UserId");
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
-14
@@ -743,6 +743,9 @@ namespace AliasServerDb.Migrations
|
||||
b.Property<bool>("Disabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("EncryptionKeyId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
@@ -755,6 +758,8 @@ namespace AliasServerDb.Migrations
|
||||
b.HasIndex("Address")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("EncryptionKeyId");
|
||||
|
||||
b.HasIndex("UserId", "CreatedAt");
|
||||
|
||||
b.HasIndex("UserId", "Disabled");
|
||||
@@ -787,9 +792,14 @@ namespace AliasServerDb.Migrations
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<Guid?>("VaultManifestId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
b.HasIndex("VaultManifestId");
|
||||
|
||||
b.HasIndex("UserId", "VaultManifestId", "IsPrimary");
|
||||
|
||||
b.ToTable("UserEncryptionKeys");
|
||||
});
|
||||
@@ -848,12 +858,11 @@ namespace AliasServerDb.Migrations
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.VaultDataBucket", b =>
|
||||
{
|
||||
b.Property<Guid>("RevisionId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
b.Property<string>("OwnerUserId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
@@ -868,25 +877,49 @@ namespace AliasServerDb.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("OwnerUserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<long>("RevisionNumber")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("RevisionId");
|
||||
|
||||
b.HasIndex("OwnerUserId", "Category", "RevisionNumber")
|
||||
.IsUnique();
|
||||
b.HasKey("OwnerUserId", "Category");
|
||||
|
||||
b.ToTable("VaultDataBuckets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.VaultDataBucketsHistory", b =>
|
||||
{
|
||||
b.Property<string>("OwnerUserId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<long>("RevisionNumber")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("CiphertextHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("EncryptedData")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("OwnerUserId", "Category", "RevisionNumber");
|
||||
|
||||
b.ToTable("VaultDataBucketsHistory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.VaultKey", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -1323,11 +1356,18 @@ namespace AliasServerDb.Migrations
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.UserEmailClaim", b =>
|
||||
{
|
||||
b.HasOne("AliasServerDb.UserEncryptionKey", "EncryptionKey")
|
||||
.WithMany()
|
||||
.HasForeignKey("EncryptionKeyId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("AliasServerDb.AliasVaultUser", "User")
|
||||
.WithMany("EmailClaims")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("EncryptionKey");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
@@ -1339,7 +1379,14 @@ namespace AliasServerDb.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("AliasServerDb.VaultManifest", "VaultManifest")
|
||||
.WithMany()
|
||||
.HasForeignKey("VaultManifestId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("User");
|
||||
|
||||
b.Navigation("VaultManifest");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.VaultBlobObject", b =>
|
||||
@@ -1375,6 +1422,17 @@ namespace AliasServerDb.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.VaultDataBucketsHistory", b =>
|
||||
{
|
||||
b.HasOne("AliasServerDb.VaultDataBucket", "Bucket")
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnerUserId", "Category")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Bucket");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.VaultKey", b =>
|
||||
{
|
||||
b.HasOne("AliasServerDb.AliasVaultUser", "User")
|
||||
|
||||
@@ -39,6 +39,19 @@ public class UserEmailClaim
|
||||
[ForeignKey("UserId")]
|
||||
public virtual AliasVaultUser? User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the encryption key incoming mail for this alias is encrypted with. For most cases this will be null,
|
||||
/// which means the routing owner's primary personal key is used, resolved at delivery time. For aliases that are shared
|
||||
/// with other users, this will be set to the encryption key of the shared folder so all members of the folder can decrypt the mail.
|
||||
/// </summary>
|
||||
public Guid? EncryptionKeyId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the navigation property to the encryption key mail for this alias is encrypted with.
|
||||
/// </summary>
|
||||
[ForeignKey("EncryptionKeyId")]
|
||||
public virtual UserEncryptionKey? EncryptionKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the full email address.
|
||||
/// </summary>
|
||||
|
||||
@@ -11,6 +11,10 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
/// <summary>
|
||||
/// UserEncryptionKey object. This object is used for storing user public keys for encryption.
|
||||
/// <para>
|
||||
/// A row can be either personal (VaultManifestId is null) or folder-scoped (VaultManifestId is set).
|
||||
/// Personal private keys live in the user's EncryptionKeys data bucket, folder-scoped private keys live in the shared folder's manifest.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class UserEncryptionKey
|
||||
{
|
||||
@@ -32,6 +36,17 @@ public class UserEncryptionKey
|
||||
[ForeignKey("UserId")]
|
||||
public virtual AliasVaultUser User { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the shared-folder manifest this key belongs to, or null when it is the user's own personal key.
|
||||
/// </summary>
|
||||
public Guid? VaultManifestId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the navigation property to the shared-folder manifest, when this key is folder-scoped.
|
||||
/// </summary>
|
||||
[ForeignKey("VaultManifestId")]
|
||||
public virtual VaultManifest? VaultManifest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the public key.
|
||||
/// </summary>
|
||||
@@ -40,6 +55,7 @@ public class UserEncryptionKey
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this public key is the primary key to use by default.
|
||||
/// Primary is scoped to VaultManifestId: a user has one primary personal key plus one primary key per shared folder they participate in.
|
||||
/// </summary>
|
||||
public bool IsPrimary { get; set; }
|
||||
|
||||
|
||||
@@ -6,27 +6,18 @@
|
||||
//-----------------------------------------------------------------------
|
||||
namespace AliasServerDb;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using AliasVault.Shared.Models.WebApi.V2.Vault;
|
||||
|
||||
/// <summary>
|
||||
/// A small, independently-syncable user-scoped data bucket. Each bucket holds one kind of data
|
||||
/// that we deliberately keep OUT of the main vault content manifest so it can sync separately and faster.
|
||||
/// The current revision of a small, independently-syncable user-scoped data bucket. Each bucket holds one kind of
|
||||
/// data that we deliberately keep out of the main vault content manifest so it can sync separately and faster.
|
||||
/// </summary>
|
||||
public class VaultDataBucket
|
||||
public class VaultDataBucket : VaultDataBucketBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the per-revision primary key. Each row is one revision of the (OwnerUserId, Category) bucket;
|
||||
/// the highest <see cref="RevisionNumber"/> for a given (OwnerUserId, Category) is the current one.
|
||||
/// Gets or sets the user ID foreign key. Part of the composite primary key (OwnerUserId, Category).
|
||||
/// </summary>
|
||||
[Key]
|
||||
public Guid RevisionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user ID foreign key.
|
||||
/// </summary>
|
||||
[StringLength(255)]
|
||||
public string OwnerUserId { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
@@ -36,33 +27,7 @@ public class VaultDataBucket
|
||||
public virtual AliasVaultUser User { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the bucket category/kind (e.g. Settings).
|
||||
/// Gets or sets the bucket category/kind (e.g. Settings). Part of the composite primary key.
|
||||
/// </summary>
|
||||
public required VaultDataBucketCategory Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the encrypted bucket payload (AES-GCM ciphertext, base64-encoded).
|
||||
/// </summary>
|
||||
public required string EncryptedData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the revision number of this bucket.
|
||||
/// </summary>
|
||||
public required long RevisionNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SHA-256 (hex) of the encrypted ciphertext for storage-layer integrity check.
|
||||
/// </summary>
|
||||
[StringLength(64)]
|
||||
public string? CiphertextHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the created timestamp.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the updated timestamp.
|
||||
/// </summary>
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="VaultDataBucketBase.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
namespace AliasServerDb;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
/// <summary>
|
||||
/// Shared revision payload columns for a vault data bucket.
|
||||
/// <see cref="VaultDataBucket"/> holds the current revision of each (owner, category) bucket.
|
||||
/// <see cref="VaultDataBucketsHistory"/> holds superseded revisions.
|
||||
/// </summary>
|
||||
public abstract class VaultDataBucketBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the encrypted bucket payload (AES-GCM ciphertext, base64-encoded).
|
||||
/// </summary>
|
||||
public required string EncryptedData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the revision number of this bucket. Incremented on every write.
|
||||
/// </summary>
|
||||
public required long RevisionNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SHA-256 (hex) of the encrypted ciphertext for storage-layer integrity check.
|
||||
/// </summary>
|
||||
[StringLength(64)]
|
||||
public string? CiphertextHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the created timestamp.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the updated timestamp.
|
||||
/// </summary>
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Copies all shared revision payload columns from another bucket revision onto this instance.
|
||||
/// </summary>
|
||||
/// <param name="source">The revision to copy the payload from.</param>
|
||||
public void CopyPayloadFrom(VaultDataBucketBase source)
|
||||
{
|
||||
EncryptedData = source.EncryptedData;
|
||||
RevisionNumber = source.RevisionNumber;
|
||||
CiphertextHash = source.CiphertextHash;
|
||||
CreatedAt = source.CreatedAt;
|
||||
UpdatedAt = source.UpdatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="VaultDataBucketsHistory.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
namespace AliasServerDb;
|
||||
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using AliasVault.Shared.Models.WebApi.V2.Vault;
|
||||
|
||||
/// <summary>
|
||||
/// A superseded revision of a vault data bucket, kept for backup/rollback per the bucket retention policy.
|
||||
/// On every write the current <see cref="VaultDataBucket"/> row is first copied into this table, after which the current row is updated in place.
|
||||
/// Composite primary key (OwnerUserId, Category, RevisionNumber).
|
||||
/// </summary>
|
||||
public class VaultDataBucketsHistory : VaultDataBucketBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of the owning user. Part of the composite PK.
|
||||
/// </summary>
|
||||
public string OwnerUserId { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the navigation property to the current bucket row this revision belongs to.
|
||||
/// </summary>
|
||||
[ForeignKey("OwnerUserId, Category")]
|
||||
public virtual VaultDataBucket Bucket { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the bucket category/kind. Part of the composite PK.
|
||||
/// </summary>
|
||||
public required VaultDataBucketCategory Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a history row from the current revision of a bucket. Called right before the current row is updated
|
||||
/// in place with a newer revision.
|
||||
/// </summary>
|
||||
/// <param name="current">The current bucket row to archive.</param>
|
||||
/// <returns>A new unsaved history entity carrying the current row's full revision payload.</returns>
|
||||
public static VaultDataBucketsHistory CreateFrom(VaultDataBucket current)
|
||||
{
|
||||
var history = new VaultDataBucketsHistory
|
||||
{
|
||||
OwnerUserId = current.OwnerUserId,
|
||||
Category = current.Category,
|
||||
EncryptedData = current.EncryptedData,
|
||||
RevisionNumber = current.RevisionNumber,
|
||||
};
|
||||
history.CopyPayloadFrom(current);
|
||||
return history;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user