vault_cache.go

v1.0.0
Doc Versions Source
1
package bitwarden
2
3
import (
4
	"strings"
5
	"sync"
6
	"time"
7
8
	"sourcecraft.dev/bigbes/go-bitwarden/crypto"
9
)
10
11
// vaultCache holds an in-memory copy of the decrypted vault data.
12
// It reduces server calls for read-heavy workloads by caching the
13
// results of sync and invalidating on writes.
14
type vaultCache struct {
15
	mu sync.RWMutex
16
17
	// Decrypted vault data
18
	ciphers []Item
19
	folders []Folder
20
	sends   []Send
21
22
	// Tracking
23
	lastSync time.Time
24
25
	// Whether the cache has been populated at all
26
	populated bool
27
}
28
29
// populate stores a full set of decrypted vault data.
30
func (vc *vaultCache) populate(ciphers []Item, folders []Folder, sends []Send) {
31
	vc.mu.Lock()
32
	defer vc.mu.Unlock()
33
	vc.ciphers = ciphers
34
	vc.folders = folders
35
	vc.sends = sends
36
	vc.populated = true
37
	vc.lastSync = time.Now()
38
}
39
40
// invalidate clears all cached data.
41
func (vc *vaultCache) invalidate() {
42
	vc.mu.Lock()
43
	defer vc.mu.Unlock()
44
	vc.ciphers = nil
45
	vc.folders = nil
46
	vc.sends = nil
47
	vc.populated = false
48
}
49
50
// isPopulated returns whether the cache has data.
51
func (vc *vaultCache) isPopulated() bool {
52
	vc.mu.RLock()
53
	defer vc.mu.RUnlock()
54
	return vc.populated
55
}
56
57
// --- Cipher cache operations ---
58
59
func (vc *vaultCache) getCiphers() ([]Item, bool) {
60
	vc.mu.RLock()
61
	defer vc.mu.RUnlock()
62
	if !vc.populated {
63
		return nil, false
64
	}
65
	out := make([]Item, len(vc.ciphers))
66
	copy(out, vc.ciphers)
67
	return out, true
68
}
69
70
func (vc *vaultCache) getCipher(id string) (*Item, bool) {
71
	vc.mu.RLock()
72
	defer vc.mu.RUnlock()
73
	if !vc.populated {
74
		return nil, false
75
	}
76
	for i := range vc.ciphers {
77
		if vc.ciphers[i].ID == id {
78
			item := vc.ciphers[i]
79
			return &item, true
80
		}
81
	}
82
	return nil, false
83
}
84
85
func (vc *vaultCache) upsertCipher(item Item) {
86
	vc.mu.Lock()
87
	defer vc.mu.Unlock()
88
	if !vc.populated {
89
		return
90
	}
91
	for i, c := range vc.ciphers {
92
		if c.ID == item.ID {
93
			vc.ciphers[i] = item
94
			return
95
		}
96
	}
97
	vc.ciphers = append(vc.ciphers, item)
98
}
99
100
func (vc *vaultCache) softDeleteCipher(id string) {
101
	vc.mu.Lock()
102
	defer vc.mu.Unlock()
103
	if !vc.populated {
104
		return
105
	}
106
	now := time.Now().UTC().Format(time.RFC3339)
107
	for i := range vc.ciphers {
108
		if vc.ciphers[i].ID == id {
109
			vc.ciphers[i].DeletedDate = &now
110
			return
111
		}
112
	}
113
}
114
115
func (vc *vaultCache) restoreCipher(id string) {
116
	vc.mu.Lock()
117
	defer vc.mu.Unlock()
118
	if !vc.populated {
119
		return
120
	}
121
	for i := range vc.ciphers {
122
		if vc.ciphers[i].ID == id {
123
			vc.ciphers[i].DeletedDate = nil
124
			return
125
		}
126
	}
127
}
128
129
func (vc *vaultCache) removeCipher(id string) {
130
	vc.mu.Lock()
131
	defer vc.mu.Unlock()
132
	if !vc.populated {
133
		return
134
	}
135
	for i, c := range vc.ciphers {
136
		if c.ID == id {
137
			vc.ciphers = append(vc.ciphers[:i], vc.ciphers[i+1:]...)
138
			return
139
		}
140
	}
141
}
142
143
// --- Folder cache operations ---
144
145
func (vc *vaultCache) getFolders() ([]Folder, bool) {
146
	vc.mu.RLock()
147
	defer vc.mu.RUnlock()
148
	if !vc.populated {
149
		return nil, false
150
	}
151
	out := make([]Folder, len(vc.folders))
152
	copy(out, vc.folders)
153
	return out, true
154
}
155
156
func (vc *vaultCache) getFolder(id string) (*Folder, bool) {
157
	vc.mu.RLock()
158
	defer vc.mu.RUnlock()
159
	if !vc.populated {
160
		return nil, false
161
	}
162
	for i := range vc.folders {
163
		if vc.folders[i].ID == id {
164
			f := vc.folders[i]
165
			return &f, true
166
		}
167
	}
168
	return nil, false
169
}
170
171
func (vc *vaultCache) upsertFolder(folder Folder) {
172
	vc.mu.Lock()
173
	defer vc.mu.Unlock()
174
	if !vc.populated {
175
		return
176
	}
177
	for i, f := range vc.folders {
178
		if f.ID == folder.ID {
179
			vc.folders[i] = folder
180
			return
181
		}
182
	}
183
	vc.folders = append(vc.folders, folder)
184
}
185
186
func (vc *vaultCache) removeFolder(id string) {
187
	vc.mu.Lock()
188
	defer vc.mu.Unlock()
189
	if !vc.populated {
190
		return
191
	}
192
	for i, f := range vc.folders {
193
		if f.ID == id {
194
			vc.folders = append(vc.folders[:i], vc.folders[i+1:]...)
195
			return
196
		}
197
	}
198
}
199
200
// --- Send cache operations ---
201
202
func (vc *vaultCache) getSends() ([]Send, bool) {
203
	vc.mu.RLock()
204
	defer vc.mu.RUnlock()
205
	if !vc.populated {
206
		return nil, false
207
	}
208
	out := make([]Send, len(vc.sends))
209
	copy(out, vc.sends)
210
	return out, true
211
}
212
213
func (vc *vaultCache) upsertSend(send Send) {
214
	vc.mu.Lock()
215
	defer vc.mu.Unlock()
216
	if !vc.populated {
217
		return
218
	}
219
	for i, s := range vc.sends {
220
		if s.ID == send.ID {
221
			vc.sends[i] = send
222
			return
223
		}
224
	}
225
	vc.sends = append(vc.sends, send)
226
}
227
228
func (vc *vaultCache) removeSend(id string) {
229
	vc.mu.Lock()
230
	defer vc.mu.Unlock()
231
	if !vc.populated {
232
		return
233
	}
234
	for i, s := range vc.sends {
235
		if s.ID == id {
236
			vc.sends = append(vc.sends[:i], vc.sends[i+1:]...)
237
			return
238
		}
239
	}
240
}
241
242
// --- Filter helpers ---
243
244
func filterCiphersFromCache(items []Item, filter ListFilter) []Item {
245
	var result []Item
246
	for i := range items {
247
		if matchesFilter(&items[i], filter) {
248
			result = append(result, items[i])
249
		}
250
	}
251
	return result
252
}
253
254
func filterFoldersFromCache(folders []Folder, search string, _ *crypto.KeyChain) []Folder {
255
	if search == "" {
256
		result := make([]Folder, len(folders))
257
		copy(result, folders)
258
		return result
259
	}
260
	searchLower := strings.ToLower(search)
261
	var result []Folder
262
	for _, f := range folders {
263
		if strings.Contains(strings.ToLower(f.Name), searchLower) {
264
			result = append(result, f)
265
		}
266
	}
267
	return result
268
}
269

Source Files