diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2021-02-16 13:50:27 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-16 13:50:27 +1100 |
commit | 879897ada6247e1bb19905b12e5d2fd99965089e (patch) | |
tree | 6ca174ec6b6408cfd21746ca1703d188782c4094 /cli/auth_tokens.rs | |
parent | ccd6ee5c2394418c078f1a1be9e5cc1012829cbc (diff) |
feat(cli): support auth tokens for accessing private modules (#9508)
Closes #5239
Diffstat (limited to 'cli/auth_tokens.rs')
-rw-r--r-- | cli/auth_tokens.rs | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/cli/auth_tokens.rs b/cli/auth_tokens.rs new file mode 100644 index 000000000..f52f564e1 --- /dev/null +++ b/cli/auth_tokens.rs @@ -0,0 +1,144 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::ModuleSpecifier; +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AuthToken { + host: String, + token: String, +} + +impl fmt::Display for AuthToken { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Bearer {}", self.token) + } +} + +/// A structure which contains bearer tokens that can be used when sending +/// requests to websites, intended to authorize access to private resources +/// such as remote modules. +#[derive(Debug, Clone)] +pub struct AuthTokens(Vec<AuthToken>); + +impl AuthTokens { + /// Create a new set of tokens based on the provided string. It is intended + /// that the string be the value of an environment variable and the string is + /// parsed for token values. The string is expected to be a semi-colon + /// separated string, where each value is `{token}@{hostname}`. + pub fn new(maybe_tokens_str: Option<String>) -> Self { + let mut tokens = Vec::new(); + if let Some(tokens_str) = maybe_tokens_str { + 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 host = pair[0].to_lowercase(); + tokens.push(AuthToken { host, token }); + } else { + error!("Badly formed auth token discarded."); + } + } + debug!("Parsed {} auth token(s).", tokens.len()); + } + + Self(tokens) + } + + /// Attempt to match the provided specifier to the tokens in the set. The + /// matching occurs from the right of the hostname plus port, irrespective of + /// scheme. For example `https://www.deno.land:8080/` would match a token + /// with a host value of `deno.land:8080` but not match `www.deno.land`. The + /// matching is case insensitive. + pub fn get(&self, specifier: &ModuleSpecifier) -> Option<AuthToken> { + self.0.iter().find_map(|t| { + let url = specifier.as_url(); + let hostname = if let Some(port) = url.port() { + format!("{}:{}", url.host_str()?, port) + } else { + url.host_str()?.to_string() + }; + if hostname.to_lowercase().ends_with(&t.host) { + Some(t.clone()) + } else { + None + } + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_auth_token() { + let auth_tokens = AuthTokens::new(Some("abc123@deno.land".to_string())); + let fixture = + ModuleSpecifier::resolve_url("https://deno.land/x/mod.ts").unwrap(); + assert_eq!( + auth_tokens.get(&fixture).unwrap().to_string(), + "Bearer abc123" + ); + let fixture = + ModuleSpecifier::resolve_url("https://www.deno.land/x/mod.ts").unwrap(); + assert_eq!( + auth_tokens.get(&fixture).unwrap().to_string(), + "Bearer abc123".to_string() + ); + let fixture = + ModuleSpecifier::resolve_url("http://127.0.0.1:8080/x/mod.ts").unwrap(); + assert_eq!(auth_tokens.get(&fixture), None); + let fixture = + ModuleSpecifier::resolve_url("https://deno.land.example.com/x/mod.ts") + .unwrap(); + assert_eq!(auth_tokens.get(&fixture), None); + let fixture = + ModuleSpecifier::resolve_url("https://deno.land:8080/x/mod.ts").unwrap(); + assert_eq!(auth_tokens.get(&fixture), None); + } + + #[test] + fn test_auth_tokens_multiple() { + let auth_tokens = + AuthTokens::new(Some("abc123@deno.land;def456@example.com".to_string())); + let fixture = + ModuleSpecifier::resolve_url("https://deno.land/x/mod.ts").unwrap(); + assert_eq!( + auth_tokens.get(&fixture).unwrap().to_string(), + "Bearer abc123".to_string() + ); + let fixture = + ModuleSpecifier::resolve_url("http://example.com/a/file.ts").unwrap(); + assert_eq!( + auth_tokens.get(&fixture).unwrap().to_string(), + "Bearer def456".to_string() + ); + } + + #[test] + fn test_auth_tokens_port() { + let auth_tokens = + AuthTokens::new(Some("abc123@deno.land:8080".to_string())); + let fixture = + ModuleSpecifier::resolve_url("https://deno.land/x/mod.ts").unwrap(); + assert_eq!(auth_tokens.get(&fixture), None); + let fixture = + ModuleSpecifier::resolve_url("http://deno.land:8080/x/mod.ts").unwrap(); + assert_eq!( + auth_tokens.get(&fixture).unwrap().to_string(), + "Bearer abc123".to_string() + ); + } + + #[test] + fn test_auth_tokens_contain_at() { + let auth_tokens = AuthTokens::new(Some("abc@123@deno.land".to_string())); + let fixture = + ModuleSpecifier::resolve_url("https://deno.land/x/mod.ts").unwrap(); + assert_eq!( + auth_tokens.get(&fixture).unwrap().to_string(), + "Bearer abc@123".to_string() + ); + } +} |