Skip to content

Commit

Permalink
impl Authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
jayjamesjay committed Aug 31, 2024
1 parent 62e47e3 commit 047845f
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 25 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 8 additions & 25 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ edition = "2021"

[dependencies]
unicase = "^2.7"
base64 = "^0.22.1"
zeroize = { version = "^1.8.1", features = ["zeroize_derive"] }
native-tls = { version = "^0.2", optional = true }
rustls = { version = "^0.23", optional = true }
rustls-pemfile = { version = "^2.1", optional = true }
rustls-pki-types = { version = "^1.7", features = ["alloc"], optional = true }
webpki = { version = "^0.22", optional = true }
webpki-roots = { version = "^0.26", optional = true }

[features]
default = ["native-tls"]
Expand All @@ -22,28 +30,3 @@ rust-tls = [
"webpki-roots",
"rustls-pemfile",
]

[dependencies.native-tls]
version = "^0.2"
optional = true

[dependencies.rustls]
version = "^0.23"
optional = true

[dependencies.rustls-pemfile]
version = "^2.1"
optional = true

[dependencies.webpki]
version = "^0.22"
optional = true

[dependencies.webpki-roots]
version = "^0.26"
optional = true

[dependencies.rustls-pki-types]
version = "^1.7"
features = ["alloc"]
optional = true
173 changes: 173 additions & 0 deletions src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
stream::{Stream, ThreadReceive, ThreadSend},
uri::Uri,
};
use base64::engine::{general_purpose::URL_SAFE, Engine};
use std::{
convert::TryFrom,
fmt,
Expand All @@ -15,6 +16,7 @@ use std::{
thread,
time::{Duration, Instant},
};
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};

const CR_LF: &str = "\r\n";
const DEFAULT_REDIRECT_LIMIT: usize = 5;
Expand Down Expand Up @@ -85,6 +87,79 @@ impl fmt::Display for HttpVersion {
}
}

/// Authentication details:
/// - Basic: username and password
/// - Bearer: token
#[derive(Debug, PartialEq, Zeroize, ZeroizeOnDrop)]
pub struct Authentication(AuthenticationType);

impl Authentication {
/// Creates a new `Authentication` of type `Basic`.
pub fn basic<T, U>(username: &T, password: &U) -> Authentication
where
T: ToString + ?Sized,
U: ToString + ?Sized,
{
Authentication(AuthenticationType::Basic {
username: username.to_string(),
password: password.to_string(),
})
}

/// Creates a new `Authentication` of type `Bearer`
pub fn bearer<T>(token: &T) -> Authentication
where
T: ToString + ?Sized,
{
Authentication(AuthenticationType::Bearer(token.to_string()))
}

/// Generates a HTTP Authorization header. Returns `key` & `value` pair.
/// - Basic: uses base64 encoding on provided credentials
/// - Bearer: uses token as is
pub fn header(&self) -> (String, String) {
let key = "Authorization".to_string();
let val = String::with_capacity(200) + self.0.scheme() + " " + &self.0.credentials();

(key, val)
}
}

/// Authentication types
#[derive(Debug, PartialEq, Zeroize, ZeroizeOnDrop)]
enum AuthenticationType {
Basic { username: String, password: String },
Bearer(String),
}

impl AuthenticationType {
/// Returns scheme
const fn scheme(&self) -> &str {
use AuthenticationType::*;

match self {
Basic {
username: _,
password: _,
} => "Basic",
Bearer(_) => "Bearer",
}
}

/// Returns encoded credentials
fn credentials(&self) -> Zeroizing<String> {
use AuthenticationType::*;

match self {
Basic { username, password } => {
let credentials = Zeroizing::new(username.to_string() + ":" + password);
Zeroizing::new(URL_SAFE.encode(credentials.as_bytes()))
}
Bearer(token) => Zeroizing::new(token.to_string()),
}
}
}

