aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/args.rs53
-rw-r--r--src/auth.rs34
-rw-r--r--src/main.rs2
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,