using Bit.Core.Vault.Enums; namespace Bit.Seeder.Models; /// /// Normalized, strongly-typed plaintext for a single cipher before encryption. /// Constructed from fixture JSON via FromSeedItem() or programmatically via CipherComposer. /// internal record CipherSeed { /// /// Drives factory dispatch in . Individual /// factories do not read this field — each hard-codes its own type. Exactly one /// matching type-specific DTO (Login, Card, Identity, SecureNote, SshKey) must be non-null. /// public required CipherType Type { get; init; } /// /// Plaintext cipher name (will be encrypted by the factory). /// public required string Name { get; init; } /// /// Symmetric key (org key or user key) used for Rust FFI encryption. /// public string? EncryptionKey { get; init; } /// /// Optional plaintext notes (will be encrypted by the factory). /// public string? Notes { get; init; } /// /// Optional custom fields (will be encrypted by the factory). /// public List? Fields { get; init; } /// /// Master-password re-prompt (0 = None, 1 = Password). /// public CipherRepromptType Reprompt { get; init; } /// /// Owning organization. Null for personal vault ciphers. /// public Guid? OrganizationId { get; init; } /// /// Owning user for personal vault ciphers. Null for organization ciphers. /// public Guid? UserId { get; init; } /// /// Plaintext login data (username, password, URIs). Non-null when Type is Login. /// public LoginViewDto? Login { get; init; } /// /// Plaintext card data (cardholder, number, expiry). Non-null when Type is Card. /// public CardViewDto? Card { get; init; } /// /// Plaintext identity data (name, address, documents). Non-null when Type is Identity. /// public IdentityViewDto? Identity { get; init; } /// /// Secure note type marker. Non-null when Type is SecureNote. /// The actual note content is carried by the Notes property, not this DTO. /// public SecureNoteViewDto? SecureNote { get; init; } /// /// Plaintext SSH key data (private key, public key, fingerprint). Non-null when Type is SSHKey. /// public SshKeyViewDto? SshKey { get; init; } /// /// Validates that required fields are set before factory consumption. /// Call after populating EncryptionKey via with. /// internal void Validate() { ArgumentException.ThrowIfNullOrEmpty(EncryptionKey); } /// /// Maps a deserialized into a , /// converting Seed* models to their ViewDto counterparts. /// EncryptionKey, OrganizationId, and UserId are left null — callers set them via with. /// internal static CipherSeed FromSeedItem(SeedVaultItem item) => new() { Type = MapCipherType(item.Type), Name = item.Name, Notes = item.Notes, Reprompt = item.Reprompt == 1 ? CipherRepromptType.Password : CipherRepromptType.None, Fields = MapFields(item.Fields), Login = MapLogin(item.Login), Card = MapCard(item.Card), Identity = MapIdentity(item.Identity), SecureNote = item.Type == "secureNote" ? new SecureNoteViewDto { Type = 0 } : null, SshKey = MapSshKey(item.SshKey) }; private static CipherType MapCipherType(string type) => type switch { "login" => CipherType.Login, "card" => CipherType.Card, "identity" => CipherType.Identity, "secureNote" => CipherType.SecureNote, "sshKey" => CipherType.SSHKey, _ => throw new ArgumentException($"Unknown cipher type: '{type}'", nameof(type)) }; private static List? MapFields(List? fields) => fields?.Select(f => new FieldViewDto { Name = f.Name, Value = f.Value, Type = MapFieldType(f.Type) }).ToList(); private static int MapFieldType(string type) => type switch { "hidden" => 1, "boolean" => 2, "linked" => 3, "text" => 0, _ => throw new ArgumentException($"Unknown field type: '{type}'", nameof(type)) }; private static LoginViewDto? MapLogin(SeedLogin? login) => login == null ? null : new LoginViewDto { Username = login.Username, Password = login.Password, Totp = login.Totp, Uris = login.Uris?.Select(u => new LoginUriViewDto { Uri = u.Uri, Match = MapUriMatchType(u.Match) }).ToList() }; private static int MapUriMatchType(string match) => match switch { "host" => 1, "startsWith" => 2, "exact" => 3, "regex" => 4, "never" => 5, "domain" => 0, _ => throw new ArgumentException($"Unknown URI match type: '{match}'", nameof(match)) }; private static CardViewDto? MapCard(SeedCard? card) => card == null ? null : new CardViewDto { CardholderName = card.CardholderName, Brand = card.Brand, Number = card.Number, ExpMonth = card.ExpMonth, ExpYear = card.ExpYear, Code = card.Code }; private static IdentityViewDto? MapIdentity(SeedIdentity? identity) => identity == null ? null : new IdentityViewDto { FirstName = identity.FirstName, MiddleName = identity.MiddleName, LastName = identity.LastName, Address1 = identity.Address1, Address2 = identity.Address2, Address3 = identity.Address3, City = identity.City, State = identity.State, PostalCode = identity.PostalCode, Country = identity.Country, Company = identity.Company, Email = identity.Email, Phone = identity.Phone, SSN = identity.Ssn, Username = identity.Username, PassportNumber = identity.PassportNumber, LicenseNumber = identity.LicenseNumber }; private static SshKeyViewDto? MapSshKey(SeedSshKey? sshKey) => sshKey == null ? null : new SshKeyViewDto { PrivateKey = sshKey.PrivateKey, PublicKey = sshKey.PublicKey, Fingerprint = sshKey.KeyFingerprint }; }