Skip to content

Commit eee7680

Browse files
committed
basic auth
1 parent 3c81294 commit eee7680

File tree

8 files changed

+76
-2
lines changed

8 files changed

+76
-2
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ tokio-util = { version = "0.7.15", features = [
3636
] }
3737
brotli = "8.0.0"
3838
urlencoding = "2.1.3"
39+
base64 = "0.22.1"
3940

4041
[target.'cfg(unix)'.dependencies]
4142
unix_mode = "0.1.4"

justfile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,6 @@ fmt:
9797
fmt_fix *ARGS:
9898
cargo +nightly fmt
9999

100-
101-
102100
watch *ARGS:
103101
cargo watch --watch src -- just run {{ARGS}}
104102

src/auth/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

src/b64/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#![allow(unused)]
2+
3+
use base64::prelude::*;
4+
5+
pub fn encode<S: AsRef<str>>(input: S) -> String {
6+
BASE64_STANDARD.encode(input.as_ref().as_bytes())
7+
}
8+
9+
pub fn decode_bytes<S: AsRef<str>>(input: S) -> anyhow::Result<Vec<u8>> {
10+
Ok(BASE64_STANDARD.decode(input.as_ref().as_bytes())?)
11+
}
12+
13+
pub fn decode_string<S: AsRef<str>>(input: S) -> anyhow::Result<String> {
14+
Ok(String::from_utf8(decode_bytes(input)?)?)
15+
}

src/cli.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ pub struct CliCommand {
3030
#[arg(short = 'H', long = "header")]
3131
pub headers: Vec<String>,
3232

33+
/// Put server behind basic auth (Format "username:password")
34+
#[arg(long = "auth")]
35+
pub basic_auth: Vec<String>,
36+
3337
/// Enable CORS header
3438
#[arg(long = "cors")]
3539
pub cors: bool,

src/config.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct Config {
2222
pub domain: String,
2323
pub domain_pretty: String,
2424
pub headers: HashMap<String, Vec<String>>,
25+
pub basic_auth: HashMap<String, String>,
2526
pub quiet: bool,
2627
pub watch: bool,
2728
pub watch_dir: PathBuf,
@@ -79,6 +80,17 @@ impl Config {
7980
);
8081
}
8182

83+
let mut basic_auth = HashMap::<String, String>::new();
84+
85+
for val in command.basic_auth {
86+
let Some((key, value)) = val.split_once(":") else {
87+
return Err(anyhow::anyhow!("Unable to parse auth"));
88+
};
89+
let key = key.to_string();
90+
let value = value.to_string();
91+
basic_auth.insert(key, value);
92+
}
93+
8294
if command.cors {
8395
headers.insert(
8496
"Access-Control-Allow-Origin".to_string(),
@@ -122,6 +134,7 @@ impl Config {
122134
compress: command.compress,
123135
sab: command.sab,
124136
address: command.address,
137+
basic_auth,
125138
port: command.port,
126139
headers,
127140
quiet: command.quiet,

src/main.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![deny(unused_crate_dependencies)]
22
#![allow(clippy::module_inception)]
33

4+
mod auth;
5+
mod b64;
46
mod cli;
57
mod compress;
68
mod config;
@@ -118,6 +120,39 @@ async fn main_async() -> anyhow::Result<()> {
118120
let watcher = watcher.clone();
119121

120122
async move {
123+
// Basic Auth
124+
if !config.basic_auth.is_empty() {
125+
let Some(header) = req.headers().get("authorization") else {
126+
return Ok(
127+
res
128+
.header(
129+
"WWW-Authenticate",
130+
"Basic realm=\"http-server-rs\"".to_string(),
131+
)
132+
.status(401)
133+
.body_from("")?,
134+
);
135+
};
136+
137+
let Some((_, token)) = header.to_str()?.split_once(" ") else {
138+
return Ok(res.status(500).body_from("Invalid basic auth header")?);
139+
};
140+
141+
let decoded = b64::decode_string(token)?;
142+
143+
let Some((username, password)) = decoded.split_once(":") else {
144+
return Ok(res.status(500).body_from("Invalid basic auth header")?);
145+
};
146+
147+
let Some(creds) = config.basic_auth.get(username) else {
148+
return Ok(res.status(403).body_from("")?);
149+
};
150+
151+
if creds != password {
152+
return Ok(res.status(403).body_from("")?);
153+
}
154+
}
155+
121156
// Remove the leading slash
122157
let req_path = req.uri().path().to_string().replacen("/", "", 1);
123158
let req_path = urlencoding::decode(&req_path)?.to_string();

0 commit comments

Comments
 (0)