1use crate::secrets_store_capnp::{self, secret_entry, secret_version_ref};
2use capnp::text_list;
3use chrono::{TimeZone, Utc};
4use serde::{Deserialize, Serialize};
5use std::cmp::Ordering;
6use std::collections::BTreeMap;
7use std::collections::HashMap;
8use std::fmt;
9use zeroize::Zeroize;
10mod command;
11mod config;
12mod event;
13mod zeroize_datetime;
14
15#[cfg(test)]
16mod tests;
17
18pub use command::*;
19pub use config::*;
20pub use event::*;
21pub use zeroize_datetime::*;
22
23pub const PROPERTY_USERNAME: &str = "username";
24pub const PROPERTY_PASSWORD: &str = "password";
25pub const PROPERTY_TOTP_URL: &str = "totpUrl";
26pub const PROPERTY_NOTES: &str = "notes";
27
28#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
31#[cfg_attr(feature = "with_specta", derive(specta::Type))]
32#[zeroize(drop)]
33pub struct Status {
34 pub locked: bool,
35 pub unlocked_by: Option<Identity>,
36 pub autolock_at: Option<ZeroizeDateTime>,
37 pub version: String,
38 pub autolock_timeout: u64,
39}
40
41#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
45#[cfg_attr(feature = "with_specta", derive(specta::Type))]
46#[zeroize(drop)]
47pub struct Identity {
48 pub id: String,
49 pub name: String,
50 pub email: String,
51 pub hidden: bool,
52}
53
54impl std::fmt::Display for Identity {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 write!(f, "{} <{}>", self.name, self.email)
57 }
58}
59
60#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
65#[cfg_attr(feature = "with_specta", derive(specta::Type))]
66#[serde(rename_all = "lowercase")]
67pub enum SecretType {
68 Login,
69 Note,
70 Licence,
71 Wlan,
72 Password,
73 #[serde(other)]
74 Other,
75}
76
77impl Zeroize for SecretType {
78 fn zeroize(&mut self) {
79 *self = SecretType::Other
80 }
81}
82
83impl SecretType {
84 pub fn password_properties(&self) -> &[&str] {
88 match self {
89 SecretType::Login => &[PROPERTY_PASSWORD],
90 SecretType::Note => &[],
91 SecretType::Licence => &[],
92 SecretType::Wlan => &[PROPERTY_PASSWORD],
93 SecretType::Password => &[PROPERTY_PASSWORD],
94 SecretType::Other => &[],
95 }
96 }
97
98 pub fn from_reader(api: secrets_store_capnp::SecretType) -> Self {
99 match api {
100 secrets_store_capnp::SecretType::Login => SecretType::Login,
101 secrets_store_capnp::SecretType::Licence => SecretType::Licence,
102 secrets_store_capnp::SecretType::Wlan => SecretType::Wlan,
103 secrets_store_capnp::SecretType::Note => SecretType::Note,
104 secrets_store_capnp::SecretType::Password => SecretType::Password,
105 secrets_store_capnp::SecretType::Other => SecretType::Other,
106 }
107 }
108
109 pub fn to_builder(self) -> secrets_store_capnp::SecretType {
110 match self {
111 SecretType::Login => secrets_store_capnp::SecretType::Login,
112 SecretType::Licence => secrets_store_capnp::SecretType::Licence,
113 SecretType::Note => secrets_store_capnp::SecretType::Note,
114 SecretType::Wlan => secrets_store_capnp::SecretType::Wlan,
115 SecretType::Password => secrets_store_capnp::SecretType::Password,
116 SecretType::Other => secrets_store_capnp::SecretType::Other,
117 }
118 }
119}
120
121impl fmt::Display for SecretType {
122 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
123 match self {
124 SecretType::Login => write!(f, "Login"),
125 SecretType::Note => write!(f, "Note"),
126 SecretType::Licence => write!(f, "Licence"),
127 SecretType::Wlan => write!(f, "WLAN"),
128 SecretType::Password => write!(f, "Password"),
129 SecretType::Other => write!(f, "Other"),
130 }
131 }
132}
133
134#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq, Zeroize)]
141#[cfg_attr(feature = "with_specta", derive(specta::Type))]
142#[zeroize(drop)]
143pub struct SecretListFilter {
144 pub url: Option<String>,
145 pub tag: Option<String>,
146 #[serde(rename = "type")]
147 pub secret_type: Option<SecretType>,
148 pub name: Option<String>,
149 #[serde(default)]
150 pub deleted: bool,
151}
152
153#[derive(Clone, Debug, Serialize, Deserialize, Eq, Zeroize)]
162#[cfg_attr(feature = "with_specta", derive(specta::Type))]
163#[zeroize(drop)]
164pub struct SecretEntry {
165 pub id: String,
166 pub name: String,
167 #[serde(rename = "type")]
168 pub secret_type: SecretType,
169 pub tags: Vec<String>,
170 pub urls: Vec<String>,
171 pub timestamp: ZeroizeDateTime,
172 pub deleted: bool,
173}
174
175impl SecretEntry {
176 pub fn from_reader(reader: secret_entry::Reader) -> capnp::Result<Self> {
177 Ok(SecretEntry {
178 id: reader.get_id()?.to_string()?,
179 timestamp: Utc.timestamp_millis_opt(reader.get_timestamp()).unwrap().into(),
180 name: reader.get_name()?.to_string()?,
181 secret_type: SecretType::from_reader(reader.get_type()?),
182 tags: reader
183 .get_tags()?
184 .into_iter()
185 .map(|t| t.and_then(|t| Ok(t.to_string()?)))
186 .collect::<capnp::Result<Vec<String>>>()?,
187 urls: reader
188 .get_urls()?
189 .into_iter()
190 .map(|u| u.and_then(|u| Ok(u.to_string()?)))
191 .collect::<capnp::Result<Vec<String>>>()?,
192 deleted: reader.get_deleted(),
193 })
194 }
195
196 pub fn to_builder(&self, mut builder: secret_entry::Builder) {
197 builder.set_id(&self.id);
198 builder.set_timestamp(self.timestamp.timestamp_millis());
199 builder.set_name(&self.name);
200 builder.set_type(self.secret_type.to_builder());
201 let mut tags = builder.reborrow().init_tags(self.tags.len() as u32);
202 for (idx, tag) in self.tags.iter().enumerate() {
203 tags.set(idx as u32, tag)
204 }
205 let mut urls = builder.reborrow().init_urls(self.urls.len() as u32);
206 for (idx, url) in self.urls.iter().enumerate() {
207 urls.set(idx as u32, url)
208 }
209 builder.set_deleted(self.deleted);
210 }
211}
212
213impl Ord for SecretEntry {
214 fn cmp(&self, other: &Self) -> Ordering {
215 match self.name.cmp(&other.name) {
216 Ordering::Equal => self.id.cmp(&other.id),
217 ord => ord,
218 }
219 }
220}
221
222impl PartialOrd for SecretEntry {
223 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
224 Some(self.cmp(other))
225 }
226}
227
228impl PartialEq for SecretEntry {
229 fn eq(&self, other: &Self) -> bool {
230 self.id.eq(&other.id)
231 }
232}
233
234#[derive(Clone, Debug, Serialize, Deserialize, Eq, Zeroize)]
240#[cfg_attr(feature = "with_specta", derive(specta::Type))]
241#[zeroize(drop)]
242pub struct SecretEntryMatch {
243 pub entry: SecretEntry,
244 pub name_score: isize,
246 pub name_highlights: Vec<usize>,
248 pub url_highlights: Vec<usize>,
250 pub tags_highlights: Vec<usize>,
252}
253
254impl Ord for SecretEntryMatch {
255 fn cmp(&self, other: &Self) -> Ordering {
256 match other.name_score.cmp(&self.name_score) {
257 Ordering::Equal => self.entry.cmp(&other.entry),
258 ord => ord,
259 }
260 }
261}
262
263impl PartialOrd for SecretEntryMatch {
264 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
265 Some(self.cmp(other))
266 }
267}
268
269impl PartialEq for SecretEntryMatch {
270 fn eq(&self, other: &Self) -> bool {
271 self.entry.eq(&other.entry)
272 }
273}
274
275#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq, Zeroize)]
279#[cfg_attr(feature = "with_specta", derive(specta::Type))]
280#[zeroize(drop)]
281pub struct SecretList {
282 pub all_tags: Vec<String>,
283 pub entries: Vec<SecretEntryMatch>,
284}
285
286#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
287#[cfg_attr(feature = "with_specta", derive(specta::Type))]
288#[serde(transparent)]
289pub struct SecretProperties(BTreeMap<String, String>);
290
291impl SecretProperties {
292 pub fn new(properties: BTreeMap<String, String>) -> Self {
293 SecretProperties(properties)
294 }
295
296 pub fn has_non_empty(&self, name: &str) -> bool {
297 matches!(self.0.get(name), Some(value) if !value.is_empty())
298 }
299
300 pub fn get(&self, name: &str) -> Option<&String> {
301 self.0.get(name)
302 }
303
304 pub fn len(&self) -> usize {
305 self.0.len()
306 }
307
308 pub fn is_empty(&self) -> bool {
309 self.0.is_empty()
310 }
311
312 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
313 self.0.iter().map(|(k, v)| (k.as_str(), v.as_str()))
314 }
315}
316
317impl Drop for SecretProperties {
318 fn drop(&mut self) {
319 self.zeroize()
320 }
321}
322
323impl Zeroize for SecretProperties {
324 fn zeroize(&mut self) {
325 self.0.values_mut().for_each(Zeroize::zeroize);
326 }
327}
328
329#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
336#[cfg_attr(feature = "with_specta", derive(specta::Type))]
337#[zeroize(drop)]
338pub struct SecretAttachment {
339 name: String,
340 mime_type: String,
341 content: Vec<u8>,
342}
343
344#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
351#[cfg_attr(feature = "with_specta", derive(specta::Type))]
352#[zeroize(drop)]
353pub struct SecretVersion {
354 pub secret_id: String,
362 #[serde(rename = "type")]
364 pub secret_type: SecretType,
365 pub timestamp: ZeroizeDateTime,
368 pub name: String,
370 #[serde(default)]
372 pub tags: Vec<String>,
373 #[serde(default)]
376 pub urls: Vec<String>,
377 pub properties: SecretProperties,
380 #[serde(default)]
382 pub attachments: Vec<SecretAttachment>,
383 #[serde(default)]
389 pub deleted: bool,
390 #[serde(default)]
394 pub recipients: Vec<String>,
395}
396
397impl SecretVersion {
398 pub fn to_entry_builder(&self, mut builder: secret_entry::Builder) -> capnp::Result<()> {
399 builder.set_id(&self.secret_id);
400 builder.set_timestamp(self.timestamp.timestamp_millis());
401 builder.set_name(&self.name);
402 builder.set_type(self.secret_type.to_builder());
403 set_text_list(builder.reborrow().init_tags(self.tags.len() as u32), &self.tags)?;
404 set_text_list(builder.reborrow().init_urls(self.urls.len() as u32), &self.urls)?;
405 builder.set_deleted(self.deleted);
406 Ok(())
407 }
408}
409
410#[derive(Clone, Debug, Serialize, Deserialize, Zeroize)]
411#[cfg_attr(feature = "with_specta", derive(specta::Type))]
412#[zeroize(drop)]
413pub struct PasswordEstimate {
414 pub password: String,
415 pub inputs: Vec<String>,
416}
417
418#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Zeroize)]
419#[cfg_attr(feature = "with_specta", derive(specta::Type))]
420#[zeroize(drop)]
421pub struct PasswordStrength {
422 pub entropy: f64,
423 pub crack_time: f64,
424 pub crack_time_display: String,
425 pub score: u8,
426}
427
428#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
429#[cfg_attr(feature = "with_specta", derive(specta::Type))]
430#[zeroize(drop)]
431pub struct SecretVersionRef {
432 pub block_id: String,
433 pub timestamp: ZeroizeDateTime,
434}
435
436impl SecretVersionRef {
437 pub fn from_reader(reader: secret_version_ref::Reader) -> capnp::Result<Self> {
438 Ok(SecretVersionRef {
439 block_id: reader.get_block_id()?.to_string()?,
440 timestamp: Utc.timestamp_millis_opt(reader.get_timestamp()).unwrap().into(),
441 })
442 }
443
444 pub fn to_builder(&self, mut builder: secret_version_ref::Builder) {
445 builder.set_block_id(&self.block_id);
446 builder.set_timestamp(self.timestamp.timestamp_millis());
447 }
448}
449
450impl std::fmt::Display for SecretVersionRef {
451 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
452 write!(f, "{}", self.timestamp.format("%Y-%m-%d %H:%M:%S"))
453 }
454}
455
456#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
460#[cfg_attr(feature = "with_specta", derive(specta::Type))]
461pub struct Secret {
462 pub id: String,
463 #[serde(rename = "type")]
464 pub secret_type: SecretType,
465 pub current: SecretVersion,
466 pub current_block_id: String,
467 pub versions: Vec<SecretVersionRef>,
468 pub password_strengths: HashMap<String, PasswordStrength>,
469}
470
471impl Zeroize for Secret {
472 fn zeroize(&mut self) {
473 self.id.zeroize();
474 self.secret_type.zeroize();
475 self.current.zeroize();
476 self.current_block_id.zeroize();
477 self.versions.zeroize();
478 self.password_strengths.values_mut().for_each(Zeroize::zeroize);
479 }
480}
481
482impl Drop for Secret {
483 fn drop(&mut self) {
484 self.zeroize();
485 }
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
489#[cfg_attr(feature = "with_specta", derive(specta::Type))]
490#[zeroize(drop)]
491pub struct PasswordGeneratorCharsParam {
492 pub num_chars: u8,
493 pub include_uppers: bool,
494 pub include_numbers: bool,
495 pub include_symbols: bool,
496 pub require_upper: bool,
497 pub require_number: bool,
498 pub require_symbol: bool,
499 pub exclude_similar: bool,
500 pub exclude_ambiguous: bool,
501}
502
503#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
504#[cfg_attr(feature = "with_specta", derive(specta::Type))]
505#[zeroize(drop)]
506pub struct PasswordGeneratorWordsParam {
507 pub num_words: u8,
508 pub delim: char,
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
512#[cfg_attr(feature = "with_specta", derive(specta::Type))]
513#[serde(rename_all = "lowercase")]
514#[zeroize(drop)]
515pub enum PasswordGeneratorParam {
516 Chars(PasswordGeneratorCharsParam),
517 Words(PasswordGeneratorWordsParam),
518}
519
520pub fn set_text_list<I, S>(mut text_list: text_list::Builder, texts: I) -> capnp::Result<()>
521where
522 I: IntoIterator<Item = S>,
523 S: AsRef<str>,
524{
525 for (idx, text) in texts.into_iter().enumerate() {
526 text_list.set(idx as u32, text.as_ref());
527 }
528 Ok(())
529}
530
531#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
532#[cfg_attr(feature = "with_specta", derive(specta::Type))]
533#[zeroize(drop)]
534pub struct ClipboardProviding {
535 pub store_name: String,
536 pub block_id: String,
537 pub secret_name: String,
538 pub property: String,
539}