feat: add folderId, custom fields, PUT/DELETE secret, GET /folders
This commit is contained in:
1 parent
0ede8f145d
commit
30ae00d5f4
4 files changed
+426
-1
No files matched your search
+4
-1
@@ -95,7 +95,7 @@ func main() {
|
|||||||
|
|
||||||
app.Use(cors.New(cors.Config{
|
app.Use(cors.New(cors.Config{
|
||||||
AllowOrigins: cfg.CORSAllowedOrigins,
|
AllowOrigins: cfg.CORSAllowedOrigins,
|
||||||
AllowMethods: "GET,POST",
|
AllowMethods: "GET,POST,PUT,DELETE",
|
||||||
AllowHeaders: "Authorization,Content-Type",
|
AllowHeaders: "Authorization,Content-Type",
|
||||||
AllowCredentials: false,
|
AllowCredentials: false,
|
||||||
}))
|
}))
|
||||||
@@ -123,6 +123,9 @@ func main() {
|
|||||||
|
|
||||||
api.Get("/secret/:name", h.GetSecret)
|
api.Get("/secret/:name", h.GetSecret)
|
||||||
api.Post("/secret", h.CreateSecret)
|
api.Post("/secret", h.CreateSecret)
|
||||||
|
api.Put("/secret/:name", h.UpdateSecret)
|
||||||
|
api.Delete("/secret/:name", h.DeleteSecret)
|
||||||
|
api.Get("/folders", h.ListFolders)
|
||||||
api.Post("/refresh", h.RefreshCache)
|
api.Post("/refresh", h.RefreshCache)
|
||||||
|
|
||||||
// Graceful shutdown.
|
// Graceful shutdown.
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ func (h *Handler) CreateSecret(c *fiber.Ctx) error {
|
|||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
URI string `json:"uri"`
|
URI string `json:"uri"`
|
||||||
Fields map[string]string `json:"fields"`
|
Fields map[string]string `json:"fields"`
|
||||||
|
FolderID string `json:"folderId"`
|
||||||
// Card (type 3)
|
// Card (type 3)
|
||||||
CardholderName string `json:"cardholderName"`
|
CardholderName string `json:"cardholderName"`
|
||||||
Brand string `json:"brand"`
|
Brand string `json:"brand"`
|
||||||
@@ -344,6 +345,7 @@ func (h *Handler) CreateSecret(c *fiber.Ctx) error {
|
|||||||
Password: body.Password,
|
Password: body.Password,
|
||||||
URI: body.URI,
|
URI: body.URI,
|
||||||
Fields: body.Fields,
|
Fields: body.Fields,
|
||||||
|
FolderID: body.FolderID,
|
||||||
CardholderName: body.CardholderName,
|
CardholderName: body.CardholderName,
|
||||||
Brand: body.Brand,
|
Brand: body.Brand,
|
||||||
Number: body.Number,
|
Number: body.Number,
|
||||||
@@ -380,3 +382,136 @@ func (h *Handler) CreateSecret(c *fiber.Ctx) error {
|
|||||||
"type": body.Type,
|
"type": body.Type,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateSecret handles PUT /secret/:name. Body (JSON) is the same as CreateSecret.
|
||||||
|
// It edits the existing item in place (same cipher ID).
|
||||||
|
func (h *Handler) UpdateSecret(c *fiber.Ctx) error {
|
||||||
|
name, err := decodeSecretPathParam(c.Params("name"))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid secret name format"})
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "secret name is required"})
|
||||||
|
}
|
||||||
|
|
||||||
|
var body struct {
|
||||||
|
Type int `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Notes string `json:"notes"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
URI string `json:"uri"`
|
||||||
|
Fields map[string]string `json:"fields"`
|
||||||
|
FolderID string `json:"folderId"`
|
||||||
|
// 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"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.Type == 0 {
|
||||||
|
body.Type = 1
|
||||||
|
}
|
||||||
|
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"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.vaultClient.UpdateSecret(name, vaultwarden.CreateSecretInput{
|
||||||
|
Type: body.Type,
|
||||||
|
Name: body.Name,
|
||||||
|
Notes: body.Notes,
|
||||||
|
Username: body.Username,
|
||||||
|
Password: body.Password,
|
||||||
|
URI: body.URI,
|
||||||
|
Fields: body.Fields,
|
||||||
|
FolderID: body.FolderID,
|
||||||
|
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,
|
||||||
|
}); err != nil {
|
||||||
|
logger.Error.Printf("Failed to update secret %q: %v", name, err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(fiber.Map{
|
||||||
|
"status": "updated",
|
||||||
|
"name": name,
|
||||||
|
"type": body.Type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFolders handles GET /folders and returns decrypted folder name -> id map.
|
||||||
|
func (h *Handler) ListFolders(c *fiber.Ctx) error {
|
||||||
|
// Client stores folders as id -> name; invert for easy name lookup by clients.
|
||||||
|
byID := h.vaultClient.ListFolders()
|
||||||
|
byName := make(map[string]string, len(byID))
|
||||||
|
for id, name := range byID {
|
||||||
|
byName[name] = id
|
||||||
|
}
|
||||||
|
return c.JSON(byName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSecret handles DELETE /secret/:name.
|
||||||
|
func (h *Handler) DeleteSecret(c *fiber.Ctx) error {
|
||||||
|
name, err := decodeSecretPathParam(c.Params("name"))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid secret name format"})
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "secret name is required"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.vaultClient.DeleteSecret(name); err != nil {
|
||||||
|
logger.Error.Printf("Failed to delete secret %q: %v", name, err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(fiber.Map{
|
||||||
|
"status": "deleted",
|
||||||
|
"name": name,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -765,6 +765,7 @@ type CipherInput struct {
|
|||||||
Password string
|
Password string
|
||||||
URI string
|
URI string
|
||||||
Fields map[string]string // custom fields (login only)
|
Fields map[string]string // custom fields (login only)
|
||||||
|
FolderID string // optional folder UUID to place the item in
|
||||||
// Card (type 3)
|
// Card (type 3)
|
||||||
CardholderName string
|
CardholderName string
|
||||||
Brand string // plaintext enum string, NOT encrypted
|
Brand string // plaintext enum string, NOT encrypted
|
||||||
@@ -823,6 +824,9 @@ func (ac *APIClient) CreateCipher(in CipherInput) (string, error) {
|
|||||||
"organizationId": nil,
|
"organizationId": nil,
|
||||||
"collectionIds": []string{},
|
"collectionIds": []string{},
|
||||||
}
|
}
|
||||||
|
if in.FolderID != "" {
|
||||||
|
cipher["folderId"] = in.FolderID
|
||||||
|
}
|
||||||
if in.Notes != "" {
|
if in.Notes != "" {
|
||||||
encNotes, e := EncryptStr(in.Notes, key)
|
encNotes, e := EncryptStr(in.Notes, key)
|
||||||
if e == nil {
|
if e == nil {
|
||||||
@@ -973,6 +977,204 @@ func (ac *APIClient) CreateCipher(in CipherInput) (string, error) {
|
|||||||
return out.ID, nil
|
return out.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteCipher removes an existing vault item by its cipher ID.
|
||||||
|
func (ac *APIClient) DeleteCipher(id string) error {
|
||||||
|
if err := ac.EnsureValidToken(); err != nil {
|
||||||
|
return fmt.Errorf("ensure valid token: %w", err)
|
||||||
|
}
|
||||||
|
ac.mu.RLock()
|
||||||
|
token := ac.accessToken
|
||||||
|
ac.mu.RUnlock()
|
||||||
|
|
||||||
|
req, err := http.NewRequest("DELETE", ac.baseURL+"/api/ciphers/"+id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("delete request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
req.Header.Set("Bitwarden-Client-Version", "2024.1.0")
|
||||||
|
req.Header.Set("User-Agent", "Bitwarden/v2024.1.0")
|
||||||
|
req.Header.Set("Device-Type", "8")
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
resp, err := ac.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("delete cipher request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("delete cipher failed (HTTP %d): %s", resp.StatusCode, string(b))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCipher edits an existing vault item by its cipher ID. It re-encrypts
|
||||||
|
// the supplied fields and sends a PUT to /api/ciphers/{id} (same payload shape
|
||||||
|
// as CreateCipher). Fields left empty in CipherInput keep their existing values
|
||||||
|
// ONLY when re-fetched; for simplicity callers should supply the full desired state.
|
||||||
|
func (ac *APIClient) UpdateCipher(id string, in CipherInput) error {
|
||||||
|
if err := ac.EnsureValidToken(); err != nil {
|
||||||
|
return fmt.Errorf("ensure valid token: %w", err)
|
||||||
|
}
|
||||||
|
ac.mu.RLock()
|
||||||
|
token := ac.accessToken
|
||||||
|
key := ac.symKey
|
||||||
|
ac.mu.RUnlock()
|
||||||
|
|
||||||
|
encName, err := EncryptStr(in.Name, key)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encrypt name: %w", err)
|
||||||
|
}
|
||||||
|
cipher := map[string]interface{}{
|
||||||
|
"type": in.Type,
|
||||||
|
"name": encName,
|
||||||
|
"notes": nil,
|
||||||
|
"login": nil,
|
||||||
|
"card": nil,
|
||||||
|
"identity": nil,
|
||||||
|
"fields": nil,
|
||||||
|
"reprompt": 0,
|
||||||
|
"edit": true,
|
||||||
|
"viewPassword": true,
|
||||||
|
"favorite": false,
|
||||||
|
"folderId": nil,
|
||||||
|
"organizationId": nil,
|
||||||
|
"collectionIds": []string{},
|
||||||
|
}
|
||||||
|
if in.FolderID != "" {
|
||||||
|
cipher["folderId"] = in.FolderID
|
||||||
|
}
|
||||||
|
if in.Notes != "" {
|
||||||
|
if encNotes, e := EncryptStr(in.Notes, key); e == nil {
|
||||||
|
cipher["notes"] = encNotes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if in.Type == CipherTypeLogin {
|
||||||
|
login := map[string]interface{}{}
|
||||||
|
if in.Username != "" {
|
||||||
|
if u, e := EncryptStr(in.Username, key); e == nil {
|
||||||
|
login["username"] = u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.Password != "" {
|
||||||
|
if pw, e := EncryptStr(in.Password, key); e == nil {
|
||||||
|
login["password"] = pw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.URI != "" {
|
||||||
|
if uri, e := EncryptStr(in.URI, key); e == nil {
|
||||||
|
login["uris"] = []map[string]interface{}{{"uri": uri, "match": nil}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cipher["login"] = login
|
||||||
|
if len(in.Fields) > 0 {
|
||||||
|
fields := make([]map[string]interface{}, 0, len(in.Fields))
|
||||||
|
for k, v := range in.Fields {
|
||||||
|
fk, e1 := EncryptStr(k, key)
|
||||||
|
fv, e2 := EncryptStr(v, key)
|
||||||
|
if e1 == nil && e2 == nil {
|
||||||
|
fields = append(fields, map[string]interface{}{"name": fk, "value": fv, "type": 0})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cipher["fields"] = fields
|
||||||
|
}
|
||||||
|
} else if in.Type == CipherTypeSecureNote {
|
||||||
|
if in.Notes != "" {
|
||||||
|
if encNotes, e := EncryptStr(in.Notes, key); e == nil {
|
||||||
|
cipher["secureNote"] = map[string]interface{}{"notes": encNotes}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if in.Type == CipherTypeCard {
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(cipher)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal cipher: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", ac.baseURL+"/api/ciphers/"+id, bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("update request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Bitwarden-Client-Version", "2024.1.0")
|
||||||
|
req.Header.Set("User-Agent", "Bitwarden/v2024.1.0")
|
||||||
|
req.Header.Set("Device-Type", "8")
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
resp, err := ac.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("update cipher request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("update cipher failed (HTTP %d): %s", resp.StatusCode, string(b))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// fetchProfileKey gets the encrypted symmetric key from the user's profile.
|
// fetchProfileKey gets the encrypted symmetric key from the user's profile.
|
||||||
// Used when API key login doesn't return the Key in the token response.
|
// Used when API key login doesn't return the Key in the token response.
|
||||||
func (ac *APIClient) fetchProfileKey() (string, error) {
|
func (ac *APIClient) fetchProfileKey() (string, error) {
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ type CreateSecretInput struct {
|
|||||||
Password string
|
Password string
|
||||||
URI string
|
URI string
|
||||||
Fields map[string]string
|
Fields map[string]string
|
||||||
|
FolderID string // optional folder UUID
|
||||||
// Card (type 3)
|
// Card (type 3)
|
||||||
CardholderName string
|
CardholderName string
|
||||||
Brand string
|
Brand string
|
||||||
@@ -247,6 +248,7 @@ func (c *Client) CreateSecret(in CreateSecretInput) (string, error) {
|
|||||||
Password: in.Password,
|
Password: in.Password,
|
||||||
URI: in.URI,
|
URI: in.URI,
|
||||||
Fields: in.Fields,
|
Fields: in.Fields,
|
||||||
|
FolderID: in.FolderID,
|
||||||
CardholderName: in.CardholderName,
|
CardholderName: in.CardholderName,
|
||||||
Brand: in.Brand,
|
Brand: in.Brand,
|
||||||
Number: in.Number,
|
Number: in.Number,
|
||||||
@@ -279,6 +281,82 @@ func (c *Client) CreateSecret(in CreateSecretInput) (string, error) {
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSecretID resolves a cipher ID from its name (case-insensitive exact/partial match).
|
||||||
|
func (c *Client) GetSecretID(name string) (string, error) {
|
||||||
|
item, err := c.getSecretItem(name, SecretFilter{})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return item.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSecret edits an existing secret identified by name. It resolves the
|
||||||
|
// cipher ID, then sends a PUT with the supplied fields. Empty fields in the
|
||||||
|
// input are NOT merged with existing values — callers should pass the full
|
||||||
|
// desired state (or fetch-then-merge if partial updates are needed).
|
||||||
|
func (c *Client) UpdateSecret(name string, in CreateSecretInput) error {
|
||||||
|
if c.api == nil {
|
||||||
|
return fmt.Errorf("vault API client not configured")
|
||||||
|
}
|
||||||
|
id, err := c.GetSecretID(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.api.UpdateCipher(id, CipherInput{
|
||||||
|
Type: in.Type,
|
||||||
|
Name: in.Name,
|
||||||
|
Notes: in.Notes,
|
||||||
|
Username: in.Username,
|
||||||
|
Password: in.Password,
|
||||||
|
URI: in.URI,
|
||||||
|
Fields: in.Fields,
|
||||||
|
FolderID: in.FolderID,
|
||||||
|
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,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.ClearCache()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSecret removes a secret identified by name.
|
||||||
|
func (c *Client) DeleteSecret(name string) error {
|
||||||
|
if c.api == nil {
|
||||||
|
return fmt.Errorf("vault API client not configured")
|
||||||
|
}
|
||||||
|
id, err := c.GetSecretID(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.api.DeleteCipher(id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.ClearCache()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Stop stops the background sync goroutine.
|
// Stop stops the background sync goroutine.
|
||||||
func (c *Client) Stop() {
|
func (c *Client) Stop() {
|
||||||
close(c.stopSync)
|
close(c.stopSync)
|
||||||
@@ -296,6 +374,13 @@ func (c *Client) NameMaps() SyncNameMaps {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListFolders returns decrypted folder name -> id mappings from the last sync.
|
||||||
|
func (c *Client) ListFolders() map[string]string {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return maps.Clone(c.nameMaps.Folders)
|
||||||
|
}
|
||||||
|
|
||||||
// syncVault fetches and decrypts all items from the vault.
|
// syncVault fetches and decrypts all items from the vault.
|
||||||
func (c *Client) syncVault() error {
|
func (c *Client) syncVault() error {
|
||||||
items, nameMaps, err := c.api.Sync()
|
items, nameMaps, err := c.api.Sync()
|
||||||
|
|||||||
Reference in new issue
Block a user