feat: write Card (3) and Identity (4) ciphers
CI / test (push) Has been cancelled
CI / docker (push) Has been cancelled

Add card/identity sub-objects (camelCase serde per Vaultwarden API).
brand/expMonth/expYear sent plaintext; other fields encrypted.
Verified end-to-end: create + read decrypt correctly.
This commit is contained in:
Hermes committed 2026-08-02 19:12:57 +02:00
1 parent b48cb16746
commit 0ede8f145d
3 files changed
+198 -17

No files matched your search

+85
View File
@@ -765,6 +765,32 @@ type CipherInput struct {
Password string
URI string
Fields map[string]string // custom fields (login only)
// Card (type 3)
CardholderName string
Brand string // plaintext enum string, NOT encrypted
Number string
ExpMonth string // plaintext "01".."12"
ExpYear string // plaintext "2026"
Code string // CVV
// Identity (type 4)
Title string
FirstName string
MiddleName string
LastName string
Address1 string
Address2 string
Address3 string
City string
State string
PostalCode string
Country string
Company string
Email string
Phone string
SSN string
Passport string
License string
// Identity also reuses Username field above.
}
// CreateCipher encrypts and writes a new vault item via the Vaultwarden API.
@@ -844,6 +870,65 @@ func (ac *APIClient) CreateCipher(in CipherInput) (string, error) {
cipher["secureNote"] = map[string]interface{}{"notes": encNotes}
}
}
} else if in.Type == CipherTypeCard {
// Vaultwarden expects type=3 data in the `card` sub-object (camelCase serde).
// brand/expMonth/expYear are plaintext enum strings; others are encrypted.
card := map[string]interface{}{}
if in.CardholderName != "" {
if s, e := EncryptStr(in.CardholderName, key); e == nil {
card["cardholderName"] = s
}
}
if in.Brand != "" {
card["brand"] = in.Brand
}
if in.Number != "" {
if s, e := EncryptStr(in.Number, key); e == nil {
card["number"] = s
}
}
if in.ExpMonth != "" {
card["expMonth"] = in.ExpMonth
}
if in.ExpYear != "" {
card["expYear"] = in.ExpYear
}
if in.Code != "" {
if s, e := EncryptStr(in.Code, key); e == nil {
card["code"] = s
}
}
cipher["card"] = card
} else if in.Type == CipherTypeIdentity {
// Vaultwarden expects type=4 data in the `identity` sub-object (camelCase serde).
idn := map[string]interface{}{}
enc := func(field, val string) {
if val == "" {
return
}
if s, e := EncryptStr(val, key); e == nil {
idn[field] = s
}
}
enc("title", in.Title)
enc("firstName", in.FirstName)
enc("middleName", in.MiddleName)
enc("lastName", in.LastName)
enc("address1", in.Address1)
enc("address2", in.Address2)
enc("address3", in.Address3)
enc("city", in.City)
enc("state", in.State)
enc("postalCode", in.PostalCode)
enc("country", in.Country)
enc("company", in.Company)
enc("email", in.Email)
enc("phone", in.Phone)
enc("username", in.Username)
enc("ssn", in.SSN)
enc("passportNumber", in.Passport)
enc("licenseNumber", in.License)
cipher["identity"] = idn
} else {
return "", fmt.Errorf("cipher type %d write not supported yet", in.Type)
}