aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--README.md4
-rw-r--r--src/args.rs10
-rw-r--r--src/config.rs16
-rw-r--r--tests/auth_file.rs62
-rw-r--r--tests/data/auth1.txt3
5 files changed, 92 insertions, 3 deletions
diff --git a/README.md b/README.md
index 56701db..dbdf564 100644
--- a/README.md
+++ b/README.md
@@ -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: