From 30d40fea83f4d9e06e14f260c23a31020bd39509 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Sat, 17 Jun 2017 14:47:35 -0400 Subject: app: Introduce subcommands for serve and create user. (Doesn't yet create users) --- Cargo.lock | 22 +++++++++++++++++++ Cargo.toml | 1 + src/auth/mod.rs | 24 ++++++++++++-------- src/bin/main.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++---------- src/db.rs | 5 ++--- 5 files changed, 96 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd8c576..75c52d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "router 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rpassword 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "sqlite 0.23.4 (registry+https://github.com/rust-lang/crates.io-index)", "staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -415,6 +416,17 @@ dependencies = [ "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rpassword" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rust-crypto" version = "0.2.36" @@ -508,6 +520,14 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "termios" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread-id" version = "3.1.0" @@ -695,6 +715,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" "checksum route-recognizer 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3255338088df8146ba63d60a9b8e3556f1146ce2973bc05a75181a42ce2256" "checksum router 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9b1797ff166029cb632237bb5542696e54961b4cf75a324c6f05c9cf0584e4e" +"checksum rpassword 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "72556b202b5b38d0b69b0fdced60cf92dec7e37640c98eb9d7baf5bd86dc8e1a" "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" "checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" @@ -707,6 +728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "31493480e073d52522a94cdf56269dd8eb05f99549effd1826b0271690608878" "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" "checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209" +"checksum termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" "checksum thread-id 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8df7875b676fddfadffd96deea3b1124e5ede707d4884248931077518cf1f773" "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" "checksum time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "ffd7ccbf969a892bf83f1e441126968a07a3941c24ff522a26af9f9f4585d1a3" diff --git a/Cargo.toml b/Cargo.toml index 2109fef..10e98fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ sqlite = "*" rust-crypto = "*" base64 = "*" clap = "*" +rpassword = "*" diff --git a/src/auth/mod.rs b/src/auth/mod.rs index f3db525..728c246 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -2,22 +2,28 @@ extern crate base64; use crypto::bcrypt_pbkdf::bcrypt_pbkdf; -// TODO: Replace salt with a random string. +#[derive(Debug, PartialEq, Eq)] +pub struct HashedPassword { + salt: String, + enc: String, +} + // TODO: Configurable number of iterations. -pub fn encode(pw: &str) -> String { - let salt = "hello"; +pub fn encode(salt: &str, pw: &str) -> HashedPassword { let mut enc = vec!(0; 32); let encrypted = bcrypt_pbkdf(pw.as_bytes(), salt.as_bytes(), 10, &mut enc); - format!("${}${}${}", "sdv1", - base64::encode(salt.as_bytes()), base64::encode(&enc)) + HashedPassword { + salt: salt.to_string(), + enc: base64::encode(&enc) + } } -pub fn validate(pw: &str, enc: &str) -> bool { +pub fn validate(pw: &str, enc: &HashedPassword) -> bool { // let cs = enc.split('$'); // println("{:?}", cs.len()); // let enc_pw = cs[3]; - encode(pw) == enc + encode(enc.salt.as_str(), pw) == *enc } #[cfg(test)] @@ -25,7 +31,7 @@ mod tests { use super::*; #[test] fn it_validates() { - assert_eq!(false, validate("123", "123")); - assert_eq!(true, validate("123", &encode("123"))); + assert_eq!(false, validate("hello", "123", "123")); + assert_eq!(true, validate("hello", "123", &encode("hello", "123"))); } } diff --git a/src/bin/main.rs b/src/bin/main.rs index 36d6c00..a4d489d 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -7,28 +7,72 @@ extern crate env_logger; #[macro_use] extern crate clap; extern crate sqlite; +extern crate rpassword; + +use rpassword::read_password; +use std::io::{self, Write}; +use systemhttp::auth; + +use clap::{App, AppSettings, Arg, SubCommand}; fn main() { - let matches = clap_app!( - systemhttpd => - (version: "0.1") - (author: "Kjetil Ørbekk") - (about: "A systemd web frontend") - (@arg PORT: -p --port +takes_value "Port to serve on") - (@arg DB_FILE: --db_file +required +takes_value - "Path to sqlite database")) + let matches = App::new("systemhttpd") + .version("0.1") + .author("Kjetil Ørbekk") + .about("Systemd web frontend") + .setting(AppSettings::SubcommandRequired) + .arg(Arg::with_name("port") + .short("p") + .long("port") + .takes_value(true) + .help("Port to serve on")) + .arg(Arg::with_name("db_file") + .long("db_file") + .takes_value(true) + .help("Path to sqlite database")) + .subcommand(SubCommand::with_name("serve") + .about("Start the systemhttpd server")) + .subcommand(SubCommand::with_name("create_admin_user") + .about("Add an admin user to the db")) .get_matches(); - let port = matches.value_of("PORT").unwrap_or("8080") + let port = matches.value_of("port").unwrap_or("8080") .parse::().expect("port number"); - let db_file = matches.value_of("DB_FILE").unwrap(); + let db_file = matches.value_of("db_file").unwrap(); env_logger::init().unwrap(); let mut conn = sqlite::Connection::open(db_file) .expect(format!("opening sqlite database at {}", db_file).as_str()); systemhttp::db::init(&mut conn); - let _server = systemhttp::server::serve(port).unwrap(); - println!("Serving on {}", port); + let mut serve = || { + let _server = systemhttp::server::serve(port).unwrap(); + println!("Serving on {}", port); + }; + + let create_admin_user = || { + println!("Create admin user"); + print!("Username: "); + io::stdout().flush(); + let mut user = String::new(); + io::stdin().read_line(&mut user).unwrap(); + let password = rpassword::prompt_password_stdout("Password: ").unwrap(); + let confirmation = + rpassword::prompt_password_stdout("Repeat password: ").unwrap(); + + if password != confirmation { + println!("\nPasswords don't match"); + return + } + println!("\nCreating user {} with password (hashed) {:?}", + user, auth::encode("test_salt", password.as_str())); + }; + + match matches.subcommand_name() { + Some("serve") => serve(), + Some("create_admin_user") => create_admin_user(), + x => panic!("Don't know about subcommand: {:?}", x) + } + } diff --git a/src/db.rs b/src/db.rs index 678e603..dea9924 100644 --- a/src/db.rs +++ b/src/db.rs @@ -12,9 +12,8 @@ pub fn init(conn: &mut Connection) -> Result<()> { info!("Initializing db..."); conn.execute(" BEGIN; - CREATE TABLE users (username TEXT, - salt TEXT, - passwd TEXT); + CREATE TABLE IF NOT EXISTS users + (username TEXT, salt TEXT, passwd TEXT); COMMIT; ").unwrap(); } -- cgit v1.2.3