feat: write Card (3) and Identity (4) ciphers
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:
1 parent
b48cb16746
commit
0ede8f145d
3 files changed
+198
-17
No files matched your search
@@ -291,6 +291,31 @@ func (h *Handler) CreateSecret(c *fiber.Ctx) error {
|
||||
Password string `json:"password"`
|
||||
URI string `json:"uri"`
|
||||
Fields map[string]string `json:"fields"`
|
||||
// Card (type 3)
|
||||
CardholderName string `json:"cardholderName"`
|
||||
Brand string `json:"brand"`
|
||||
Number string `json:"number"`
|
||||
ExpMonth string `json:"expMonth"`
|
||||
ExpYear string `json:"expYear"`
|
||||
Code string `json:"code"`
|
||||
// Identity (type 4)
|
||||
Title string `json:"title"`
|
||||
FirstName string `json:"firstName"`
|
||||
MiddleName string `json:"middleName"`
|
||||
LastName string `json:"lastName"`
|
||||
Address1 string `json:"address1"`
|
||||
Address2 string `json:"address2"`
|
||||
Address3 string `json:"address3"`
|
||||
City string `json:"city"`
|
||||
State string `json:"state"`
|
||||
PostalCode string `json:"postalCode"`
|
||||
Country string `json:"country"`
|
||||
Company string `json:"company"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
SSN string `json:"ssn"`
|
||||
Passport string `json:"passport"`
|
||||
License string `json:"license"`
|
||||
}
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid JSON body"})
|
||||
@@ -307,18 +332,41 @@ func (h *Handler) CreateSecret(c *fiber.Ctx) error {
|
||||
if body.Type == 0 {
|
||||
body.Type = 1 // default to Login
|
||||
}
|
||||
if body.Type != 1 && body.Type != 2 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "only Login (1) and SecureNote (2) are supported for writing"})
|
||||
if body.Type < 1 || body.Type > 4 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "supported types: 1=Login, 2=SecureNote, 3=Card, 4=Identity"})
|
||||
}
|
||||
|
||||
id, err := h.vaultClient.CreateSecret(vaultwarden.CreateSecretInput{
|
||||
Type: body.Type,
|
||||
Name: body.Name,
|
||||
Notes: body.Notes,
|
||||
Username: body.Username,
|
||||
Password: body.Password,
|
||||
URI: body.URI,
|
||||
Fields: body.Fields,
|
||||
Type: body.Type,
|
||||
Name: body.Name,
|
||||
Notes: body.Notes,
|
||||
Username: body.Username,
|
||||
Password: body.Password,
|
||||
URI: body.URI,
|
||||
Fields: body.Fields,
|
||||
CardholderName: body.CardholderName,
|
||||
Brand: body.Brand,
|
||||
Number: body.Number,
|
||||
ExpMonth: body.ExpMonth,
|
||||
ExpYear: body.ExpYear,
|
||||
Code: body.Code,
|
||||
Title: body.Title,
|
||||
FirstName: body.FirstName,
|
||||
MiddleName: body.MiddleName,
|
||||
LastName: body.LastName,
|
||||
Address1: body.Address1,
|
||||
Address2: body.Address2,
|
||||
Address3: body.Address3,
|
||||
City: body.City,
|
||||
State: body.State,
|
||||
PostalCode: body.PostalCode,
|
||||
Country: body.Country,
|
||||
Company: body.Company,
|
||||
Email: body.Email,
|
||||
Phone: body.Phone,
|
||||
SSN: body.SSN,
|
||||
Passport: body.Passport,
|
||||
License: body.License,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error.Printf("Failed to create secret (requested by IP: %s): %v", c.IP(), err)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -199,13 +199,38 @@ func (c *Client) ClearCache() {
|
||||
|
||||
// CreateSecretInput is the public input for writing a new secret.
|
||||
type CreateSecretInput struct {
|
||||
Type int // 1=Login, 2=SecureNote
|
||||
Type int // 1=Login, 2=SecureNote, 3=Card, 4=Identity
|
||||
Name string
|
||||
Notes string
|
||||
Username string
|
||||
Password string
|
||||
URI string
|
||||
Fields map[string]string
|
||||
// Card (type 3)
|
||||
CardholderName string
|
||||
Brand string
|
||||
Number string
|
||||
ExpMonth string
|
||||
ExpYear string
|
||||
Code string
|
||||
// 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
|
||||
}
|
||||
|
||||
// CreateSecret writes a new vault item and refreshes the local cache so it is
|
||||
@@ -215,13 +240,36 @@ func (c *Client) CreateSecret(in CreateSecretInput) (string, error) {
|
||||
return "", fmt.Errorf("vault API client not configured")
|
||||
}
|
||||
id, err := c.api.CreateCipher(CipherInput{
|
||||
Type: in.Type,
|
||||
Name: in.Name,
|
||||
Notes: in.Notes,
|
||||
Username: in.Username,
|
||||
Password: in.Password,
|
||||
URI: in.URI,
|
||||
Fields: in.Fields,
|
||||
Type: in.Type,
|
||||
Name: in.Name,
|
||||
Notes: in.Notes,
|
||||
Username: in.Username,
|
||||
Password: in.Password,
|
||||
URI: in.URI,
|
||||
Fields: in.Fields,
|
||||
CardholderName: in.CardholderName,
|
||||
Brand: in.Brand,
|
||||
Number: in.Number,
|
||||
ExpMonth: in.ExpMonth,
|
||||
ExpYear: in.ExpYear,
|
||||
Code: in.Code,
|
||||
Title: in.Title,
|
||||
FirstName: in.FirstName,
|
||||
MiddleName: in.MiddleName,
|
||||
LastName: in.LastName,
|
||||
Address1: in.Address1,
|
||||
Address2: in.Address2,
|
||||
Address3: in.Address3,
|
||||
City: in.City,
|
||||
State: in.State,
|
||||
PostalCode: in.PostalCode,
|
||||
Country: in.Country,
|
||||
Company: in.Company,
|
||||
Email: in.Email,
|
||||
Phone: in.Phone,
|
||||
SSN: in.SSN,
|
||||
Passport: in.Passport,
|
||||
License: in.License,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
Reference in new issue
Block a user