mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 02:22:21 +00:00
Add some CLI commands to generate a default configuration and password hashes
This commit is contained in:
94
src/commands/mod.rs
Normal file
94
src/commands/mod.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use argon2::password_hash::SaltString;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use password_hash::PasswordHasher;
|
||||
use pbkdf2::Params;
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
use rustical_frontend::FrontendConfig;
|
||||
use rustical_store::auth::{static_user_store::UserEntry, StaticUserStoreConfig, User};
|
||||
|
||||
use crate::config::{
|
||||
AuthConfig, Config, DataStoreConfig, HttpConfig, SqliteDataStoreConfig, TracingConfig,
|
||||
};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct GenConfigArgs {}
|
||||
|
||||
fn generate_frontend_secret() -> [u8; 64] {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let mut secret = [0u8; 64];
|
||||
rng.fill_bytes(&mut secret);
|
||||
secret
|
||||
}
|
||||
|
||||
pub fn cmd_gen_config(_args: GenConfigArgs) -> anyhow::Result<()> {
|
||||
let config = Config {
|
||||
http: HttpConfig::default(),
|
||||
auth: AuthConfig::Static(StaticUserStoreConfig {
|
||||
users: vec![UserEntry {
|
||||
user: User {
|
||||
id: "default".to_owned(),
|
||||
displayname: Some("Default user".to_owned()),
|
||||
password: Some("generate a password hash with rustical pwhash".to_owned()),
|
||||
},
|
||||
app_tokens: vec![],
|
||||
}],
|
||||
}),
|
||||
data_store: DataStoreConfig::Sqlite(SqliteDataStoreConfig {
|
||||
db_url: "".to_owned(),
|
||||
}),
|
||||
tracing: TracingConfig::default(),
|
||||
frontend: FrontendConfig {
|
||||
secret_key: generate_frontend_secret(),
|
||||
},
|
||||
};
|
||||
let generated_config = toml::to_string(&config)?;
|
||||
println!("{generated_config}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, ValueEnum)]
|
||||
enum PwhashAlgorithm {
|
||||
#[value(help = "Use this for your password")]
|
||||
Argon2,
|
||||
#[value(help = "Significantly faster algorithm, use for app tokens")]
|
||||
Pbkdf2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct PwhashArgs {
|
||||
#[arg(long, short = 'a')]
|
||||
algorithm: PwhashAlgorithm,
|
||||
#[arg(
|
||||
long,
|
||||
short = 'r',
|
||||
help = "ONLY for pbkdf2: Number of rounds to calculate",
|
||||
default_value_t = 1000
|
||||
)]
|
||||
rounds: u32,
|
||||
}
|
||||
|
||||
pub fn cmd_pwhash(args: PwhashArgs) -> anyhow::Result<()> {
|
||||
println!("Enter your password:");
|
||||
let password = rpassword::read_password()?;
|
||||
let salt = SaltString::generate(OsRng);
|
||||
let password_hash = match args.algorithm {
|
||||
PwhashAlgorithm::Argon2 => argon2::Argon2::default()
|
||||
.hash_password(password.as_bytes(), &salt)
|
||||
.unwrap(),
|
||||
PwhashAlgorithm::Pbkdf2 => pbkdf2::Pbkdf2
|
||||
.hash_password_customized(
|
||||
password.as_bytes(),
|
||||
None,
|
||||
None,
|
||||
Params {
|
||||
rounds: args.rounds,
|
||||
..Default::default()
|
||||
},
|
||||
&salt,
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
println!("{password_hash}");
|
||||
Ok(())
|
||||
}
|
||||
55
src/main.rs
55
src/main.rs
@@ -2,7 +2,8 @@ use crate::config::Config;
|
||||
use actix_web::HttpServer;
|
||||
use anyhow::Result;
|
||||
use app::make_app;
|
||||
use clap::Parser;
|
||||
use clap::{Parser, Subcommand};
|
||||
use commands::{cmd_gen_config, cmd_pwhash};
|
||||
use config::{DataStoreConfig, SqliteDataStoreConfig};
|
||||
use rustical_store::auth::StaticUserStore;
|
||||
use rustical_store::{AddressbookStore, CalendarStore};
|
||||
@@ -12,16 +13,26 @@ use std::fs;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod app;
|
||||
mod commands;
|
||||
mod config;
|
||||
mod setup_tracing;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(short, long, env)]
|
||||
#[arg(short, long, env, default_value = "/etc/rustical/config.toml")]
|
||||
config_file: String,
|
||||
#[arg(long, env, help = "Run database migrations (only for sql store)")]
|
||||
migrate: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Command {
|
||||
GenConfig(commands::GenConfigArgs),
|
||||
Pwhash(commands::PwhashArgs),
|
||||
}
|
||||
|
||||
async fn get_data_stores(
|
||||
@@ -40,27 +51,33 @@ async fn get_data_stores(
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
let config: Config = toml::from_str(&fs::read_to_string(&args.config_file)?)?;
|
||||
|
||||
setup_tracing(&config.tracing);
|
||||
match args.command {
|
||||
Some(Command::GenConfig(gen_config_args)) => cmd_gen_config(gen_config_args)?,
|
||||
Some(Command::Pwhash(pwhash_args)) => cmd_pwhash(pwhash_args)?,
|
||||
None => {
|
||||
let config: Config = toml::from_str(&fs::read_to_string(&args.config_file)?)?;
|
||||
|
||||
let (addr_store, cal_store) = get_data_stores(args.migrate, &config.data_store).await?;
|
||||
setup_tracing(&config.tracing);
|
||||
|
||||
let user_store = Arc::new(match config.auth {
|
||||
config::AuthConfig::Static(config) => StaticUserStore::new(config),
|
||||
});
|
||||
let (addr_store, cal_store) = get_data_stores(args.migrate, &config.data_store).await?;
|
||||
|
||||
HttpServer::new(move || {
|
||||
make_app(
|
||||
addr_store.clone(),
|
||||
cal_store.clone(),
|
||||
user_store.clone(),
|
||||
config.frontend.clone(),
|
||||
)
|
||||
})
|
||||
.bind((config.http.host, config.http.port))?
|
||||
.run()
|
||||
.await?;
|
||||
let user_store = Arc::new(match config.auth {
|
||||
config::AuthConfig::Static(config) => StaticUserStore::new(config),
|
||||
});
|
||||
|
||||
HttpServer::new(move || {
|
||||
make_app(
|
||||
addr_store.clone(),
|
||||
cal_store.clone(),
|
||||
user_store.clone(),
|
||||
config.frontend.clone(),
|
||||
)
|
||||
})
|
||||
.bind((config.http.host, config.http.port))?
|
||||
.run()
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user