Skip to main content

fur_cli/security/
unlock.rs

1use std::fs;
2use std::path::Path;
3
4use clap::Parser;
5use rpassword::read_password;
6
7use crate::security::{io, crypto, state};
8
9#[derive(Parser, Clone)]
10pub struct UnlockArgs {
11    #[arg(long)]
12    pub hide: bool,
13}
14
15pub fn run_unlock(args: UnlockArgs) {
16
17    if !state::is_locked() {
18        println!("🔓 Project already unlocked.");
19        return;
20    }
21
22    let pass = if args.hide {
23        println!("🔑 Enter password:");
24        read_password().unwrap()
25    } else {
26        io::read_visible_password()
27    };
28
29    if !verify_password(&pass) {
30        println!("❌ Incorrect password. Project remains locked.");
31        return;
32    }
33
34    decrypt_project(&pass);
35
36    state::remove_lock();
37
38    println!("🔓 Project decrypted.");
39}
40
41fn verify_password(password: &str) -> bool {
42
43    let check_path = Path::new(".fur/.lockcheck");
44
45    let bytes = match std::fs::read(check_path) {
46        Ok(b) => b,
47        Err(_) => return false,
48    };
49
50    let decrypted = match crypto::decrypt(&bytes, password) {
51        Ok(d) => d,
52        Err(_) => return false,
53    };
54
55    match std::str::from_utf8(&decrypted) {
56        Ok(text) => text.trim() == "FUR_LOCK_CHECK_V1",
57        Err(_) => false,
58    }
59}
60
61fn decrypt_project(password: &str) {
62
63    decrypt_dir(".fur/messages", password);
64    decrypt_dir(".fur/threads", password);
65
66    decrypt_file(".fur/index.json", password);
67    decrypt_file(".fur/.lockcheck", password);
68
69    decrypt_markdowns(password);
70}
71
72fn decrypt_dir(dir: &str, password: &str) {
73
74    let path = Path::new(dir);
75
76    if let Ok(entries) = fs::read_dir(path) {
77
78        for e in entries.flatten() {
79
80            let p = e.path();
81
82            if p.is_file() {
83                io::decrypt_file(&p, password);
84            }
85        }
86    }
87}
88
89fn decrypt_file(path: &str, password: &str) {
90
91    let p = Path::new(path);
92
93    if p.exists() {
94        io::decrypt_file(p, password);
95    }
96}
97
98fn decrypt_markdowns(password: &str) {
99
100    let chats = Path::new("chats");
101
102    if !chats.exists() {
103        return;
104    }
105
106    if let Ok(entries) = fs::read_dir(chats) {
107
108        for e in entries.flatten() {
109
110            let p = e.path();
111
112            if p.is_file() {
113                io::decrypt_file(&p, password);
114            }
115        }
116    }
117}