diff --git a/Cargo.lock b/Cargo.lock index 34cbbf275e..09eb674c01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3838,6 +3838,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-stream", + "base64 0.13.0", "bytes", "futures", "hyper", diff --git a/cli/auth_tokens.rs b/cli/auth_tokens.rs index 83c97e641e..c81c296f37 100644 --- a/cli/auth_tokens.rs +++ b/cli/auth_tokens.rs @@ -5,15 +5,27 @@ use log::debug; use log::error; use std::fmt; +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AuthTokenData { + Bearer(String), + Basic { username: String, password: String }, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct AuthToken { host: String, - token: String, + token: AuthTokenData, } impl fmt::Display for AuthToken { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Bearer {}", self.token) + match &self.token { + AuthTokenData::Bearer(token) => write!(f, "Bearer {}", token), + AuthTokenData::Basic { username, password } => { + let credentials = format!("{}:{}", username, password); + write!(f, "Basic {}", base64::encode(credentials)) + } + } } } @@ -34,9 +46,22 @@ impl AuthTokens { for token_str in tokens_str.split(';') { if token_str.contains('@') { let pair: Vec<&str> = token_str.rsplitn(2, '@').collect(); - let token = pair[1].to_string(); + let token = pair[1]; let host = pair[0].to_lowercase(); - tokens.push(AuthToken { host, token }); + if token.contains(':') { + let pair: Vec<&str> = token.rsplitn(2, ':').collect(); + let username = pair[1].to_string(); + let password = pair[0].to_string(); + tokens.push(AuthToken { + host, + token: AuthTokenData::Basic { username, password }, + }) + } else { + tokens.push(AuthToken { + host, + token: AuthTokenData::Bearer(token.to_string()), + }); + } } else { error!("Badly formed auth token discarded."); } @@ -133,4 +158,26 @@ mod tests { "Bearer abc@123".to_string() ); } + + #[test] + fn test_auth_token_basic() { + let auth_tokens = AuthTokens::new(Some("abc:123@deno.land".to_string())); + let fixture = resolve_url("https://deno.land/x/mod.ts").unwrap(); + assert_eq!( + auth_tokens.get(&fixture).unwrap().to_string(), + "Basic YWJjOjEyMw==" + ); + let fixture = resolve_url("https://www.deno.land/x/mod.ts").unwrap(); + assert_eq!( + auth_tokens.get(&fixture).unwrap().to_string(), + "Basic YWJjOjEyMw==".to_string() + ); + let fixture = resolve_url("http://127.0.0.1:8080/x/mod.ts").unwrap(); + assert_eq!(auth_tokens.get(&fixture), None); + let fixture = + resolve_url("https://deno.land.example.com/x/mod.ts").unwrap(); + assert_eq!(auth_tokens.get(&fixture), None); + let fixture = resolve_url("https://deno.land:8080/x/mod.ts").unwrap(); + assert_eq!(auth_tokens.get(&fixture), None); + } } diff --git a/cli/tests/integration/mod.rs b/cli/tests/integration/mod.rs index 7d13c78316..4ada4ef54e 100644 --- a/cli/tests/integration/mod.rs +++ b/cli/tests/integration/mod.rs @@ -1077,6 +1077,54 @@ fn js_unit_tests() { assert!(status.success()); } +#[test] +fn basic_auth_tokens() { + let _g = util::http_server(); + + let output = util::deno_cmd() + .current_dir(util::root_path()) + .arg("run") + .arg("http://127.0.0.1:4554/001_hello.js") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + + assert!(!output.status.success()); + + let stdout_str = std::str::from_utf8(&output.stdout).unwrap().trim(); + assert!(stdout_str.is_empty()); + + let stderr_str = std::str::from_utf8(&output.stderr).unwrap().trim(); + eprintln!("{}", stderr_str); + + assert!(stderr_str.contains( + "Import 'http://127.0.0.1:4554/001_hello.js' failed: 404 Not Found" + )); + + let output = util::deno_cmd() + .current_dir(util::root_path()) + .arg("run") + .arg("http://127.0.0.1:4554/001_hello.js") + .env("DENO_AUTH_TOKENS", "testuser123:testpassabc@127.0.0.1:4554") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + + let stderr_str = std::str::from_utf8(&output.stderr).unwrap().trim(); + eprintln!("{}", stderr_str); + + assert!(output.status.success()); + + let stdout_str = std::str::from_utf8(&output.stdout).unwrap().trim(); + assert_eq!(util::strip_ansi_codes(stdout_str), "Hello World"); +} + #[tokio::test] async fn listen_tls_alpn() { // TLS streams require the presence of an ambient local task set to gracefully diff --git a/test_util/Cargo.toml b/test_util/Cargo.toml index 5e2a41ed8f..b33f389a68 100644 --- a/test_util/Cargo.toml +++ b/test_util/Cargo.toml @@ -14,6 +14,7 @@ path = "src/test_server.rs" [dependencies] anyhow = "1.0.43" async-stream = "0.3.2" +base64 = "0.13.0" bytes = "1.1.0" futures = "0.3.16" hyper = { version = "0.14.12", features = ["server", "http1", "runtime"] } diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 46679b98d1..3d9913576a 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -51,6 +51,8 @@ pub mod lsp; const PORT: u16 = 4545; const TEST_AUTH_TOKEN: &str = "abcdef123456789"; +const TEST_BASIC_AUTH_USERNAME: &str = "testuser123"; +const TEST_BASIC_AUTH_PASSWORD: &str = "testpassabc"; const REDIRECT_PORT: u16 = 4546; const ANOTHER_REDIRECT_PORT: u16 = 4547; const DOUBLE_REDIRECTS_PORT: u16 = 4548; @@ -58,6 +60,7 @@ const INF_REDIRECTS_PORT: u16 = 4549; const REDIRECT_ABSOLUTE_PORT: u16 = 4550; const AUTH_REDIRECT_PORT: u16 = 4551; const TLS_CLIENT_AUTH_PORT: u16 = 4552; +const BASIC_AUTH_REDIRECT_PORT: u16 = 4554; const HTTPS_PORT: u16 = 5545; const HTTPS_CLIENT_AUTH_PORT: u16 = 5552; const WS_PORT: u16 = 4242; @@ -229,6 +232,29 @@ async fn auth_redirect(req: Request
) -> hyper::Result