package session import ( "bytes" "errors" "fmt" "os" "git.julianfamily.org/keepassgo/vault" "git.julianfamily.org/keepassgo/webdav" ) var ( ErrLocked = errors.New("vault is locked") ErrNoPath = errors.New("no vault path configured") ) type Manager struct { model vault.Model config *vault.KDBXConfig path string key vault.MasterKey locked bool encoded []byte remoteClient *webdav.Client remotePath string remoteVersion webdav.Version } func (m *Manager) Create(model vault.Model, key vault.MasterKey) error { var encoded bytes.Buffer if err := vault.SaveKDBXWithConfigAndKey(&encoded, model, key, m.config); err != nil { return fmt.Errorf("encode new vault: %w", err) } m.model = model m.key = key m.encoded = encoded.Bytes() m.locked = false return nil } func (m *Manager) Open(path string, key vault.MasterKey) error { content, err := os.ReadFile(path) if err != nil { return fmt.Errorf("read %s: %w", path, err) } model, config, err := vault.LoadKDBXWithConfig(bytes.NewReader(content), key) if err != nil { return fmt.Errorf("open %s: %w", path, err) } m.model = model m.config = config m.path = path m.key = key m.encoded = content m.locked = false return nil } func (m *Manager) Save() error { if m.remoteClient != nil && m.remotePath != "" { return m.SaveRemote() } if m.path == "" { return ErrNoPath } return m.saveToPath(m.path) } func (m *Manager) OpenRemote(client webdav.Client, path string, key vault.MasterKey) error { content, version, err := client.Open(path) if err != nil { return fmt.Errorf("open remote %s: %w", path, err) } model, config, err := vault.LoadKDBXWithConfig(bytes.NewReader(content), key) if err != nil { return fmt.Errorf("decode remote %s: %w", path, err) } m.model = model m.config = config m.key = key m.encoded = content m.locked = false m.remoteClient = &client m.remotePath = path m.remoteVersion = version return nil } func (m *Manager) SaveRemote() error { if m.remoteClient == nil || m.remotePath == "" { return ErrNoPath } encoded, err := m.persistableBytes() if err != nil { return err } version, err := m.remoteClient.Save(m.remotePath, bytes.NewReader(encoded), m.remoteVersion) if err != nil { return fmt.Errorf("save remote %s: %w", m.remotePath, err) } m.encoded = encoded m.remoteVersion = version return nil } func (m *Manager) SaveAs(path string) error { if err := m.saveToPath(path); err != nil { return err } m.path = path return nil } func (m *Manager) Replace(model vault.Model) { m.model = model m.locked = false } func (m *Manager) Current() (vault.Model, error) { if m.locked { return vault.Model{}, ErrLocked } return m.model, nil } func (m *Manager) Lock() error { if m.locked { return nil } var encoded bytes.Buffer if err := vault.SaveKDBXWithConfigAndKey(&encoded, m.model, m.key, m.config); err != nil { return fmt.Errorf("encode vault for lock: %w", err) } m.encoded = encoded.Bytes() m.model = vault.Model{} m.locked = true return nil } func (m *Manager) Unlock(key vault.MasterKey) error { model, config, err := vault.LoadKDBXWithConfig(bytes.NewReader(m.encoded), key) if err != nil { return fmt.Errorf("unlock vault: %w", err) } m.model = model m.config = config m.key = key m.locked = false return nil } func (m *Manager) ChangeMasterKey(key vault.MasterKey) error { var ( model vault.Model config *vault.KDBXConfig err error ) if m.locked { model, config, err = vault.LoadKDBXWithConfig(bytes.NewReader(m.encoded), m.key) if err != nil { return fmt.Errorf("decode locked vault: %w", err) } } else { model = m.model config = m.config } var encoded bytes.Buffer if err := vault.SaveKDBXWithConfigAndKey(&encoded, model, key, config); err != nil { return fmt.Errorf("encode vault with updated master key: %w", err) } m.key = key m.config = config m.encoded = encoded.Bytes() if !m.locked { m.model = model } return nil } func (m *Manager) saveToPath(path string) error { encoded, err := m.persistableBytes() if err != nil { return err } if err := os.WriteFile(path, encoded, 0o600); err != nil { return fmt.Errorf("write %s: %w", path, err) } m.encoded = encoded return nil } func (m *Manager) persistableBytes() ([]byte, error) { if m.locked { return append([]byte(nil), m.encoded...), nil } var encoded bytes.Buffer if err := vault.SaveKDBXWithConfigAndKey(&encoded, m.model, m.key, m.config); err != nil { return nil, fmt.Errorf("encode vault: %w", err) } return encoded.Bytes(), nil }