Public Access
The private key now lives in the vault as ciphertext, syncs between a user's machines, and is stored on the server so it can later be shared — sharing itself needs M3's signed grants; this is the storage that makes it possible. More was already reserved than expected: SyncEntityType.SshKey, CryptoSpec.AadResourceType.SshKey, ChangeEntityType.SshKey, SyncPlaintextFields.PublicKeyFingerprint, and SshPrivateKeyCredential wired through PrivateKeyFile over a MemoryStream so a key never touches disk. The frozen contract and crypto spec needed no change at all. What was missing was the server. Rather than copy the push path per item type — version check, change-log append, exactly-once receipt, advisory lock — it is now written once over IVaultItem, with everything type-specific behind IItemKind: which table, which plaintext columns, and what those columns must satisfy. Ten copies of that logic by M5, with a fix applied to nine, is the outcome this avoids. The refactor landed first with no behaviour change, so all 66 existing Host tests were the regression net, and they stayed green. An interface rather than a base class, deliberately: EF Core maps an inheritance hierarchy when it can see one, so a mapped base would quietly become a table-per-hierarchy discriminator across item types — the very arrangement per-type tables exist to avoid. ssh_key mirrors host and pointedly has no relay trio. That is the argument for separate tables rather than one wide item table: the columns a host needs are columns a key must never have, and a shared table could only make them nullable and trust the code. A key carrying a relay target is refused with a reason rather than silently dropped. A key hydrates PlaintextFields as null, not an empty instance — the difference is visible on the wire, because an all-defaults instance still serialises "relayEnabled": false and invites a reader to believe the setting exists and is off. It has none. Two things now defended by tests rather than by comments. Each kind states its own ChangeEntityType instead of casting: the two enums agree numerically but do not even share member names (Host against SshHost), and filing key changes under the host type is silent sync corruption — sabotaging it fails three tests. And EntityTypeAlignmentTests asserts the two enums stay aligned in both directions and in count, which nothing did before. The client half is next: SshKeySecret, its codec and merge, the cipher, a repository, and the UI. Note for that work — SyncEntityType.SshKey is 3 while AadResourceType.SshKey is 6, so a cast between them would seal key ciphertext as a vault and nothing would fail.
1070 lines
42 KiB
C#
1070 lines
42 KiB
C#
// <auto-generated />
|
|
using System;
|
|
using DodoSSH.Infrastructure;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
using Microsoft.EntityFrameworkCore.Migrations;
|
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
|
|
#nullable disable
|
|
|
|
namespace DodoSSH.Infrastructure.Migrations
|
|
{
|
|
[DbContext(typeof(DodoDbContext))]
|
|
[Migration("20260729130834_AddSshKeyItem")]
|
|
partial class AddSshKeyItem
|
|
{
|
|
/// <inheritdoc />
|
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
{
|
|
#pragma warning disable 612, 618
|
|
modelBuilder
|
|
.HasDefaultSchema("dodo")
|
|
.HasAnnotation("ProductVersion", "10.0.10")
|
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
|
|
|
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "citext");
|
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.Device", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("id");
|
|
|
|
b.Property<DateTimeOffset>("EnrolledAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("enrolled_at_utc");
|
|
|
|
b.Property<DateTimeOffset?>("LastSeenAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("last_seen_at_utc");
|
|
|
|
b.Property<string>("Name")
|
|
.IsRequired()
|
|
.HasMaxLength(256)
|
|
.HasColumnType("character varying(256)")
|
|
.HasColumnName("name");
|
|
|
|
b.Property<int>("Platform")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("platform");
|
|
|
|
b.Property<byte[]>("PublicKey")
|
|
.IsRequired()
|
|
.HasMaxLength(32)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("public_key");
|
|
|
|
b.Property<DateTimeOffset?>("RevokedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("revoked_at_utc");
|
|
|
|
b.Property<Guid>("UserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("user_id");
|
|
|
|
b.HasKey("Id")
|
|
.HasName("pk_device");
|
|
|
|
b.HasIndex("UserId")
|
|
.HasDatabaseName("ix_device_user_id");
|
|
|
|
b.ToTable("device", "dodo");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.KeyLogEntry", b =>
|
|
{
|
|
b.Property<long>("Sequence")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("bigint")
|
|
.HasColumnName("sequence");
|
|
|
|
NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property<long>("Sequence"));
|
|
|
|
b.Property<DateTimeOffset>("CreatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("created_at_utc");
|
|
|
|
b.Property<byte[]>("EncryptionPublicKey")
|
|
.IsRequired()
|
|
.HasMaxLength(32)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("encryption_public_key");
|
|
|
|
b.Property<int>("Generation")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("generation");
|
|
|
|
b.Property<byte[]>("Hash")
|
|
.IsRequired()
|
|
.HasMaxLength(32)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("hash");
|
|
|
|
b.Property<byte[]>("PreviousHash")
|
|
.IsRequired()
|
|
.HasMaxLength(32)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("previous_hash");
|
|
|
|
b.Property<byte[]>("SigningPublicKey")
|
|
.IsRequired()
|
|
.HasMaxLength(32)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("signing_public_key");
|
|
|
|
b.Property<byte[]>("StatementSignature")
|
|
.IsRequired()
|
|
.HasMaxLength(64)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("statement_signature");
|
|
|
|
b.Property<Guid>("UserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("user_id");
|
|
|
|
b.HasKey("Sequence")
|
|
.HasName("pk_key_log");
|
|
|
|
b.HasIndex("Hash")
|
|
.IsUnique()
|
|
.HasDatabaseName("ix_key_log_hash");
|
|
|
|
b.HasIndex("UserId")
|
|
.HasDatabaseName("ix_key_log_user_id");
|
|
|
|
b.ToTable("key_log", "dodo");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.SshHost", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("id");
|
|
|
|
b.Property<long>("ChangeSequence")
|
|
.HasColumnType("bigint")
|
|
.HasColumnName("change_sequence");
|
|
|
|
b.Property<Guid?>("ContentKeyId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("content_key_id");
|
|
|
|
b.Property<DateTimeOffset>("CreatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("created_at_utc");
|
|
|
|
b.Property<Guid>("CreatedByUserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("created_by_user_id");
|
|
|
|
b.Property<byte[]>("DataKeyWrap")
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("data_key_wrap");
|
|
|
|
b.Property<DateTimeOffset?>("DeletedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("deleted_at_utc");
|
|
|
|
b.Property<Guid?>("GroupId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("group_id");
|
|
|
|
b.Property<string>("Hostname")
|
|
.HasMaxLength(255)
|
|
.HasColumnType("character varying(255)")
|
|
.HasColumnName("hostname");
|
|
|
|
b.Property<int>("KeyGeneration")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("key_generation");
|
|
|
|
b.Property<byte[]>("Payload")
|
|
.IsRequired()
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("payload");
|
|
|
|
b.Property<short>("PayloadAadVersion")
|
|
.HasColumnType("smallint")
|
|
.HasColumnName("payload_aad_version");
|
|
|
|
b.Property<int?>("Port")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("port");
|
|
|
|
b.Property<bool>("RelayEnabled")
|
|
.HasColumnType("boolean")
|
|
.HasColumnName("relay_enabled");
|
|
|
|
b.Property<DateTimeOffset>("UpdatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("updated_at_utc");
|
|
|
|
b.Property<Guid>("UpdatedByUserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("updated_by_user_id");
|
|
|
|
b.Property<Guid>("VaultId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("vault_id");
|
|
|
|
b.Property<int>("Version")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("version");
|
|
|
|
b.Property<uint>("xmin")
|
|
.IsConcurrencyToken()
|
|
.ValueGeneratedOnAddOrUpdate()
|
|
.HasColumnType("xid")
|
|
.HasColumnName("xmin");
|
|
|
|
b.HasKey("Id")
|
|
.HasName("pk_host");
|
|
|
|
b.HasIndex("VaultId")
|
|
.HasDatabaseName("ix_host_vault_live")
|
|
.HasFilter("deleted_at_utc IS NULL");
|
|
|
|
b.HasIndex("VaultId", "ChangeSequence")
|
|
.HasDatabaseName("ix_host_vault_id_change_sequence");
|
|
|
|
b.ToTable("host", "dodo", t =>
|
|
{
|
|
t.HasCheckConstraint("ck_host_port_range", "port IS NULL OR (port BETWEEN 1 AND 65535)");
|
|
|
|
t.HasCheckConstraint("ck_host_relay_target", "(relay_enabled AND hostname IS NOT NULL AND port IS NOT NULL)\nOR (NOT relay_enabled AND hostname IS NULL AND port IS NULL)");
|
|
|
|
t.HasCheckConstraint("ck_host_version", "version >= 1");
|
|
});
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.SyncOperationReceipt", b =>
|
|
{
|
|
b.Property<Guid>("OperationId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("operation_id");
|
|
|
|
b.Property<long?>("AppliedChangeSequence")
|
|
.HasColumnType("bigint")
|
|
.HasColumnName("applied_change_sequence");
|
|
|
|
b.Property<DateTimeOffset>("CreatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("created_at_utc");
|
|
|
|
b.Property<int?>("ResultVersion")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("result_version");
|
|
|
|
b.Property<Guid>("VaultId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("vault_id");
|
|
|
|
b.HasKey("OperationId")
|
|
.HasName("pk_sync_operation_receipt");
|
|
|
|
b.HasIndex("VaultId", "CreatedAtUtc")
|
|
.HasDatabaseName("ix_sync_operation_receipt_vault_id_created_at_utc");
|
|
|
|
b.ToTable("sync_operation_receipt", "dodo");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.Team", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("id");
|
|
|
|
b.Property<DateTimeOffset>("CreatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("created_at_utc");
|
|
|
|
b.Property<Guid>("CreatedByUserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("created_by_user_id");
|
|
|
|
b.Property<DateTimeOffset?>("DeletedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("deleted_at_utc");
|
|
|
|
b.Property<string>("Description")
|
|
.HasMaxLength(2048)
|
|
.HasColumnType("character varying(2048)")
|
|
.HasColumnName("description");
|
|
|
|
b.Property<string>("Name")
|
|
.IsRequired()
|
|
.HasMaxLength(256)
|
|
.HasColumnType("character varying(256)")
|
|
.HasColumnName("name");
|
|
|
|
b.Property<string>("Slug")
|
|
.IsRequired()
|
|
.HasMaxLength(128)
|
|
.HasColumnType("citext")
|
|
.HasColumnName("slug");
|
|
|
|
b.Property<uint>("xmin")
|
|
.IsConcurrencyToken()
|
|
.ValueGeneratedOnAddOrUpdate()
|
|
.HasColumnType("xid")
|
|
.HasColumnName("xmin");
|
|
|
|
b.HasKey("Id")
|
|
.HasName("pk_team");
|
|
|
|
b.HasIndex("Slug")
|
|
.IsUnique()
|
|
.HasDatabaseName("ix_team_slug")
|
|
.HasFilter("deleted_at_utc IS NULL");
|
|
|
|
b.ToTable("team", "dodo");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.TeamMembership", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("id");
|
|
|
|
b.Property<DateTimeOffset>("CreatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("created_at_utc");
|
|
|
|
b.Property<DateTimeOffset?>("DeletedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("deleted_at_utc");
|
|
|
|
b.Property<Guid?>("InvitedByUserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("invited_by_user_id");
|
|
|
|
b.Property<DateTimeOffset?>("JoinedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("joined_at_utc");
|
|
|
|
b.Property<int>("Role")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("role");
|
|
|
|
b.Property<int>("Status")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("status");
|
|
|
|
b.Property<Guid>("TeamId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("team_id");
|
|
|
|
b.Property<Guid>("UserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("user_id");
|
|
|
|
b.Property<uint>("xmin")
|
|
.IsConcurrencyToken()
|
|
.ValueGeneratedOnAddOrUpdate()
|
|
.HasColumnType("xid")
|
|
.HasColumnName("xmin");
|
|
|
|
b.HasKey("Id")
|
|
.HasName("pk_team_membership");
|
|
|
|
b.HasIndex("UserId")
|
|
.HasDatabaseName("ix_team_membership_user_id");
|
|
|
|
b.HasIndex("TeamId", "UserId")
|
|
.IsUnique()
|
|
.HasDatabaseName("ix_team_membership_team_id_user_id")
|
|
.HasFilter("deleted_at_utc IS NULL");
|
|
|
|
b.ToTable("team_membership", "dodo");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.UserAccount", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("id");
|
|
|
|
b.Property<DateTimeOffset>("CreatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("created_at_utc");
|
|
|
|
b.Property<DateTimeOffset?>("DeletedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("deleted_at_utc");
|
|
|
|
b.Property<string>("DisplayName")
|
|
.HasMaxLength(256)
|
|
.HasColumnType("character varying(256)")
|
|
.HasColumnName("display_name");
|
|
|
|
b.Property<string>("Email")
|
|
.HasMaxLength(320)
|
|
.HasColumnType("citext")
|
|
.HasColumnName("email");
|
|
|
|
b.Property<DateTimeOffset?>("EnrolledAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("enrolled_at_utc");
|
|
|
|
b.Property<string>("Issuer")
|
|
.IsRequired()
|
|
.HasMaxLength(512)
|
|
.HasColumnType("character varying(512)")
|
|
.HasColumnName("issuer");
|
|
|
|
b.Property<DateTimeOffset?>("LastSeenAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("last_seen_at_utc");
|
|
|
|
b.Property<int>("Status")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("status");
|
|
|
|
b.Property<string>("Subject")
|
|
.IsRequired()
|
|
.HasMaxLength(256)
|
|
.HasColumnType("character varying(256)")
|
|
.HasColumnName("subject");
|
|
|
|
b.Property<DateTimeOffset>("UpdatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("updated_at_utc");
|
|
|
|
b.Property<uint>("xmin")
|
|
.IsConcurrencyToken()
|
|
.ValueGeneratedOnAddOrUpdate()
|
|
.HasColumnType("xid")
|
|
.HasColumnName("xmin");
|
|
|
|
b.HasKey("Id")
|
|
.HasName("pk_user_account");
|
|
|
|
b.HasIndex("Email")
|
|
.IsUnique()
|
|
.HasDatabaseName("ix_user_account_email")
|
|
.HasFilter("email IS NOT NULL AND deleted_at_utc IS NULL");
|
|
|
|
b.HasIndex("Issuer", "Subject")
|
|
.IsUnique()
|
|
.HasDatabaseName("ix_user_account_issuer_subject");
|
|
|
|
b.ToTable("user_account", "dodo");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.UserKey", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("id");
|
|
|
|
b.Property<DateTimeOffset>("CreatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("created_at_utc");
|
|
|
|
b.Property<byte[]>("EncryptionPublicKey")
|
|
.IsRequired()
|
|
.HasMaxLength(32)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("encryption_public_key");
|
|
|
|
b.Property<byte[]>("FingerprintSha256")
|
|
.IsRequired()
|
|
.HasMaxLength(32)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("fingerprint_sha256");
|
|
|
|
b.Property<int>("Generation")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("generation");
|
|
|
|
b.Property<string>("IdentityProviderBinding")
|
|
.HasColumnType("jsonb")
|
|
.HasColumnName("identity_provider_binding");
|
|
|
|
b.Property<bool>("IsCurrent")
|
|
.HasColumnType("boolean")
|
|
.HasColumnName("is_current");
|
|
|
|
b.Property<DateTimeOffset?>("RevokedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("revoked_at_utc");
|
|
|
|
b.Property<byte[]>("SigningPublicKey")
|
|
.IsRequired()
|
|
.HasMaxLength(32)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("signing_public_key");
|
|
|
|
b.Property<string>("Statement")
|
|
.IsRequired()
|
|
.HasColumnType("jsonb")
|
|
.HasColumnName("statement");
|
|
|
|
b.Property<byte[]>("StatementSignature")
|
|
.IsRequired()
|
|
.HasMaxLength(64)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("statement_signature");
|
|
|
|
b.Property<Guid>("UserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("user_id");
|
|
|
|
b.HasKey("Id")
|
|
.HasName("pk_user_key");
|
|
|
|
b.HasIndex("FingerprintSha256")
|
|
.IsUnique()
|
|
.HasDatabaseName("ix_user_key_fingerprint_sha256");
|
|
|
|
b.HasIndex("UserId")
|
|
.IsUnique()
|
|
.HasDatabaseName("ix_user_key_current")
|
|
.HasFilter("is_current");
|
|
|
|
b.HasIndex("UserId", "Generation")
|
|
.IsUnique()
|
|
.HasDatabaseName("ix_user_key_user_id_generation");
|
|
|
|
b.ToTable("user_key", "dodo");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.UserKeyWrap", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("id");
|
|
|
|
b.Property<DateTimeOffset>("CreatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("created_at_utc");
|
|
|
|
b.Property<Guid?>("DeviceId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("device_id");
|
|
|
|
b.Property<string>("KdfAlgorithm")
|
|
.HasMaxLength(64)
|
|
.HasColumnType("character varying(64)")
|
|
.HasColumnName("kdf_algorithm");
|
|
|
|
b.Property<int?>("KdfMemoryKibibytes")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("kdf_memory_kibibytes");
|
|
|
|
b.Property<int?>("KdfParallelism")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("kdf_parallelism");
|
|
|
|
b.Property<int?>("KdfPasses")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("kdf_passes");
|
|
|
|
b.Property<byte[]>("KdfSalt")
|
|
.HasMaxLength(64)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("kdf_salt");
|
|
|
|
b.Property<int>("Kind")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("kind");
|
|
|
|
b.Property<DateTimeOffset?>("LastUsedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("last_used_at_utc");
|
|
|
|
b.Property<Guid>("UserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("user_id");
|
|
|
|
b.Property<byte[]>("Wrap")
|
|
.IsRequired()
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("wrap");
|
|
|
|
b.Property<int>("WrapVersion")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("wrap_version");
|
|
|
|
b.Property<uint>("xmin")
|
|
.IsConcurrencyToken()
|
|
.ValueGeneratedOnAddOrUpdate()
|
|
.HasColumnType("xid")
|
|
.HasColumnName("xmin");
|
|
|
|
b.HasKey("Id")
|
|
.HasName("pk_user_key_wrap");
|
|
|
|
b.HasIndex("DeviceId")
|
|
.HasDatabaseName("ix_user_key_wrap_device_id");
|
|
|
|
b.HasIndex("UserId", "DeviceId")
|
|
.IsUnique()
|
|
.HasDatabaseName("ix_user_key_wrap_user_device")
|
|
.HasFilter("device_id IS NOT NULL");
|
|
|
|
b.HasIndex("UserId", "Kind")
|
|
.IsUnique()
|
|
.HasDatabaseName("ix_user_key_wrap_user_kind")
|
|
.HasFilter("device_id IS NULL");
|
|
|
|
b.ToTable("user_key_wrap", "dodo", t =>
|
|
{
|
|
t.HasCheckConstraint("ck_user_key_wrap_device", "(kind = 2 AND device_id IS NOT NULL) OR (kind <> 2 AND device_id IS NULL)");
|
|
|
|
t.HasCheckConstraint("ck_user_key_wrap_kdf", "(kind IN (1, 3) AND kdf_algorithm IS NOT NULL AND kdf_salt IS NOT NULL\n AND kdf_memory_kibibytes IS NOT NULL AND kdf_passes IS NOT NULL\n AND kdf_parallelism IS NOT NULL)\nOR (kind IN (2, 4) AND kdf_algorithm IS NULL AND kdf_salt IS NULL)");
|
|
});
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.Vault", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("id");
|
|
|
|
b.Property<DateTimeOffset>("CreatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("created_at_utc");
|
|
|
|
b.Property<DateTimeOffset?>("DeletedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("deleted_at_utc");
|
|
|
|
b.Property<int>("KeyGeneration")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("key_generation");
|
|
|
|
b.Property<string>("Name")
|
|
.IsRequired()
|
|
.HasMaxLength(256)
|
|
.HasColumnType("character varying(256)")
|
|
.HasColumnName("name");
|
|
|
|
b.Property<int>("OwnerKind")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("owner_kind");
|
|
|
|
b.Property<Guid?>("OwnerUserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("owner_user_id");
|
|
|
|
b.Property<int>("RekeyReason")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("rekey_reason");
|
|
|
|
b.Property<bool>("RekeyRequired")
|
|
.HasColumnType("boolean")
|
|
.HasColumnName("rekey_required");
|
|
|
|
b.Property<Guid?>("TeamId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("team_id");
|
|
|
|
b.Property<DateTimeOffset>("UpdatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("updated_at_utc");
|
|
|
|
b.Property<uint>("xmin")
|
|
.IsConcurrencyToken()
|
|
.ValueGeneratedOnAddOrUpdate()
|
|
.HasColumnType("xid")
|
|
.HasColumnName("xmin");
|
|
|
|
b.HasKey("Id")
|
|
.HasName("pk_vault");
|
|
|
|
b.HasIndex("OwnerUserId")
|
|
.HasDatabaseName("ix_vault_owner_user_id");
|
|
|
|
b.HasIndex("TeamId")
|
|
.HasDatabaseName("ix_vault_team_id");
|
|
|
|
b.ToTable("vault", "dodo", t =>
|
|
{
|
|
t.HasCheckConstraint("ck_vault_key_generation", "key_generation >= 1");
|
|
|
|
t.HasCheckConstraint("ck_vault_owner", "(owner_kind = 1 AND owner_user_id IS NOT NULL AND team_id IS NULL)\nOR (owner_kind = 2 AND team_id IS NOT NULL AND owner_user_id IS NULL)");
|
|
});
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.VaultChange", b =>
|
|
{
|
|
b.Property<long>("Sequence")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("bigint")
|
|
.HasColumnName("sequence");
|
|
|
|
NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property<long>("Sequence"));
|
|
|
|
b.Property<Guid>("ActorUserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("actor_user_id");
|
|
|
|
b.Property<Guid>("EntityId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("entity_id");
|
|
|
|
b.Property<int>("EntityType")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("entity_type");
|
|
|
|
b.Property<DateTimeOffset>("OccurredAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("occurred_at_utc");
|
|
|
|
b.Property<int>("Operation")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("operation");
|
|
|
|
b.Property<int>("Revision")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("revision");
|
|
|
|
b.Property<Guid>("VaultId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("vault_id");
|
|
|
|
b.HasKey("Sequence")
|
|
.HasName("pk_sync_change");
|
|
|
|
b.HasIndex("VaultId", "Sequence")
|
|
.HasDatabaseName("ix_sync_change_vault_id_sequence");
|
|
|
|
b.HasIndex("VaultId", "EntityId", "Sequence")
|
|
.IsDescending(false, false, true)
|
|
.HasDatabaseName("ix_sync_change_vault_id_entity_id_sequence");
|
|
|
|
b.ToTable("sync_change", "dodo");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.VaultKeyGrant", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("id");
|
|
|
|
b.Property<DateTimeOffset>("CreatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("created_at_utc");
|
|
|
|
b.Property<byte[]>("GranterKeyFingerprint")
|
|
.IsRequired()
|
|
.HasMaxLength(32)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("granter_key_fingerprint");
|
|
|
|
b.Property<Guid>("GranterUserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("granter_user_id");
|
|
|
|
b.Property<int>("KeyGeneration")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("key_generation");
|
|
|
|
b.Property<byte[]>("KeyLogHead")
|
|
.HasMaxLength(32)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("key_log_head");
|
|
|
|
b.Property<int>("Kind")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("kind");
|
|
|
|
b.Property<byte[]>("RecipientKeyFingerprint")
|
|
.IsRequired()
|
|
.HasMaxLength(32)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("recipient_key_fingerprint");
|
|
|
|
b.Property<Guid?>("RecipientUserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("recipient_user_id");
|
|
|
|
b.Property<DateTimeOffset?>("RevokedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("revoked_at_utc");
|
|
|
|
b.Property<byte[]>("Signature")
|
|
.IsRequired()
|
|
.HasMaxLength(64)
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("signature");
|
|
|
|
b.Property<int>("State")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("state");
|
|
|
|
b.Property<Guid>("VaultId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("vault_id");
|
|
|
|
b.Property<byte[]>("WrappedKey")
|
|
.IsRequired()
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("wrapped_key");
|
|
|
|
b.Property<uint>("xmin")
|
|
.IsConcurrencyToken()
|
|
.ValueGeneratedOnAddOrUpdate()
|
|
.HasColumnType("xid")
|
|
.HasColumnName("xmin");
|
|
|
|
b.HasKey("Id")
|
|
.HasName("pk_vault_key_grant");
|
|
|
|
b.HasIndex("RecipientUserId")
|
|
.HasDatabaseName("ix_vault_key_grant_recipient_user_id");
|
|
|
|
b.HasIndex("VaultId", "KeyGeneration", "RecipientUserId")
|
|
.IsUnique()
|
|
.HasDatabaseName("ix_vault_key_grant_vault_id_key_generation_recipient_user_id")
|
|
.HasFilter("revoked_at_utc IS NULL AND recipient_user_id IS NOT NULL");
|
|
|
|
b.ToTable("vault_key_grant", "dodo", t =>
|
|
{
|
|
t.HasCheckConstraint("ck_vault_key_grant_recipient", "(kind = 1 AND recipient_user_id IS NOT NULL) OR (kind <> 1 AND recipient_user_id IS NULL)");
|
|
});
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.VaultSshKey", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("id");
|
|
|
|
b.Property<long>("ChangeSequence")
|
|
.HasColumnType("bigint")
|
|
.HasColumnName("change_sequence");
|
|
|
|
b.Property<Guid?>("ContentKeyId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("content_key_id");
|
|
|
|
b.Property<DateTimeOffset>("CreatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("created_at_utc");
|
|
|
|
b.Property<Guid>("CreatedByUserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("created_by_user_id");
|
|
|
|
b.Property<byte[]>("DataKeyWrap")
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("data_key_wrap");
|
|
|
|
b.Property<DateTimeOffset?>("DeletedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("deleted_at_utc");
|
|
|
|
b.Property<int>("KeyGeneration")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("key_generation");
|
|
|
|
b.Property<byte[]>("Payload")
|
|
.IsRequired()
|
|
.HasColumnType("bytea")
|
|
.HasColumnName("payload");
|
|
|
|
b.Property<short>("PayloadAadVersion")
|
|
.HasColumnType("smallint")
|
|
.HasColumnName("payload_aad_version");
|
|
|
|
b.Property<string>("PublicKeyFingerprint")
|
|
.HasMaxLength(128)
|
|
.HasColumnType("character varying(128)")
|
|
.HasColumnName("public_key_fingerprint");
|
|
|
|
b.Property<DateTimeOffset>("UpdatedAtUtc")
|
|
.HasColumnType("timestamp with time zone")
|
|
.HasColumnName("updated_at_utc");
|
|
|
|
b.Property<Guid>("UpdatedByUserId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("updated_by_user_id");
|
|
|
|
b.Property<Guid>("VaultId")
|
|
.HasColumnType("uuid")
|
|
.HasColumnName("vault_id");
|
|
|
|
b.Property<int>("Version")
|
|
.HasColumnType("integer")
|
|
.HasColumnName("version");
|
|
|
|
b.Property<uint>("xmin")
|
|
.IsConcurrencyToken()
|
|
.ValueGeneratedOnAddOrUpdate()
|
|
.HasColumnType("xid")
|
|
.HasColumnName("xmin");
|
|
|
|
b.HasKey("Id")
|
|
.HasName("pk_ssh_key");
|
|
|
|
b.HasIndex("VaultId")
|
|
.HasDatabaseName("ix_ssh_key_vault_live")
|
|
.HasFilter("deleted_at_utc IS NULL");
|
|
|
|
b.HasIndex("VaultId", "ChangeSequence")
|
|
.HasDatabaseName("ix_ssh_key_vault_id_change_sequence");
|
|
|
|
b.ToTable("ssh_key", "dodo", t =>
|
|
{
|
|
t.HasCheckConstraint("ck_ssh_key_version", "version >= 1");
|
|
});
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.Device", b =>
|
|
{
|
|
b.HasOne("DodoSSH.Domain.UserAccount", "User")
|
|
.WithMany("Devices")
|
|
.HasForeignKey("UserId")
|
|
.OnDelete(DeleteBehavior.Cascade)
|
|
.IsRequired()
|
|
.HasConstraintName("fk_device_users_user_id");
|
|
|
|
b.Navigation("User");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.SshHost", b =>
|
|
{
|
|
b.HasOne("DodoSSH.Domain.Vault", "Vault")
|
|
.WithMany("Hosts")
|
|
.HasForeignKey("VaultId")
|
|
.OnDelete(DeleteBehavior.Cascade)
|
|
.IsRequired()
|
|
.HasConstraintName("fk_host_vaults_vault_id");
|
|
|
|
b.Navigation("Vault");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.TeamMembership", b =>
|
|
{
|
|
b.HasOne("DodoSSH.Domain.Team", "Team")
|
|
.WithMany("Memberships")
|
|
.HasForeignKey("TeamId")
|
|
.OnDelete(DeleteBehavior.Cascade)
|
|
.IsRequired()
|
|
.HasConstraintName("fk_team_membership_team_team_id");
|
|
|
|
b.HasOne("DodoSSH.Domain.UserAccount", "User")
|
|
.WithMany()
|
|
.HasForeignKey("UserId")
|
|
.OnDelete(DeleteBehavior.Cascade)
|
|
.IsRequired()
|
|
.HasConstraintName("fk_team_membership_users_user_id");
|
|
|
|
b.Navigation("Team");
|
|
|
|
b.Navigation("User");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.UserKey", b =>
|
|
{
|
|
b.HasOne("DodoSSH.Domain.UserAccount", "User")
|
|
.WithMany("Keys")
|
|
.HasForeignKey("UserId")
|
|
.OnDelete(DeleteBehavior.Cascade)
|
|
.IsRequired()
|
|
.HasConstraintName("fk_user_key_user_account_user_id");
|
|
|
|
b.Navigation("User");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.UserKeyWrap", b =>
|
|
{
|
|
b.HasOne("DodoSSH.Domain.Device", "Device")
|
|
.WithMany()
|
|
.HasForeignKey("DeviceId")
|
|
.OnDelete(DeleteBehavior.Cascade)
|
|
.HasConstraintName("fk_user_key_wrap_device_device_id");
|
|
|
|
b.HasOne("DodoSSH.Domain.UserAccount", "User")
|
|
.WithMany("KeyWraps")
|
|
.HasForeignKey("UserId")
|
|
.OnDelete(DeleteBehavior.Cascade)
|
|
.IsRequired()
|
|
.HasConstraintName("fk_user_key_wrap_user_account_user_id");
|
|
|
|
b.Navigation("Device");
|
|
|
|
b.Navigation("User");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.Vault", b =>
|
|
{
|
|
b.HasOne("DodoSSH.Domain.UserAccount", "OwnerUser")
|
|
.WithMany()
|
|
.HasForeignKey("OwnerUserId")
|
|
.OnDelete(DeleteBehavior.Restrict)
|
|
.HasConstraintName("fk_vault_user_account_owner_user_id");
|
|
|
|
b.HasOne("DodoSSH.Domain.Team", "Team")
|
|
.WithMany()
|
|
.HasForeignKey("TeamId")
|
|
.OnDelete(DeleteBehavior.Restrict)
|
|
.HasConstraintName("fk_vault_team_team_id");
|
|
|
|
b.Navigation("OwnerUser");
|
|
|
|
b.Navigation("Team");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.VaultKeyGrant", b =>
|
|
{
|
|
b.HasOne("DodoSSH.Domain.UserAccount", "RecipientUser")
|
|
.WithMany()
|
|
.HasForeignKey("RecipientUserId")
|
|
.OnDelete(DeleteBehavior.Cascade)
|
|
.HasConstraintName("fk_vault_key_grant_user_account_recipient_user_id");
|
|
|
|
b.HasOne("DodoSSH.Domain.Vault", "Vault")
|
|
.WithMany("KeyGrants")
|
|
.HasForeignKey("VaultId")
|
|
.OnDelete(DeleteBehavior.Cascade)
|
|
.IsRequired()
|
|
.HasConstraintName("fk_vault_key_grant_vault_vault_id");
|
|
|
|
b.Navigation("RecipientUser");
|
|
|
|
b.Navigation("Vault");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.VaultSshKey", b =>
|
|
{
|
|
b.HasOne("DodoSSH.Domain.Vault", "Vault")
|
|
.WithMany()
|
|
.HasForeignKey("VaultId")
|
|
.OnDelete(DeleteBehavior.Cascade)
|
|
.IsRequired()
|
|
.HasConstraintName("fk_ssh_key_vaults_vault_id");
|
|
|
|
b.Navigation("Vault");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.Team", b =>
|
|
{
|
|
b.Navigation("Memberships");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.UserAccount", b =>
|
|
{
|
|
b.Navigation("Devices");
|
|
|
|
b.Navigation("KeyWraps");
|
|
|
|
b.Navigation("Keys");
|
|
});
|
|
|
|
modelBuilder.Entity("DodoSSH.Domain.Vault", b =>
|
|
{
|
|
b.Navigation("Hosts");
|
|
|
|
b.Navigation("KeyGrants");
|
|
});
|
|
#pragma warning restore 612, 618
|
|
}
|
|
}
|
|
}
|