mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-22 14:06:02 +00:00
Added password protection
This commit is contained in:
parent
f8ac02d5e5
commit
f328e2c1cb
5 changed files with 68 additions and 2 deletions
7
tty-fwd/Cargo.lock
generated
7
tty-fwd/Cargo.lock
generated
|
|
@ -77,6 +77,12 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
|
|
@ -571,6 +577,7 @@ dependencies = [
|
|||
"argument-parser",
|
||||
"bytes",
|
||||
"ctrlc",
|
||||
"data-encoding",
|
||||
"dirs",
|
||||
"http",
|
||||
"jiff",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ regex = "1.10"
|
|||
dirs = "5.0"
|
||||
notify = "6.1.1"
|
||||
ctrlc = "3.4.2"
|
||||
data-encoding = "2.5"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.59", features = ["Win32_System_Console"] }
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ When running with `--serve`, the following REST API endpoints are available:
|
|||
- `--serve`: Start HTTP API server on specified address/port
|
||||
- `--static-path`: Directory to serve static files from (requires --serve)
|
||||
- `--cleanup`: Remove exited sessions
|
||||
- `--password`: Enables an HTTP basic auth password (username is ignored)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use data_encoding::BASE64;
|
||||
use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -85,6 +86,33 @@ struct ApiResponse {
|
|||
session_id: Option<String>,
|
||||
}
|
||||
|
||||
fn check_basic_auth(req: &HttpRequest, expected_password: &str) -> bool {
|
||||
if let Some(auth_header) = req.headers().get("authorization") {
|
||||
if let Ok(auth_str) = auth_header.to_str() {
|
||||
if let Some(credentials) = auth_str.strip_prefix("Basic ") {
|
||||
if let Ok(decoded_bytes) = BASE64.decode(credentials.as_bytes()) {
|
||||
if let Ok(decoded_str) = String::from_utf8(decoded_bytes) {
|
||||
if let Some(colon_pos) = decoded_str.find(':') {
|
||||
let password = &decoded_str[colon_pos + 1..];
|
||||
return password == expected_password;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn unauthorized_response() -> Response<String> {
|
||||
Response::builder()
|
||||
.status(StatusCode::UNAUTHORIZED)
|
||||
.header("WWW-Authenticate", "Basic realm=\"tty-fwd\"")
|
||||
.header("Content-Type", "text/plain")
|
||||
.body("Unauthorized".to_string())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn get_mime_type(file_path: &Path) -> &'static str {
|
||||
match file_path.extension().and_then(|ext| ext.to_str()) {
|
||||
Some("html") | Some("htm") => "text/html",
|
||||
|
|
@ -203,16 +231,32 @@ pub fn start_server(
|
|||
bind_address: &str,
|
||||
control_path: PathBuf,
|
||||
static_path: Option<String>,
|
||||
password: Option<String>,
|
||||
) -> Result<()> {
|
||||
fs::create_dir_all(&control_path)?;
|
||||
|
||||
let server = HttpServer::bind(bind_address)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to bind server: {}", e))?;
|
||||
println!("HTTP API server listening on {}", bind_address);
|
||||
|
||||
// Set up auth if password is provided
|
||||
let auth_password = if let Some(ref password) = password {
|
||||
println!(
|
||||
"HTTP API server listening on {} with Basic Auth enabled (any username)",
|
||||
bind_address
|
||||
);
|
||||
Some(password.clone())
|
||||
} else {
|
||||
println!(
|
||||
"HTTP API server listening on {} with no authentication",
|
||||
bind_address
|
||||
);
|
||||
None
|
||||
};
|
||||
|
||||
for req in server.incoming() {
|
||||
let control_path = control_path.clone();
|
||||
let static_path = static_path.clone();
|
||||
let auth_password = auth_password.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut req = match req {
|
||||
|
|
@ -229,6 +273,14 @@ pub fn start_server(
|
|||
|
||||
println!("{:?} {} (full URI: {})", method, path, full_uri);
|
||||
|
||||
// Check authentication if enabled (but skip /api/health)
|
||||
if let Some(ref expected_password) = auth_password {
|
||||
if path != "/api/health" && !check_basic_auth(&req, expected_password) {
|
||||
let _ = req.respond(unauthorized_response());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for static file serving first
|
||||
if method == &Method::GET && !path.starts_with("/api/") {
|
||||
if let Some(ref static_dir) = static_path {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||
let mut cleanup = false;
|
||||
let mut serve_address = None::<String>;
|
||||
let mut static_path = None::<String>;
|
||||
let mut password = None::<String>;
|
||||
let mut cmdline = Vec::<OsString>::new();
|
||||
|
||||
while let Some(param) = parser.param()? {
|
||||
|
|
@ -82,6 +83,9 @@ fn main() -> Result<(), anyhow::Error> {
|
|||
p if p.is_long("static-path") => {
|
||||
static_path = Some(parser.value()?);
|
||||
}
|
||||
p if p.is_long("password") => {
|
||||
password = Some(parser.value()?);
|
||||
}
|
||||
p if p.is_pos() => {
|
||||
cmdline.push(parser.value()?);
|
||||
}
|
||||
|
|
@ -107,6 +111,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||
println!(
|
||||
" --static-path <path> Path to static files directory for HTTP server"
|
||||
);
|
||||
println!(" --password <password> Enable basic auth with random username and specified password");
|
||||
println!(" --help Show this help message");
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -171,7 +176,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||
std::process::exit(0);
|
||||
})
|
||||
.unwrap();
|
||||
return crate::api_server::start_server(&addr, control_path, static_path);
|
||||
return crate::api_server::start_server(&addr, control_path, static_path, password);
|
||||
}
|
||||
|
||||
let exit_code = sessions::spawn_command(control_path, session_name, cmdline)?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue