diff options
Diffstat (limited to '')
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | src/args.rs | 10 | ||||
-rw-r--r-- | src/config.rs | 16 | ||||
-rw-r--r-- | tests/auth_file.rs | 62 | ||||
-rw-r--r-- | tests/data/auth1.txt | 3 |
5 files changed, 92 insertions, 3 deletions
@@ -47,6 +47,10 @@ Sometimes this is just a more practical and quick way than doing things properly pw=$(echo -n "123" | sha256sum | cut -f 1 -d ' ') miniserve --auth joe:sha256:$pw unreleased-linux-distros/ + +### Require username/password from file (separate logins with new lines): + + miniserve --auth-file auth.txt unreleased-linux-distros/ ### Generate random 6-hexdigit URL: diff --git a/src/args.rs b/src/args.rs index 590ac14..8de05ad 100644 --- a/src/args.rs +++ b/src/args.rs @@ -80,6 +80,14 @@ pub struct CliArgs { )] pub auth: Vec<auth::RequiredAuth>, + /// Read authentication values from a file. Example file content: + /// + /// joe:123 + /// bob:sha256:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3 + /// bill: + #[arg(long, value_hint = ValueHint::FilePath, env = "MINISERVE_AUTH_FILE")] + pub auth_file: Option<PathBuf>, + /// Use a specific route prefix #[arg(long = "route-prefix", env = "MINISERVE_ROUTE_PREFIX")] pub route_prefix: Option<String>, @@ -249,7 +257,7 @@ fn parse_interface(src: &str) -> Result<IpAddr, std::net::AddrParseError> { } /// Parse authentication requirement -fn parse_auth(src: &str) -> Result<auth::RequiredAuth, ContextualError> { +pub fn parse_auth(src: &str) -> Result<auth::RequiredAuth, ContextualError> { let mut split = src.splitn(3, ':'); let invalid_auth_format = Err(ContextualError::InvalidAuthFormat); diff --git a/src/config.rs b/src/config.rs index 2df9fdd..314d186 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ #[cfg(feature = "tls")] use std::{fs::File, io::BufReader}; use std::{ + io::BufRead, net::{IpAddr, Ipv4Addr, Ipv6Addr}, path::PathBuf, }; @@ -14,7 +15,7 @@ use http::HeaderMap; use rustls_pemfile as pemfile; use crate::{ - args::{CliArgs, MediaType}, + args::{parse_auth, CliArgs, MediaType}, auth::RequiredAuth, file_upload::sanitize_path, renderer::ThemeSlug, @@ -164,6 +165,17 @@ impl MiniserveConfig { _ => "".to_owned(), }; + let mut auth = args.auth; + + if let Some(path) = args.auth_file { + let file = File::open(path)?; + let lines = BufReader::new(file).lines(); + + for line in lines { + auth.push(parse_auth(line?.as_str())?); + } + } + // Generate some random routes for the favicon and css so that they are very unlikely to conflict with // real files. // If --random-route is enabled , in order to not leak the random generated route, we must not use it @@ -246,7 +258,7 @@ impl MiniserveConfig { path: args.path.unwrap_or_else(|| PathBuf::from(".")), port, interfaces, - auth: args.auth, + auth, path_explicitly_chosen, no_symlinks: args.no_symlinks, show_hidden: args.hidden, diff --git a/tests/auth_file.rs b/tests/auth_file.rs new file mode 100644 index 0000000..e022a3d --- /dev/null +++ b/tests/auth_file.rs @@ -0,0 +1,62 @@ +mod fixtures; + +use fixtures::{server, server_no_stderr, Error, FILES}; +use http::StatusCode; +use reqwest::blocking::Client; +use rstest::rstest; +use select::document::Document; +use select::predicate::Text; + +#[rstest( + cli_auth_file_arg, client_username, client_password, + case("tests/data/auth1.txt", "joe", "123"), + case("tests/data/auth1.txt", "bob", "123"), + case("tests/data/auth1.txt", "bill", ""), +)] +fn auth_file_accepts( + cli_auth_file_arg: &str, + client_username: &str, + client_password: &str +) -> Result<(), Error> { + let server = server(&["--auth-file", cli_auth_file_arg]); + let client = Client::new(); + let response = client + .get(server.url()) + .basic_auth(client_username, Some(client_password)) + .send()?; + + let status_code = response.status(); + assert_eq!(status_code, StatusCode::OK); + + let body = response.error_for_status()?; + let parsed = Document::from_read(body)?; + for &file in FILES { + assert!(parsed.find(Text).any(|x| x.text() == file)); + } + + Ok(()) +} + +#[rstest( + cli_auth_file_arg, client_username, client_password, + case("tests/data/auth1.txt", "joe", "wrongpassword"), + case("tests/data/auth1.txt", "bob", ""), + case("tests/data/auth1.txt", "nonexistentuser", "wrongpassword"), +)] +fn auth_file_rejects( + cli_auth_file_arg: &str, + client_username: &str, + client_password: &str, +) -> Result<(), Error> { + let server = server_no_stderr(&["--auth-file", cli_auth_file_arg]); + let client = Client::new(); + let status = client + .get(server.url()) + .basic_auth(client_username, Some(client_password)) + .send()? + .status(); + + assert_eq!(status, StatusCode::UNAUTHORIZED); + + Ok(()) +} diff --git a/tests/data/auth1.txt b/tests/data/auth1.txt new file mode 100644 index 0000000..3744d61 --- /dev/null +++ b/tests/data/auth1.txt @@ -0,0 +1,3 @@ +joe:123 +bob:sha256:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3 +bill: |