Skip to content

Commit e32600c

Browse files
authored
feat: add --database-path argument (#485)
This permits the configuration of the database path without using environments variables, which are not supported in some environments such as Scoop.
2 parents ea79257 + 565d452 commit e32600c

File tree

8 files changed

+70
-57
lines changed

8 files changed

+70
-57
lines changed

Cargo.lock

Lines changed: 16 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ debug = false
2727
strip = "symbols"
2828

2929
[dependencies]
30-
serde = { version = "1.0.207", features = ["derive"] }
31-
serde_json = "1.0.124"
30+
serde_json = "1.0.127"
31+
serde = { version = "1.0.209", features = ["derive"] }
3232
dirs = "5.0.1"
3333
rpassword = "7.3.1"
3434
data-encoding = "2.6.0"
3535
copypasta-ext = "0.4.4"
3636
zeroize = { version = "1.8.1", features = ["zeroize_derive"] }
37-
clap = { version = "4.5.15", features = ["derive"] }
37+
clap = { version = "4.5.16", features = ["derive"] }
3838
hmac = "0.12.1"
3939
sha1 = "0.10.6"
4040
sha2 = "0.10.8"
@@ -48,9 +48,9 @@ qrcode = "0.14.1"
4848
urlencoding = "2.1.3"
4949
base64 = "0.22.1"
5050
md-5 = "0.10.6"
51-
ratatui = { version = "0.28.0", features = ["all-widgets"] }
51+
ratatui = { version = "0.28.1", features = ["all-widgets"] }
5252
crossterm = "0.28.1"
5353
url = "2.5.2"
5454
color-eyre = "0.6.3"
5555
enum_dispatch = "0.3.13"
56-
derive_builder = "0.20.0"
56+
derive_builder = "0.20.1"

src/arguments/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,16 @@ pub trait SubcommandExecutor {
2727

2828
/// Main structure defining the Clap argument for the cotp commandline utility
2929
#[derive(Parser)]
30-
#[command(author, version = env ! ("COTP_VERSION"), about, long_about = None)]
30+
#[command(author, version = env!("COTP_VERSION"), about, long_about = None)]
3131
pub struct CotpArgs {
3232
#[command(subcommand)]
3333
command: Option<CotpSubcommands>,
3434
/// Fetch the password from standard input
3535
#[arg(long = "password-stdin", default_value_t = false)]
3636
pub password_from_stdin: bool,
37+
/// Set the database path
38+
#[arg(short = 'd', long = "database-path")]
39+
pub database_path: Option<String>,
3740
}
3841

3942
/// Define available Subcommands

src/main.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use interface::event::{Event, EventHandler};
77
use interface::handlers::handle_key_events;
88
use interface::ui::Tui;
99
use otp::otp_element::{OTPDatabase, CURRENT_DATABASE_VERSION};
10+
use path::init_path;
1011
use ratatui::prelude::CrosstermBackend;
1112
use ratatui::Terminal;
1213
use reading::{get_elements_from_input, get_elements_from_stdin, ReadResult};
@@ -24,7 +25,9 @@ mod path;
2425
mod reading;
2526
mod utils;
2627

27-
fn init(read_password_from_stdin: bool) -> color_eyre::Result<ReadResult> {
28+
fn init(args: &CotpArgs) -> color_eyre::Result<ReadResult> {
29+
init_path(args);
30+
2831
match utils::init_app() {
2932
Ok(first_run) => {
3033
if first_run {
@@ -38,7 +41,7 @@ fn init(read_password_from_stdin: bool) -> color_eyre::Result<ReadResult> {
3841
let save_result = database.save_with_pw(&pw);
3942
pw.zeroize();
4043
save_result.map(|(key, salt)| (database, key, salt.to_vec()))
41-
} else if read_password_from_stdin {
44+
} else if args.password_from_stdin {
4245
get_elements_from_stdin()
4346
} else {
4447
get_elements_from_input()
@@ -52,7 +55,7 @@ fn main() -> AppResult<()> {
5255
color_eyre::install()?;
5356

5457
let cotp_args: CotpArgs = CotpArgs::parse();
55-
let (database, mut key, salt) = match init(cotp_args.password_from_stdin) {
58+
let (database, mut key, salt) = match init(&cotp_args) {
5659
Ok(v) => v,
5760
Err(e) => {
5861
println!("{e}");

src/otp/otp_element.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{fs::File, io::Write, vec};
44

55
use crate::crypto::cryptography::{argon_derive_key, encrypt_string_with_key, gen_salt};
66
use crate::otp::otp_error::OtpError;
7-
use crate::path::get_db_path;
7+
use crate::path::DATABASE_PATH;
88
use data_encoding::BASE32_NOPAD;
99
use qrcode::render::unicode;
1010
use qrcode::QrCode;
@@ -69,7 +69,7 @@ impl OTPDatabase {
6969
fn overwrite_database_key(&self, key: &Vec<u8>, salt: &[u8]) -> Result<(), std::io::Error> {
7070
let json: &str = &serde_json::to_string(&self)?;
7171
let encrypted = encrypt_string_with_key(json.to_string(), key, salt).unwrap();
72-
let mut file = File::create(get_db_path())?;
72+
let mut file = File::create(DATABASE_PATH.get().unwrap())?;
7373
match serde_json::to_string(&encrypted) {
7474
Ok(content) => {
7575
file.write_all(content.as_bytes())?;

src/path.rs

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,26 @@ use std::path::PathBuf;
33
use std::sync::OnceLock;
44
use std::{env, fs};
55

6+
use crate::arguments::CotpArgs;
7+
68
const CURRENT_DB_PATH: &str = "./db.cotp";
79
const XDG_PATH: &str = "cotp/db.cotp";
810
const HOME_PATH: &str = ".cotp/db.cotp";
911

10-
static ONCE_COMPUTED_PATH: OnceLock<PathBuf> = OnceLock::new();
12+
pub static DATABASE_PATH: OnceLock<PathBuf> = OnceLock::new();
1113

12-
pub fn get_db_path() -> PathBuf {
13-
env::var("COTP_DB_PATH")
14-
.map(PathBuf::from)
15-
.unwrap_or_else(|_| get_default_db_path())
14+
/// Initialize singleton database path
15+
pub fn init_path(args: &CotpArgs) -> PathBuf {
16+
DATABASE_PATH
17+
.get_or_init(|| {
18+
args.database_path
19+
.as_ref()
20+
.map(String::from)
21+
.or(env::var("COTP_DB_PATH").ok())
22+
.map(PathBuf::from)
23+
.unwrap_or_else(get_default_db_path)
24+
})
25+
.to_owned()
1626
}
1727

1828
// Pushing an absolute path to a PathBuf replaces the entire PathBuf: https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push
@@ -26,26 +36,21 @@ fn get_default_db_path() -> PathBuf {
2636

2737
let home_path = home_dir().map(|path| path.join(HOME_PATH));
2838

29-
ONCE_COMPUTED_PATH
30-
.get_or_init(|| {
31-
data_dir()
32-
.map(PathBuf::from)
33-
.map(|p| p.join(XDG_PATH))
34-
.map(|xdg| {
35-
if !xdg.exists() {
36-
if let Some(home) = &home_path {
37-
if home.exists() {
38-
fs::create_dir_all(xdg.parent().unwrap())
39-
.expect("Failed to create dir");
40-
fs::copy(home, xdg.as_path())
41-
.expect("Failed on copy from legacy dir to XDG_DATA_HOME");
42-
}
43-
}
39+
data_dir()
40+
.map(PathBuf::from)
41+
.map(|p| p.join(XDG_PATH))
42+
.map(|xdg| {
43+
if !xdg.exists() {
44+
if let Some(home) = &home_path {
45+
if home.exists() {
46+
fs::create_dir_all(xdg.parent().unwrap()).expect("Failed to create dir");
47+
fs::copy(home, xdg.as_path())
48+
.expect("Failed on copy from legacy dir to XDG_DATA_HOME");
4449
}
45-
xdg
46-
})
47-
.or(home_path)
48-
.unwrap_or(portable_path)
50+
}
51+
}
52+
xdg
4953
})
50-
.to_owned()
54+
.or(home_path)
55+
.unwrap_or(portable_path)
5156
}

src/reading.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::crypto;
22
use crate::otp::otp_element::{OTPDatabase, OTPElement};
3-
use crate::path::get_db_path;
3+
use crate::path::DATABASE_PATH;
44
use crate::utils;
55
use color_eyre::eyre::{eyre, ErrReport};
66
use std::fs::read_to_string;
@@ -28,7 +28,8 @@ fn get_elements_with_password(mut password: String) -> color_eyre::Result<ReadRe
2828
}
2929

3030
pub fn read_decrypted_text(password: &str) -> color_eyre::Result<(String, Vec<u8>, Vec<u8>)> {
31-
let encrypted_contents = read_to_string(get_db_path()).map_err(ErrReport::from)?;
31+
let encrypted_contents =
32+
read_to_string(DATABASE_PATH.get().unwrap()).map_err(ErrReport::from)?;
3233
if encrypted_contents.is_empty() {
3334
return match delete_db() {
3435
Ok(_) => Err(eyre!(
@@ -58,5 +59,5 @@ pub fn read_from_file(password: &str) -> color_eyre::Result<ReadResult> {
5859
}
5960

6061
fn delete_db() -> io::Result<()> {
61-
std::fs::remove_file(get_db_path())
62+
std::fs::remove_file(DATABASE_PATH.get().unwrap())
6263
}

src/utils.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use crate::path::get_db_path;
21
use std::time::{SystemTime, UNIX_EPOCH};
32

3+
use crate::path::DATABASE_PATH;
4+
45
pub fn init_app() -> Result<bool, ()> {
5-
let db_path = get_db_path();
6+
let db_path = DATABASE_PATH.get().unwrap(); // Safe to unwrap because we initialize
67
let db_dir = db_path.parent().unwrap();
78
if !db_dir.exists() {
89
if let Err(_e) = std::fs::create_dir_all(db_dir) {

0 commit comments

Comments
 (0)