diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/args.rs | 53 | ||||
-rw-r--r-- | src/auth.rs | 34 | ||||
-rw-r--r-- | src/main.rs | 2 |
3 files changed, 59 insertions, 30 deletions
diff --git a/src/args.rs b/src/args.rs index 825a4ac..404225a 100644 --- a/src/args.rs +++ b/src/args.rs @@ -39,7 +39,7 @@ struct CLIArgs { /// Set authentication (username:password) #[structopt(short = "a", long = "auth", parse(try_from_str = "parse_auth"))] - auth: Option<(String, String)>, + auth: Option<auth::RequiredAuth>, /// Generate a random 6-hexdigit route #[structopt(long = "random-route")] @@ -76,36 +76,42 @@ fn parse_interface(src: &str) -> Result<IpAddr, std::net::AddrParseError> { } /// Checks wether the auth string is valid, i.e. it follows the syntax username:password -fn parse_auth(src: &str) -> Result<(String, String), String> { +fn parse_auth(src: &str) -> Result<auth::RequiredAuth, String> { let mut split = src.splitn(2, ':'); + let errmsg = "Invalid credentials string, expected format is username:password".to_owned(); let username = match split.next() { Some(username) => username, - None => { - return Err( - "Invalid credentials string, expected format is username:password".to_owned(), - ) - } + None => return Err(errmsg), }; - let password = match split.next() { + let second_part = match split.next() { // This allows empty passwords, as the spec does not forbid it Some(password) => password, - None => { - return Err( - "Invalid credentials string, expected format is username:password".to_owned(), - ) - } + None => return Err(errmsg), }; - // To make it Windows-compatible, the password needs to be shorter than 255 characters. - // After 255 characters, Windows will truncate the value. - // As for the username, the spec does not mention a limit in length - if password.len() > 255 { - return Err("Password length cannot exceed 255 characters".to_owned()); - } + let password = match split.next() { + Some(hash) => match second_part { + "sha256" => auth::RequiredAuthPassword::Sha256(hash.to_owned()), + _ => return Err("Invalid hash method, valid methods is sha256".to_owned()) + }, + None => { + // To make it Windows-compatible, the password needs to be shorter than 255 characters. + // After 255 characters, Windows will truncate the value. + // As for the username, the spec does not mention a limit in length + if second_part.len() > 255 { + return Err("Password length cannot exceed 255 characters".to_owned()); + } + + auth::RequiredAuthPassword::Plain(second_part.to_owned()) + }, + }; - Ok((username.to_owned(), password.to_owned())) + Ok(auth::RequiredAuth { + username: username.to_owned(), + password, + }) } /// Parses the command line arguments @@ -121,11 +127,6 @@ pub fn parse_args() -> crate::MiniserveConfig { ] }; - let auth = match args.auth { - Some((username, password)) => Some(auth::BasicAuthParams { username, password }), - None => None, - }; - let random_route = if args.random_route { Some(nanoid::custom(6, &ROUTE_ALPHABET)) } else { @@ -141,7 +142,7 @@ pub fn parse_args() -> crate::MiniserveConfig { path: args.path.unwrap_or_else(|| PathBuf::from(".")), port: args.port, interfaces, - auth, + auth: args.auth, path_explicitly_chosen, no_symlinks: args.no_symlinks, random_route, diff --git a/src/auth.rs b/src/auth.rs index 10e7a4a..6aed8cf 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,6 +1,7 @@ use actix_web::http::header; use actix_web::middleware::{Middleware, Response}; use actix_web::{HttpRequest, HttpResponse, Result}; +use sha2::{Sha256, Digest}; pub struct Auth; @@ -16,6 +17,19 @@ pub struct BasicAuthParams { pub password: String, } +#[derive(Clone, Debug)] +pub enum RequiredAuthPassword { + Plain(String), + Sha256(String), +} + +#[derive(Clone, Debug)] +/// Authentication structure to match BasicAuthParams against +pub struct RequiredAuth { + pub username: String, + pub password: RequiredAuthPassword, +} + /// Decode a HTTP basic auth string into a tuple of username and password. pub fn parse_basic_auth( authorization_header: &header::HeaderValue, @@ -34,6 +48,22 @@ pub fn parse_basic_auth( }) } +pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> bool { + if basic_auth.username != required_auth.username { + return false; + } + + match &required_auth.password { + RequiredAuthPassword::Plain(ref required_password) => basic_auth.password == *required_password, + RequiredAuthPassword::Sha256(password_hash) => { + let mut hasher = Sha256::new(); + hasher.input(basic_auth.password); + let received_hash = hex::encode(hasher.result()); + received_hash == *password_hash + } + } +} + impl Middleware<crate::MiniserveConfig> for Auth { fn response( &self, @@ -51,9 +81,7 @@ impl Middleware<crate::MiniserveConfig> for Auth { )))); } }; - if auth_req.username != required_auth.username - || auth_req.password != required_auth.password - { + if match_auth(auth_req, required_auth) { let new_resp = HttpResponse::Unauthorized().finish(); return Ok(Response::Done(new_resp)); } diff --git a/src/main.rs b/src/main.rs index 42a43b5..96e05c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,7 @@ pub struct MiniserveConfig { pub interfaces: Vec<IpAddr>, /// Enable HTTP basic authentication - pub auth: Option<auth::BasicAuthParams>, + pub auth: Option<auth::RequiredAuth>, /// If false, miniserve will serve the current working directory pub path_explicitly_chosen: bool, |