Skip to main content

fur_cli/security/
io.rs

1use std::fs;
2use std::io::{self, Write};
3use std::path::Path;
4
5use crate::security::crypto;
6use crate::security::state;
7
8pub fn encrypt_file(path: &Path, password: &str) {
9    if let Ok(bytes) = fs::read(path) {
10        if let Ok(enc) = crypto::encrypt(&bytes, password) {
11            let _ = fs::write(path, enc);
12        }
13    }
14}
15
16pub fn decrypt_file(path: &Path, password: &str) {
17    if let Ok(bytes) = fs::read(path) {
18        if let Ok(dec) = crypto::decrypt(&bytes, password) {
19            let _ = fs::write(path, dec);
20        }
21    }
22}
23
24pub fn read_text_file(path: &Path) -> Option<String> {
25    if state::is_locked() {
26        eprintln!("🔒 Project locked. Run `fur unlock`.");
27        return None;
28    }
29
30    fs::read_to_string(path).ok()
31}
32
33/// Read a password without echoing it.
34///
35/// viceroy: the old `read_visible_password` printed the passphrase in clear
36/// text and left it in scrollback and terminal history. Hiding is now the
37/// default rather than something `--hide` opts into.
38pub fn read_password(prompt: &str) -> String {
39    print!("{}", prompt);
40    io::stdout().flush().unwrap();
41
42    match rpassword::read_password() {
43        Ok(p) => p.trim().to_string(),
44        Err(_) => {
45            // No TTY (piped input, CI). Fall back to a plain line read so
46            // scripts keep working, and say so rather than failing silently.
47            eprintln!("⚠ No terminal available — password will not be hidden.");
48            let mut buf = String::new();
49            io::stdin().read_line(&mut buf).unwrap_or_default();
50            buf.trim().to_string()
51        }
52    }
53}
54
55/// Read a password twice and require the two to match.
56///
57/// Only `lock` uses this. A typo while locking encrypts the archive under a
58/// passphrase nobody knows; a typo while unlocking simply fails and can be
59/// retried, so confirmation there would only be friction.
60pub fn read_password_confirmed() -> Option<String> {
61    let first = read_password("🔑 Enter password: ");
62
63    if first.is_empty() {
64        eprintln!("❌ Empty password. Aborting.");
65        return None;
66    }
67
68    let second = read_password("🔑 Confirm password: ");
69
70    if first != second {
71        eprintln!("❌ Passwords do not match. Nothing was encrypted.");
72        return None;
73    }
74
75    Some(first)
76}