/// Allows to control redirects
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum RedirectPolicy<F> {
Expand Down Expand Up @@ -258,6 +333,29 @@ impl<'a> RequestMessage<'a> {
self
}

/// Adds an authorization header to existing headers
///
/// # Examples
/// ```
/// use std::convert::TryFrom;
/// use http_req::{request::{RequestMessage, Authentication}, response::Headers, uri::Uri};
///
/// let addr = Uri::try_from("https://www.rust-lang.org/learn").unwrap();
///
/// let request_msg = RequestMessage::new(&addr)
/// .authentication(Authentication::bearer("secret456token123"));
/// ```
pub fn authentication<T>(&mut self, auth: T) -> &mut Self
where
Authentication: From<T>,
{
let auth = Authentication::from(auth);
let (key, val) = auth.header();

self.headers.insert_raw(key, val);
self
}

/// Sets the body for request
///
/// # Examples
Expand Down Expand Up @@ -456,6 +554,26 @@ impl<'a> Request<'a> {
self
}

/// Adds an authorization header to existing headers.
///
/// # Examples
/// ```
/// use std::convert::TryFrom;
/// use http_req::{request::{RequestMessage, Authentication}, response::Headers, uri::Uri};
///
/// let addr = Uri::try_from("https://www.rust-lang.org/learn").unwrap();
///
/// let request_msg = RequestMessage::new(&addr)
/// .authentication(Authentication::bearer("secret456token123"));
/// ```
pub fn authentication<T>(&mut self, auth: T) -> &mut Self
where
Authentication: From<T>,
{
self.messsage.authentication(auth);
self
}

/// Sets the body for request.
///
/// # Examples
Expand Down Expand Up @@ -784,6 +902,43 @@ mod tests {
assert_eq!(&format!("{}", METHOD), "HEAD");
}

#[test]
fn authentication_basic() {
let auth = Authentication::basic("user", "password123");
assert_eq!(
auth,
Authentication(AuthenticationType::Basic {
username: "user".to_string(),
password: "password123".to_string()
})
);
}

#[test]
fn authentication_baerer() {
let auth = Authentication::bearer("456secret123token");
assert_eq!(
auth,
Authentication(AuthenticationType::Bearer("456secret123token".to_string()))
);
}

#[test]
fn authentication_header() {
{
let auth = Authentication::basic("user", "password123");
let (key, val) = auth.header();
assert_eq!(key, "Authorization".to_string());
assert_eq!(val, "Basic dXNlcjpwYXNzd29yZDEyMw==".to_string());
}
{
let auth = Authentication::bearer("456secret123token");
let (key, val) = auth.header();
assert_eq!(key, "Authorization".to_string());
assert_eq!(val, "Bearer 456secret123token".to_string());
}
}

#[test]
fn request_m_new() {
RequestMessage::new(&Uri::try_from(URI).unwrap());
Expand Down Expand Up @@ -831,6 +986,24 @@ mod tests {
assert_eq!(req.headers, expect_headers);
}

#[test]
fn request_m_authentication() {
let uri = Uri::try_from(URI).unwrap();
let mut req = RequestMessage::new(&uri);
let token = "456secret123token";
let k = "Authorization";
let v = "Bearer ".to_string() + token;

let mut expect_headers = Headers::new();
expect_headers.insert("Host", "doc.rust-lang.org");
expect_headers.insert("User-Agent", "http_req/0.13.0");
expect_headers.insert(k, &v);

let req = req.authentication(Authentication::bearer(token));

assert_eq!(req.headers, expect_headers);
}

#[test]
fn request_m_body() {
let uri = Uri::try_from(URI).unwrap();
Expand Down
18 changes: 18 additions & 0 deletions src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,24 @@ impl Headers {
self.0.insert(Ascii::new(key.to_string()), val.to_string())
}

/// Inserts key-value pair into the headers and takes ownership over them.
///
/// If the headers did not have this key present, None is returned.
///
/// If the headers did have this key present, the value is updated, and the old value is returned.
/// The key is not updated, though; this matters for types that can be == without being identical.
///
/// # Examples
/// ```
/// use http_req::response::Headers;
///
/// let mut headers = Headers::new();
/// headers.insert_raw("Accept-Language".to_string(), "en-US".to_string());
/// ```
pub fn insert_raw(&mut self, key: String, val: String) -> Option<String> {
self.0.insert(Ascii::new(key), val)
}

/// Creates default headers for a HTTP request
///
/// # Examples
Expand Down

0 comments on commit 047845f

Please sign in to comment.