1use crate::{Error, Result};
2use async_sqlite::rusqlite::{
3 CachedStatement, Connection, Error as SqlError, OptionalExtension, Row,
4 Transaction,
5};
6use async_sqlite::Client;
7use sos_core::crypto::Seed;
8use sos_core::{
9 commit::CommitHash, crypto::AeadPack, decode, encode, SecretId,
10 UtcDateTime, VaultCommit, VaultEntry, VaultFlags, VaultId,
11};
12use sos_vault::{SharedAccess, Summary, Vault};
13use sql_query_builder as sql;
14use std::collections::HashMap;
15use std::ops::Deref;
16use std::result::Result as StdResult;
17
18fn folder_select_columns(sql: sql::Select) -> sql::Select {
19 sql.select(
20 r#"
21 folders.folder_id,
22 folders.created_at,
23 folders.modified_at,
24 folders.identifier,
25 folders.name,
26 folders.salt,
27 folders.meta,
28 folders.seed,
29 folders.version,
30 folders.cipher,
31 folders.kdf,
32 folders.flags,
33 folders.shared_access
34 "#,
35 )
36}
37
38fn secret_select_columns(sql: sql::Select) -> sql::Select {
39 sql.select(
40 r#"
41 secret_id,
42 created_at,
43 modified_at,
44 identifier,
45 commit_hash,
46 meta,
47 secret
48 "#,
49 )
50}
51
52#[doc(hidden)]
54#[derive(Debug, Default)]
55pub struct FolderRow {
56 pub row_id: i64,
57 created_at: String,
58 modified_at: String,
59 identifier: String,
60 name: String,
61 salt: Option<String>,
62 meta: Option<Vec<u8>>,
63 seed: Option<Vec<u8>>,
64 version: i64,
65 cipher: String,
66 kdf: String,
67 flags: Vec<u8>,
68 shared_access: Option<Vec<u8>>,
69}
70
71impl FolderRow {
72 pub async fn new_insert(vault: &Vault) -> Result<Self> {
74 let meta = if let Some(meta) = vault.header().meta() {
75 Some(encode(meta).await?)
76 } else {
77 None
78 };
79 let salt = vault.salt().cloned();
80 let seed = vault.seed().map(|s| s.as_ref().to_vec());
81 Self::new_insert_parts(vault.summary(), salt, meta, seed, None)
82 }
83
84 pub fn new_insert_parts(
86 summary: &Summary,
87 salt: Option<String>,
88 meta: Option<Vec<u8>>,
89 seed: Option<Vec<u8>>,
90 shared_access: Option<Vec<u8>>,
91 ) -> Result<Self> {
92 Ok(Self {
93 created_at: UtcDateTime::default().to_rfc3339()?,
94 modified_at: UtcDateTime::default().to_rfc3339()?,
95 identifier: summary.id().to_string(),
96 name: summary.name().to_string(),
97 salt,
98 meta,
99 seed,
100 version: *summary.version() as i64,
101 cipher: summary.cipher().to_string(),
102 kdf: summary.kdf().to_string(),
103 flags: summary.flags().bits().to_le_bytes().to_vec(),
104 shared_access,
105 ..Default::default()
106 })
107 }
108
109 pub async fn new_update(vault: &Vault) -> Result<Self> {
111 let summary = vault.summary();
112 let meta = if let Some(meta) = vault.header().meta() {
113 Some(encode(meta).await?)
114 } else {
115 None
116 };
117 let salt = vault.salt().cloned();
118 let seed = vault.seed().map(|s| s.as_ref().to_vec());
119 Ok(Self {
120 modified_at: UtcDateTime::default().to_rfc3339()?,
121 identifier: summary.id().to_string(),
122 name: summary.name().to_string(),
123 salt,
124 meta,
125 seed,
126 version: *summary.version() as i64,
127 cipher: summary.cipher().to_string(),
128 kdf: summary.kdf().to_string(),
129 flags: summary.flags().bits().to_le_bytes().to_vec(),
130 ..Default::default()
131 })
132 }
133}
134
135impl<'a> TryFrom<&Row<'a>> for FolderRow {
136 type Error = SqlError;
137 fn try_from(row: &Row<'a>) -> StdResult<Self, Self::Error> {
138 Ok(FolderRow {
139 row_id: row.get(0)?,
140 created_at: row.get(1)?,
141 modified_at: row.get(2)?,
142 identifier: row.get(3)?,
143 name: row.get(4)?,
144 salt: row.get(5)?,
145 meta: row.get(6)?,
146 seed: row.get(7)?,
147 version: row.get(8)?,
148 cipher: row.get(9)?,
149 kdf: row.get(10)?,
150 flags: row.get(11)?,
151 shared_access: row.get(12)?,
152 })
153 }
154}
155
156#[derive(Debug, Clone)]
158pub struct FolderRecord {
159 pub row_id: i64,
161 pub created_at: UtcDateTime,
163 pub modified_at: UtcDateTime,
165 pub salt: Option<String>,
167 pub meta: Option<AeadPack>,
169 pub seed: Option<Seed>,
171 pub summary: Summary,
173 pub shared_access: Option<SharedAccess>,
175}
176
177impl FolderRecord {
178 pub async fn from_row(value: FolderRow) -> Result<Self> {
180 let created_at = UtcDateTime::parse_rfc3339(&value.created_at)?;
181 let modified_at = UtcDateTime::parse_rfc3339(&value.modified_at)?;
182 let folder_id: VaultId = value.identifier.parse()?;
183 let version: u16 = value.version.try_into()?;
184 let cipher = value.cipher.parse()?;
185 let kdf = value.kdf.parse()?;
186 let bytes: [u8; 8] = value.flags.as_slice().try_into()?;
187 let bits = u64::from_le_bytes(bytes);
188 let flags = VaultFlags::from_bits(bits)
189 .ok_or(sos_vault::Error::InvalidVaultFlags)?;
190
191 let salt = value.salt;
192
193 let meta = if let Some(meta) = &value.meta {
194 Some(decode(meta).await?)
195 } else {
196 None
197 };
198
199 let seed = if let Some(seed) = value.seed {
200 let seed: [u8; Seed::SIZE] = seed.as_slice().try_into()?;
201 Some(Seed(seed))
202 } else {
203 None
204 };
205
206 let summary =
207 Summary::new(version, folder_id, value.name, cipher, kdf, flags);
208
209 let shared_access = if let Some(shared_access) = &value.shared_access
210 {
211 Some(decode(shared_access).await?)
212 } else {
213 None
214 };
215
216 Ok(FolderRecord {
217 row_id: value.row_id,
218 created_at,
219 modified_at,
220 salt,
221 meta,
222 seed,
223 summary,
224 shared_access,
225 })
226 }
227
228 pub fn into_vault(&self) -> Result<Vault> {
230 let mut vault: Vault = self.summary.clone().into();
231 vault.header_mut().set_meta(self.meta.clone());
232 vault.header_mut().set_salt(self.salt.clone());
233 vault.header_mut().set_seed(self.seed);
234 if let Some(shared_access) = &self.shared_access {
235 vault.header_mut().set_shared_access(shared_access.clone());
236 }
237 Ok(vault)
238 }
239}
240
241#[doc(hidden)]
243#[derive(Debug, Default)]
244pub struct SecretRow {
245 pub row_id: i64,
246 created_at: String,
247 modified_at: String,
248 identifier: String,
249 commit: Vec<u8>,
250 meta: Vec<u8>,
251 secret: Vec<u8>,
252}
253
254impl SecretRow {
255 pub async fn new(
257 secret_id: &SecretId,
258 commit: &CommitHash,
259 entry: &VaultEntry,
260 ) -> Result<Self> {
261 let VaultEntry(meta, secret) = entry;
262 let meta = encode(meta).await?;
263 let secret = encode(secret).await?;
264 Ok(Self {
265 created_at: UtcDateTime::default().to_rfc3339()?,
266 modified_at: UtcDateTime::default().to_rfc3339()?,
267 identifier: secret_id.to_string(),
268 commit: commit.as_ref().to_vec(),
269 meta,
270 secret,
271 ..Default::default()
272 })
273 }
274
275 pub fn identifier(&self) -> &str {
277 &self.identifier
278 }
279
280 pub fn commit(&self) -> &[u8] {
282 &self.commit
283 }
284
285 pub fn meta_bytes(&self) -> &[u8] {
287 &self.meta
288 }
289
290 pub fn secret_bytes(&self) -> &[u8] {
292 &self.secret
293 }
294}
295
296impl<'a> TryFrom<&Row<'a>> for SecretRow {
297 type Error = SqlError;
298 fn try_from(row: &Row<'a>) -> StdResult<Self, Self::Error> {
299 Ok(SecretRow {
300 row_id: row.get(0)?,
301 created_at: row.get(1)?,
302 modified_at: row.get(2)?,
303 identifier: row.get(3)?,
304 commit: row.get(4)?,
305 meta: row.get(5)?,
306 secret: row.get(6)?,
307 })
308 }
309}
310
311#[doc(hidden)]
313#[derive(Debug)]
314pub struct SecretRecord {
315 pub row_id: i64,
316 pub created_at: UtcDateTime,
317 pub modified_at: UtcDateTime,
318 pub secret_id: VaultId,
319 pub commit: VaultCommit,
320}
321
322impl SecretRecord {
323 pub async fn from_row(value: SecretRow) -> Result<Self> {
325 let created_at = UtcDateTime::parse_rfc3339(&value.created_at)?;
326 let modified_at = UtcDateTime::parse_rfc3339(&value.modified_at)?;
327 let secret_id: SecretId = value.identifier.parse()?;
328 let commit_hash = CommitHash(value.commit.as_slice().try_into()?);
329 let meta: AeadPack = decode(&value.meta).await?;
330 let secret: AeadPack = decode(&value.secret).await?;
331 let commit = VaultCommit(commit_hash, VaultEntry(meta, secret));
332
333 Ok(SecretRecord {
334 row_id: value.row_id,
335 created_at,
336 modified_at,
337 secret_id,
338 commit,
339 })
340 }
341}
342
343pub struct FolderEntity<'conn, C>
345where
346 C: Deref<Target = Connection>,
347{
348 conn: &'conn C,
349}
350
351impl<'conn> FolderEntity<'conn, Box<Connection>> {
352 pub fn find_all_secrets_query() -> sql::Select {
354 secret_select_columns(sql::Select::new())
355 .from("folder_secrets")
356 .where_clause("folder_id=?1")
357 }
358
359 pub async fn compute_folder_vault(
361 client: &Client,
362 folder_id: &VaultId,
363 ) -> Result<Vault> {
364 let folder_id = *folder_id;
365
366 let (folder_row, secret_rows) = client
367 .conn_and_then(move |conn| {
368 let folder_entity = FolderEntity::new(&conn);
369 let folder_row = folder_entity.find_one(&folder_id)?;
370 let secret_rows =
371 folder_entity.load_secrets(folder_row.row_id)?;
372 Ok::<_, Error>((folder_row, secret_rows))
373 })
374 .await?;
375
376 let folder_record = FolderRecord::from_row(folder_row).await?;
377 let mut vault = folder_record.into_vault()?;
378 for row in secret_rows {
379 let record = SecretRecord::from_row(row).await?;
380 vault.insert_entry(record.secret_id, record.commit);
381 }
382 Ok(vault)
383 }
384}
385
386impl<'conn> FolderEntity<'conn, Transaction<'conn>> {
387 pub async fn upsert_folder_and_secrets(
393 client: &Client,
394 account_id: i64,
395 vault: &Vault,
396 ) -> Result<(i64, HashMap<SecretId, i64>)> {
397 let folder_id = *vault.id();
398
399 let meta = if let Some(meta) = vault.header().meta() {
400 Some(encode(meta).await?)
401 } else {
402 None
403 };
404 let salt = vault.salt().cloned();
405 let seed = vault.seed().map(|s| s.as_ref().to_vec());
406
407 let shared_access = if !vault.shared_access().is_empty() {
408 Some(encode(vault.shared_access()).await?)
409 } else {
410 None
411 };
412
413 let folder_row = FolderRow::new_insert_parts(
414 vault.summary(),
415 salt,
416 meta,
417 seed,
418 shared_access,
419 )?;
420
421 let mut secret_rows = Vec::new();
422 for (secret_id, commit) in vault.iter() {
423 let VaultCommit(commit, entry) = commit;
424 secret_rows.push(SecretRow::new(secret_id, commit, entry).await?);
425 }
426
427 client
428 .conn_mut_and_then(move |conn| {
429 let tx = conn.transaction()?;
430 let folder_entity = FolderEntity::new(&tx);
431
432 let folder_id = if let Some(row) =
433 folder_entity.find_optional(&folder_id)?
434 {
435 folder_entity.update_folder(&folder_id, &folder_row)?;
436 folder_entity.delete_all_secrets(row.row_id)?;
437 row.row_id
438 } else {
439 folder_entity.insert_folder(account_id, &folder_row)?
440 };
441
442 let secret_ids = folder_entity.insert_folder_secrets(
443 folder_id,
444 secret_rows.as_slice(),
445 )?;
446 tx.commit()?;
447 Ok::<_, Error>((folder_id, secret_ids))
448 })
449 .await
450 }
451
452 pub async fn replace_all_secrets(
454 client: Client,
455 folder_id: &VaultId,
456 vault: &Vault,
457 ) -> Result<()> {
458 let folder_id = *folder_id;
459 let mut insert_secrets = Vec::new();
460 for (secret_id, commit) in vault.iter() {
461 let VaultCommit(commit, entry) = commit;
462 insert_secrets
463 .push(SecretRow::new(secret_id, commit, entry).await?);
464 }
465
466 let folder_update_row = FolderRow::new_update(vault).await?;
467 client
468 .conn_mut(move |conn| {
469 let tx = conn.transaction()?;
470 let folder = FolderEntity::new(&tx);
471 let folder_row = folder.find_one(&folder_id)?;
472 folder.delete_all_secrets(folder_row.row_id)?;
473 for secret_row in insert_secrets {
474 folder.insert_secret_by_row_id(
475 folder_row.row_id,
476 &secret_row,
477 )?;
478 }
479 folder.update_folder(&folder_id, &folder_update_row)?;
480 tx.commit()?;
481 Ok(())
482 })
483 .await
484 .map_err(Error::from)?;
485 Ok(())
486 }
487}
488
489impl<'conn, C> FolderEntity<'conn, C>
490where
491 C: Deref<Target = Connection>,
492{
493 pub fn new(conn: &'conn C) -> Self {
495 Self { conn }
496 }
497
498 fn select_folder<'a>(
499 &'a self,
500 use_identifier: bool,
501 ) -> StdResult<CachedStatement<'a>, SqlError> {
502 let query = folder_select_columns(sql::Select::new()).from("folders");
503
504 let query = if use_identifier {
505 query.where_clause("identifier = ?1")
506 } else {
507 query.where_clause("folder_id = ?1")
508 };
509 self.conn.prepare_cached(&query.as_string())
510 }
511
512 pub fn find_one(
514 &self,
515 folder_id: &VaultId,
517 ) -> StdResult<FolderRow, SqlError> {
518 let mut stmt = self.select_folder(true)?;
519 stmt.query_row([folder_id.to_string()], |row| row.try_into())
520 }
521
522 pub fn find_optional(
524 &self,
525 folder_id: &VaultId,
527 ) -> StdResult<Option<FolderRow>, SqlError> {
528 let mut stmt = self.select_folder(true)?;
529 stmt.query_row([folder_id.to_string()], |row| {
530 let row: FolderRow = row.try_into()?;
531 Ok(row)
532 })
533 .optional()
534 }
535
536 pub fn find_by_row_id(
538 &self,
539 folder_id: i64,
540 ) -> StdResult<FolderRow, SqlError> {
541 let mut stmt = self.select_folder(false)?;
542 stmt.query_row([folder_id], |row| row.try_into())
543 }
544
545 pub fn find_login_folder(&self, account_id: i64) -> Result<FolderRow> {
547 self.find_login_folder_optional(account_id)?
548 .ok_or_else(|| Error::NoLoginFolder(account_id))
549 }
550
551 pub fn find_login_folder_optional(
553 &self,
554 account_id: i64,
555 ) -> StdResult<Option<FolderRow>, SqlError> {
556 let query = folder_select_columns(sql::Select::new())
557 .from("folders")
558 .left_join(
559 "account_login_folder login ON folders.folder_id = login.folder_id",
560 )
561 .where_clause("folders.account_id=?1")
562 .where_and("login.account_id=?1");
563
564 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
565 stmt.query_row([account_id], |row| row.try_into())
566 .optional()
567 }
568
569 pub fn find_device_folder(
571 &self,
572 account_id: i64,
573 ) -> StdResult<Option<FolderRow>, SqlError> {
574 let query = folder_select_columns(sql::Select::new())
575 .from("folders")
576 .left_join(
577 "account_device_folder device ON folders.folder_id = device.folder_id",
578 )
579 .where_clause("folders.account_id=?1")
580 .where_and("device.account_id=?1");
581
582 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
583 stmt.query_row([account_id], |row| row.try_into())
584 .optional()
585 }
586
587 pub fn list_user_folders(
591 &self,
592 account_id: i64,
593 ) -> Result<Vec<FolderRow>> {
594 let query = folder_select_columns(sql::Select::new())
595 .from("folders")
596 .left_join(
597 "account_login_folder login ON folders.folder_id = login.folder_id",
598 )
599 .left_join(
600 "account_device_folder device ON folders.folder_id = device.folder_id",
601 )
602 .left_join(
603 "account_shared_folder shared ON folders.folder_id = shared.folder_id",
604 )
605 .where_clause("(folders.account_id=?1 OR shared.account_id=?1)")
606 .where_and("login.folder_id IS NULL")
607 .where_and("device.folder_id IS NULL");
608
609 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
610
611 fn convert_row(row: &Row<'_>) -> Result<FolderRow> {
612 Ok(row.try_into()?)
613 }
614
615 let rows = stmt.query_and_then([account_id], convert_row)?;
616 let mut folders = Vec::new();
617 for row in rows {
618 folders.push(row?);
619 }
620 Ok(folders)
621 }
622
623 pub fn update_name(&self, folder_id: &VaultId, name: &str) -> Result<()> {
625 let modified_at = UtcDateTime::default().to_rfc3339()?;
626 let query = sql::Update::new()
627 .update("folders")
628 .set("name = ?1, modified_at = ?2")
629 .where_clause("identifier = ?3");
630 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
631 stmt.execute((name, modified_at, folder_id.to_string()))?;
632 Ok(())
633 }
634
635 pub fn update_flags(
637 &self,
638 folder_id: &VaultId,
639 flags: &VaultFlags,
640 ) -> Result<()> {
641 let flags = flags.bits().to_le_bytes();
642 let modified_at = UtcDateTime::default().to_rfc3339()?;
643 let query = sql::Update::new()
644 .update("folders")
645 .set("flags = ?1, modified_at = ?2")
646 .where_clause("identifier = ?3");
647 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
648 stmt.execute((flags, modified_at, folder_id.to_string()))?;
649 Ok(())
650 }
651
652 pub fn update_meta(
654 &self,
655 folder_id: &VaultId,
656 meta: &[u8],
657 ) -> Result<()> {
658 let modified_at = UtcDateTime::default().to_rfc3339()?;
659 let query = sql::Update::new()
660 .update("folders")
661 .set("meta = ?1, modified_at = ?2")
662 .where_clause("identifier = ?3");
663 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
664 stmt.execute((meta, modified_at, folder_id.to_string()))?;
665 Ok(())
666 }
667
668 pub fn insert_folder(
670 &self,
671 account_id: i64,
672 folder_row: &FolderRow,
673 ) -> StdResult<i64, SqlError> {
674 let query = sql::Insert::new()
675 .insert_into(
676 r#"
677 folders
678 (
679 account_id,
680 created_at,
681 modified_at,
682 identifier,
683 name,
684 salt,
685 meta,
686 seed,
687 version,
688 cipher,
689 kdf,
690 flags,
691 shared_access
692 )
693 "#,
694 )
695 .values(
696 "(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
697 );
698
699 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
700 stmt.execute((
701 &account_id,
702 &folder_row.created_at,
703 &folder_row.modified_at,
704 &folder_row.identifier,
705 &folder_row.name,
706 &folder_row.salt,
707 &folder_row.meta,
708 &folder_row.seed,
709 &folder_row.version,
710 &folder_row.cipher,
711 &folder_row.kdf,
712 &folder_row.flags,
713 &folder_row.shared_access,
714 ))?;
715
716 Ok(self.conn.last_insert_rowid())
717 }
718
719 pub fn update_folder(
721 &self,
722 folder_id: &VaultId,
723 folder_row: &FolderRow,
724 ) -> StdResult<(), SqlError> {
725 let query = sql::Update::new()
726 .update("folders")
727 .set(
728 r#"
729 modified_at = ?1,
730 identifier = ?2,
731 name = ?3,
732 salt = ?4,
733 meta = ?5,
734 seed = ?6,
735 version = ?7,
736 cipher = ?8,
737 kdf = ?9,
738 flags = ?10,
739 shared_access = ?11
740 "#,
741 )
742 .where_clause("identifier=?12");
743 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
744 stmt.execute((
745 &folder_row.modified_at,
746 &folder_row.identifier,
747 &folder_row.name,
748 &folder_row.salt,
749 &folder_row.meta,
750 &folder_row.seed,
751 &folder_row.version,
752 &folder_row.cipher,
753 &folder_row.kdf,
754 &folder_row.flags,
755 &folder_row.shared_access,
756 folder_id.to_string(),
757 ))?;
758
759 Ok(())
760 }
761
762 pub fn insert_folder_secrets(
764 &self,
765 folder_id: i64,
766 rows: &[SecretRow],
767 ) -> Result<HashMap<SecretId, i64>> {
768 let mut secret_ids = HashMap::new();
769 for secret_row in rows {
770 let identifier: SecretId = secret_row.identifier.parse()?;
771 let secret_id =
772 self.insert_secret_by_row_id(folder_id, secret_row)?;
773 secret_ids.insert(identifier, secret_id);
774 }
775 Ok(secret_ids)
776 }
777
778 pub fn insert_secret(
780 &self,
781 folder_id: &VaultId,
782 secret_row: &SecretRow,
783 ) -> StdResult<i64, SqlError> {
784 let row = self.find_one(folder_id)?;
785 self.insert_secret_by_row_id(row.row_id, secret_row)
786 }
787
788 pub fn insert_secret_by_row_id(
790 &self,
791 folder_id: i64,
792 secret_row: &SecretRow,
793 ) -> StdResult<i64, SqlError> {
794 let query = sql::Insert::new()
798 .insert_into("folder_secrets (folder_id, identifier, commit_hash, meta, secret, created_at, modified_at)")
799 .values("(?1, ?2, ?3, ?4, ?5, ?6, ?7)")
800 .on_conflict(
801 r#"
802 (identifier)
803 DO UPDATE SET
804 folder_id=excluded.folder_id,
805 commit_hash=excluded.commit_hash,
806 meta=excluded.meta,
807 secret=excluded.secret,
808 modified_at=excluded.modified_at
809 "#);
810 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
811 stmt.execute((
812 &folder_id,
813 &secret_row.identifier,
814 &secret_row.commit,
815 &secret_row.meta,
816 &secret_row.secret,
817 &secret_row.created_at,
818 &secret_row.modified_at,
819 ))?;
820 Ok(self.conn.last_insert_rowid())
821 }
822
823 pub fn find_secret(
825 &self,
826 folder_id: &VaultId,
827 secret_id: &SecretId,
828 ) -> StdResult<Option<SecretRow>, SqlError> {
829 let row = self.find_one(folder_id)?;
830 let query = secret_select_columns(sql::Select::new())
831 .from("folder_secrets")
832 .where_clause("folder_id=?1")
833 .where_and("identifier=?2");
834
835 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
836 stmt.query_row((row.row_id, secret_id.to_string()), |row| {
837 let row: SecretRow = row.try_into()?;
838 Ok(row)
839 })
840 .optional()
841 }
842
843 pub fn update_secret(
845 &self,
846 folder_id: &VaultId,
847 secret_row: &SecretRow,
848 ) -> Result<bool> {
849 let modified_at = UtcDateTime::default().to_rfc3339()?;
850 let row = self.find_one(folder_id)?;
851 let query = sql::Update::new()
852 .update("folder_secrets")
853 .set(
854 r#"
855
856 modified_at=?1,
857 commit_hash=?2,
858 meta=?3,
859 secret=?4
860 "#,
861 )
862 .where_clause("folder_id=?5")
863 .where_and("identifier = ?6");
864
865 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
866 let affected_rows = stmt.execute((
867 modified_at,
868 &secret_row.commit,
869 &secret_row.meta,
870 &secret_row.secret,
871 row.row_id,
872 &secret_row.identifier,
873 ))?;
874 Ok(affected_rows > 0)
875 }
876
877 pub fn load_secrets(&self, folder_row_id: i64) -> Result<Vec<SecretRow>> {
879 let query = secret_select_columns(sql::Select::new())
880 .from("folder_secrets")
881 .where_clause("folder_id=?1");
882 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
883
884 fn convert_row(row: &Row<'_>) -> Result<SecretRow> {
885 Ok(row.try_into()?)
886 }
887
888 let rows = stmt.query_and_then([folder_row_id], convert_row)?;
889 let mut secrets = Vec::new();
890 for row in rows {
891 secrets.push(row?);
892 }
893 Ok(secrets)
894 }
895
896 pub fn list_secret_ids(
898 &self,
899 folder_id: &VaultId,
900 ) -> Result<Vec<SecretId>> {
901 let folder = self.find_one(folder_id)?;
902 let query = sql::Select::new()
903 .select("identifier")
904 .from("folder_secrets")
905 .where_clause("folder_id=?1");
906 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
907
908 fn convert_row(row: &Row<'_>) -> Result<SecretId> {
909 let id: String = row.get(0)?;
910 Ok(id.parse()?)
911 }
912
913 let rows = stmt.query_and_then([folder.row_id], convert_row)?;
914 let mut secrets = Vec::new();
915 for row in rows {
916 secrets.push(row?);
917 }
918 Ok(secrets)
919 }
920
921 pub fn delete_folder(
923 &self,
924 folder_id: &VaultId,
925 ) -> StdResult<bool, SqlError> {
926 let row = self.find_one(folder_id)?;
927 let query = sql::Delete::new()
928 .delete_from("folders")
929 .where_clause("folder_id = ?1");
930 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
931 let affected_rows = stmt.execute([row.row_id])?;
932 Ok(affected_rows > 0)
933 }
934
935 pub fn delete_secret(
937 &self,
938 folder_id: &VaultId,
939 secret_id: &SecretId,
940 ) -> StdResult<bool, SqlError> {
941 let row = self.find_one(folder_id)?;
942 let query = sql::Delete::new()
943 .delete_from("folder_secrets")
944 .where_clause("folder_id = ?1")
945 .where_and("identifier = ?2");
946 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
947 let affected_rows =
948 stmt.execute((row.row_id, secret_id.to_string()))?;
949 Ok(affected_rows > 0)
950 }
951
952 fn delete_all_secrets(
954 &self,
955 folder_id: i64,
956 ) -> StdResult<usize, SqlError> {
957 let query = sql::Delete::new()
958 .delete_from("folder_secrets")
959 .where_clause("folder_id = ?1");
960 let mut stmt = self.conn.prepare_cached(&query.as_string())?;
961 stmt.execute([folder_id])
962 }
963}