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::{crypto, io, 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    if !state::is_locked() {
17        println!("🔓 Project already unlocked.");
18        return;
19    }
20
21    let pass = if args.hide {
22        println!("🔑 Enter password:");
23        read_password().unwrap()
24    } else {
25        io::read_visible_password()
26    };
27
28    if !verify_password(&pass) {
29        println!("❌ Incorrect password. Project remains locked.");
30        return;
31    }
32
33    decrypt_project(&pass);
34
35    state::remove_lock();
36
37    println!("🔓 Project decrypted.");
38}
39
40fn verify_password(password: &str) -> bool {
41    let check_path = Path::new(".fur/.lockcheck");
42
43    let bytes = match std::fs::read(check_path) {
44        Ok(b) => b,
45        Err(_) => return false,
46    };
47
48    let decrypted = match crypto::decrypt(&bytes, password) {
49        Ok(d) => d,
50        Err(_) => return false,
51    };
52
53    match std::str::from_utf8(&decrypted) {
54        Ok(text) => text.trim() == "FUR_LOCK_CHECK_V1",
55        Err(_) => false,
56    }
57}
58
59fn decrypt_project(password: &str) {
60    decrypt_dir(".fur/messages", password);
61    decrypt_dir(".fur/threads", password);
62
63    decrypt_file(".fur/index.json", password);
64    decrypt_file(".fur/.lockcheck", password);
65
66    decrypt_markdowns(password);
67}
68
69fn decrypt_dir(dir: &str, password: &str) {
70    let path = Path::new(dir);
71
72    if let Ok(entries) = fs::read_dir(path) {
73        for e in entries.flatten() {
74            let p = e.path();
75
76            if p.is_file() {
77                io::decrypt_file(&p, password);
78            }
79        }
80    }
81}
82
83fn decrypt_file(path: &str, password: &str) {
84    let p = Path::new(path);
85
86    if p.exists() {
87        io::decrypt_file(p, password);
88    }
89}
90
91fn decrypt_markdowns(password: &str) {
92    let chats = Path::new("chats");
93
94    if !chats.exists() {
95        return;
96    }
97
98    if let Ok(entries) = fs::read_dir(chats) {
99        for e in entries.flatten() {
100            let p = e.path();
101
102            if p.is_file() {
103                io::decrypt_file(&p, password);
104            }
105        }
106    }
107}