1use std::{cell::RefCell, collections::HashMap, error::Error, rc::Rc};
2
3use aes_gcm::{aead::OsRng, Aes256Gcm, Key};
4use argon2::password_hash::SaltString;
5use inquire::Confirm;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 action::Record,
10 cli::CLICommands,
11 command::{Commands, Instructions, Interaction},
12 file::{BackupFile, ProjectFile, RecordFile, SchemaFile, VaultFile},
13 output::Output,
14 schema::Schema,
15 secure::{Encrypted, SecureData},
16 vault::Vault,
17};
18
19#[derive(Debug, Serialize, Deserialize)]
20pub struct PasswordEncrypted<Data> {
21 data: Encrypted<Data>,
22 salt: String,
23}
24
25impl<Data> SecureData for PasswordEncrypted<Data> {
26 type Item = Data;
27 fn salt(&self) -> &str {
28 &self.salt
29 }
30 fn data(&self) -> &Encrypted<Self::Item> {
31 &self.data
32 }
33}
34
35pub type VaultEncrypted = PasswordEncrypted<Vault>;
36
37impl VaultEncrypted {
38 fn new(password: String) -> Result<Self, Box<dyn Error>> {
39 let salt = SaltString::generate(&mut OsRng).to_string();
40 let key = Self::get_key(&salt, password);
41 Encrypted::encrypt(&Vault::new(), key).map(|vault| Self { data: vault, salt })
42 }
43
44 fn update(&mut self, data: &Vault, key: Key<Aes256Gcm>) -> Result<(), Box<dyn Error>> {
45 let updated = Encrypted::encrypt(data, key)?;
46 self.data = updated;
47 Ok(())
48 }
49}
50
51pub type RecordEncrypted = PasswordEncrypted<Record>;
52
53impl RecordEncrypted {
54 fn new(password: String) -> Result<Self, Box<dyn Error>> {
55 let salt = SaltString::generate(&mut OsRng).to_string();
56 let key = Self::get_key(&salt, password);
57 Encrypted::encrypt(&Record::new(), key).map(|vault| Self { data: vault, salt })
58 }
59
60 fn update(&mut self, data: &Record, key: Key<Aes256Gcm>) -> Result<(), Box<dyn Error>> {
61 let updated = Encrypted::encrypt(data, key)?;
62 self.data = updated;
63 Ok(())
64 }
65}
66
67pub struct VaultInterface {
68 vault_file: VaultFile,
69 schema_file: Rc<RefCell<SchemaFile>>,
70 vault: VaultEncrypted,
71 record: RecordEncrypted,
72 key: Key<Aes256Gcm>,
73}
74
75impl VaultInterface {
76 pub fn create(
77 vault_file: VaultFile,
78 schema_file: Rc<RefCell<SchemaFile>>,
79 password: String,
80 ) -> VaultInterface {
81 let vault = VaultEncrypted::new(password.clone()).unwrap();
82 let record = RecordEncrypted::new(password.clone()).unwrap();
83 let key = vault.key(password);
84 Self {
85 vault_file,
86 schema_file,
87 vault,
88 record,
89 key,
90 }
91 }
92
93 pub fn open(
94 vault_file: VaultFile,
95 schema_file: Rc<RefCell<SchemaFile>>,
96 password: String,
97 ) -> Result<VaultInterface, Box<dyn Error>> {
98 let vault: VaultEncrypted = vault_file.read()?.deserialize();
99 let record = RecordEncrypted::new(password.clone()).unwrap();
100 let key = vault.key(password);
101 Ok(Self {
102 vault_file,
103 schema_file,
104 vault,
105 record,
106 key,
107 })
108 }
109
110 fn save(&mut self) -> Result<(), Box<dyn Error>> {
112 self.vault_file.write(&self.vault)?;
113 self.schema_file
114 .borrow_mut()
115 .write(&self.vault.decrypt(self.key)?.deserialize().schema())?;
116 Ok(())
117 }
118
119 fn transact(
120 transaction: Commands,
121 schema_file: Rc<RefCell<SchemaFile>>,
122 ) -> Result<Output, Box<dyn Error>> {
123 let mut interface = Self::get_interface(schema_file)?;
124 let mut vault = interface.vault.decrypt(interface.key)?.deserialize();
125 let (reads, record) = vault.transaction(transaction);
126 interface.record.update(&record, interface.key)?;
127
128 let mut record_file = RecordFile::default();
129 record_file.write(&interface.record)?;
130 vault.apply_record(record);
131 interface.vault.update(&vault, interface.key)?;
132 interface.save()?;
133 record_file.delete()?;
134 Ok(reads.into())
135 }
136
137 fn interact(
138 interaction: Interaction,
139 schema_file: Rc<RefCell<SchemaFile>>,
140 ) -> Result<Output, Box<dyn Error>> {
141 match interaction {
142 Interaction::List => {
143 let schema = schema_file.borrow_mut().read()?.deserialize();
144 Ok(Output::List(schema.all_info()))
145 }
146 Interaction::Backup => {
147 let interface = Self::get_interface(schema_file)?;
150 let backup = interface.backup()?;
151
152 Ok(Output::Backup(backup.path()))
153 }
154 Interaction::Rotate => {
155 let mut interface = Self::get_interface(schema_file)?;
157 let backup = interface.backup()?;
158 let vault = interface.vault.decrypt(interface.key)?.deserialize();
159 let new_password = inquire::Password::new("New vault password: ")
160 .with_display_toggle_enabled()
161 .with_display_mode(inquire::PasswordDisplayMode::Masked)
162 .prompt()?;
163 let mut new_vault = VaultEncrypted::new(new_password.clone())?;
164 new_vault.update(&vault, new_vault.key(new_password.clone()))?;
165 interface.vault = new_vault;
166 interface.key = interface.vault.key(new_password);
167 interface.save()?;
168 Ok(Output::Backup(backup.path()))
169 }
170 }
171 }
172
173 fn backup(&self) -> Result<BackupFile, Box<dyn Error>> {
174 let vault = self.vault.decrypt(self.key)?.deserialize();
175 let mut backup_file = BackupFile::default();
176 let backup = VaultEncrypted {
177 salt: self.vault.salt.clone(),
178 data: Encrypted::encrypt(&vault, self.key)?,
179 };
180 backup_file.write(&backup)?;
181 Ok(backup_file)
182 }
183
184 fn check_unfinished(&mut self) -> Result<(), Box<dyn Error>> {
185 if let Some(file) = RecordFile::last() {
186 let ans = Confirm::new("There appears to be an unapplied update, apply it? Will clear out old record if not applied.")
187 .with_default(true)
188 .with_help_message("Likely occurred due to some failure in saving off the updates from the previous interaction.")
189 .prompt();
190
191 match ans {
192 Ok(true) => self.apply_unfinished(file)?,
193 Ok(false) => file.delete()?,
194 Err(_) => (),
195 }
196 }
197
198 Ok(())
199 }
200
201 fn apply_unfinished(&mut self, record_file: RecordFile) -> Result<(), Box<dyn Error>> {
202 let mut vault = self.vault.decrypt(self.key)?.deserialize();
203 let record = record_file
204 .read()?
205 .deserialize()
206 .decrypt(self.key)?
207 .deserialize();
208 vault.apply_record(record);
209 self.vault.update(&vault, self.key)?;
210 self.save()?;
211 record_file.delete()?;
212 Ok(())
213 }
214
215 fn get_interface(
216 schema_file: Rc<RefCell<SchemaFile>>,
217 ) -> Result<VaultInterface, Box<dyn Error>> {
218 let vault_file = VaultFile::default();
219 let mut interface = if vault_file.check() {
220 let password = inquire::Password::new("Vault password: ")
221 .without_confirmation()
222 .with_display_toggle_enabled()
223 .with_display_mode(inquire::PasswordDisplayMode::Masked)
224 .prompt()?;
225 let res = Self::open(vault_file, schema_file.clone(), password)?;
226
227 Ok::<VaultInterface, Box<dyn Error>>(res)
228 } else {
229 let new_password = inquire::Password::new("Vault doesn't exist, create password: ")
230 .with_display_toggle_enabled()
231 .with_display_mode(inquire::PasswordDisplayMode::Masked)
232 .prompt()?;
233 let mut interface = Self::create(vault_file, schema_file.clone(), new_password);
234 interface.save()?;
235 Ok(interface)
236 }?;
237
238 interface.check_unfinished()?;
239 Ok(interface)
240 }
241
242 pub fn interaction(transaction: CLICommands) -> Result<Output, Box<dyn Error>> {
243 let schema_file: Rc<RefCell<SchemaFile>> = Rc::new(RefCell::new(SchemaFile::default()));
244 let schema = if schema_file.borrow().check() {
245 schema_file.borrow_mut().read()?.deserialize()
246 } else {
247 let s = Schema::new(HashMap::new());
250 schema_file.borrow_mut().write(&s)?;
251 s
252 };
253
254 let commands = Instructions::from_commands(transaction, schema)?;
255 match commands {
256 Instructions::Interaction(i) => Self::interact(i, schema_file),
257 Instructions::Commands(c) => Self::transact(c, schema_file),
258 }
259 }
260}