Skip to content

Commit 047845f

Browse files
committed
impl Authentication
1 parent 62e47e3 commit 047845f

File tree

4 files changed

+201
-25
lines changed

4 files changed

+201
-25
lines changed

Cargo.lock

Lines changed: 2 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: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ edition = "2021"
1212

1313
[dependencies]
1414
unicase = "^2.7"
15+
base64 = "^0.22.1"
16+
zeroize = { version = "^1.8.1", features = ["zeroize_derive"] }
17+
native-tls = { version = "^0.2", optional = true }
18+
rustls = { version = "^0.23", optional = true }
19+
rustls-pemfile = { version = "^2.1", optional = true }
20+
rustls-pki-types = { version = "^1.7", features = ["alloc"], optional = true }
21+
webpki = { version = "^0.22", optional = true }
22+
webpki-roots = { version = "^0.26", optional = true }
1523

1624
[features]
1725
default = ["native-tls"]
@@ -22,28 +30,3 @@ rust-tls = [
2230
"webpki-roots",
2331
"rustls-pemfile",
2432
]
25-
26-
[dependencies.native-tls]
27-
version = "^0.2"
28-
optional = true
29-
30-
[dependencies.rustls]
31-
version = "^0.23"
32-
optional = true
33-
34-
[dependencies.rustls-pemfile]
35-
version = "^2.1"
36-
optional = true
37-
38-
[dependencies.webpki]
39-
version = "^0.22"
40-
optional = true
41-
42-
[dependencies.webpki-roots]
43-
version = "^0.26"
44-
optional = true
45-
46-
[dependencies.rustls-pki-types]
47-
version = "^1.7"
48-
features = ["alloc"]
49-
optional = true

src/request.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::{
66
stream::{Stream, ThreadReceive, ThreadSend},
77
uri::Uri,
88
};
9+
use base64::engine::{general_purpose::URL_SAFE, Engine};
910
use std::{
1011
convert::TryFrom,
1112
fmt,
@@ -15,6 +16,7 @@ use std::{
1516
thread,
1617
time::{Duration, Instant},
1718
};
19+
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
1820

1921
const CR_LF: &str = "\r\n";
2022
const DEFAULT_REDIRECT_LIMIT: usize = 5;
@@ -85,6 +87,79 @@ impl fmt::Display for HttpVersion {
8587
}
8688
}
8789

90+
/// Authentication details:
91+
/// - Basic: username and password
92+
/// - Bearer: token
93+
#[derive(Debug, PartialEq, Zeroize, ZeroizeOnDrop)]
94+
pub struct Authentication(AuthenticationType);
95+
96+
impl Authentication {
97+
/// Creates a new `Authentication` of type `Basic`.
98+
pub fn basic<T, U>(username: &T, password: &U) -> Authentication
99+
where
100+
T: ToString + ?Sized,
101+
U: ToString + ?Sized,
102+
{
103+
Authentication(AuthenticationType::Basic {
104+
username: username.to_string(),
105+
password: password.to_string(),
106+
})
107+
}
108+
109+
/// Creates a new `Authentication` of type `Bearer`
110+
pub fn bearer<T>(token: &T) -> Authentication
111+
where
112+
T: ToString + ?Sized,
113+
{
114+
Authentication(AuthenticationType::Bearer(token.to_string()))
115+
}
116+
117+
/// Generates a HTTP Authorization header. Returns `key` & `value` pair.
118+
/// - Basic: uses base64 encoding on provided credentials
119+
/// - Bearer: uses token as is
120+
pub fn header(&self) -> (String, String) {
121+
let key = "Authorization".to_string();
122+
let val = String::with_capacity(200) + self.0.scheme() + " " + &self.0.credentials();
123+
124+
(key, val)
125+
}
126+
}
127+
128+
/// Authentication types
129+
#[derive(Debug, PartialEq, Zeroize, ZeroizeOnDrop)]
130+
enum AuthenticationType {
131+
Basic { username: String, password: String },
132+
Bearer(String),
133+
}
134+
135+
impl AuthenticationType {
136+
/// Returns scheme
137+
const fn scheme(&self) -> &str {
138+
use AuthenticationType::*;
139+
140+
match self {
141+
Basic {
142+
username: _,
143+
password: _,
144+
} => "Basic",
145+
Bearer(_) => "Bearer",
146+
}
147+
}
148+
149+
/// Returns encoded credentials
150+
fn credentials(&self) -> Zeroizing<String> {
151+
use AuthenticationType::*;
152+
153+
match self {
154+
Basic { username, password } => {
155+
let credentials = Zeroizing::new(username.to_string() + ":" + password);
156+
Zeroizing::new(URL_SAFE.encode(credentials.as_bytes()))
157+
}
158+
Bearer(token) => Zeroizing::new(token.to_string()),
159+
}
160+
}
161+
}
162+
88163
/// Allows to control redirects
89164
#[derive(Debug, PartialEq, Clone, Copy)]
90165
pub enum RedirectPolicy<F> {
@@ -258,6 +333,29 @@ impl<'a> RequestMessage<'a> {
258333
self
259334
}
260335

336+
/// Adds an authorization header to existing headers
337+
///
338+
/// # Examples
339+
/// ```
340+
/// use std::convert::TryFrom;
341+
/// use http_req::{request::{RequestMessage, Authentication}, response::Headers, uri::Uri};
342+
///
343+
/// let addr = Uri::try_from("https://www.rust-lang.org/learn").unwrap();
344+
///
345+
/// let request_msg = RequestMessage::new(&addr)
346+
/// .authentication(Authentication::bearer("secret456token123"));
347+
/// ```
348+
pub fn authentication<T>(&mut self, auth: T) -> &mut Self
349+
where
350+
Authentication: From<T>,
351+
{
352+
let auth = Authentication::from(auth);
353+
let (key, val) = auth.header();
354+
355+
self.headers.insert_raw(key, val);
356+
self
357+
}
358+
261359
/// Sets the body for request
262360
///
263361
/// # Examples
@@ -456,6 +554,26 @@ impl<'a> Request<'a> {
456554
self
457555
}
458556

557+
/// Adds an authorization header to existing headers.
558+
///
559+
/// # Examples
560+
/// ```
561+
/// use std::convert::TryFrom;
562+
/// use http_req::{request::{RequestMessage, Authentication}, response::Headers, uri::Uri};
563+
///
564+
/// let addr = Uri::try_from("https://www.rust-lang.org/learn").unwrap();
565+
///
566+
/// let request_msg = RequestMessage::new(&addr)
567+
/// .authentication(Authentication::bearer("secret456token123"));
568+
/// ```
569+
pub fn authentication<T>(&mut self, auth: T) -> &mut Self
570+
where
571+
Authentication: From<T>,
572+
{
573+
self.messsage.authentication(auth);
574+
self
575+
}
576+
459577
/// Sets the body for request.
460578
///
461579
/// # Examples
@@ -784,6 +902,43 @@ mod tests {
784902
assert_eq!(&format!("{}", METHOD), "HEAD");
785903
}
786904

905+
#[test]
906+
fn authentication_basic() {
907+
let auth = Authentication::basic("user", "password123");
908+
assert_eq!(
909+
auth,
910+
Authentication(AuthenticationType::Basic {
911+
username: "user".to_string(),
912+
password: "password123".to_string()
913+
})
914+
);
915+
}
916+
917+
#[test]
918+
fn authentication_baerer() {
919+
let auth = Authentication::bearer("456secret123token");
920+
assert_eq!(
921+
auth,
922+
Authentication(AuthenticationType::Bearer("456secret123token".to_string()))
923+
);
924+
}
925+
926+
#[test]
927+
fn authentication_header() {
928+
{
929+
let auth = Authentication::basic("user", "password123");
930+
let (key, val) = auth.header();
931+
assert_eq!(key, "Authorization".to_string());
932+
assert_eq!(val, "Basic dXNlcjpwYXNzd29yZDEyMw==".to_string());
933+
}
934+
{
935+
let auth = Authentication::bearer("456secret123token");
936+
let (key, val) = auth.header();
937+
assert_eq!(key, "Authorization".to_string());
938+
assert_eq!(val, "Bearer 456secret123token".to_string());
939+
}
940+
}
941+
787942
#[test]
788943
fn request_m_new() {
789944
RequestMessage::new(&Uri::try_from(URI).unwrap());
@@ -831,6 +986,24 @@ mod tests {
831986
assert_eq!(req.headers, expect_headers);
832987
}
833988

989+
#[test]
990+
fn request_m_authentication() {
991+
let uri = Uri::try_from(URI).unwrap();
992+
let mut req = RequestMessage::new(&uri);
993+
let token = "456secret123token";
994+
let k = "Authorization";
995+
let v = "Bearer ".to_string() + token;
996+
997+
let mut expect_headers = Headers::new();
998+
expect_headers.insert("Host", "doc.rust-lang.org");
999+
expect_headers.insert("User-Agent", "http_req/0.13.0");
1000+
expect_headers.insert(k, &v);
1001+
1002+
let req = req.authentication(Authentication::bearer(token));
1003+
1004+
assert_eq!(req.headers, expect_headers);
1005+
}
1006+
8341007
#[test]
8351008
fn request_m_body() {
8361009
let uri = Uri::try_from(URI).unwrap();

src/response.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,24 @@ impl Headers {
355355
self.0.insert(Ascii::new(key.to_string()), val.to_string())
356356
}
357357

358+
/// Inserts key-value pair into the headers and takes ownership over them.
359+
///
360+
/// If the headers did not have this key present, None is returned.
361+
///
362+
/// If the headers did have this key present, the value is updated, and the old value is returned.
363+
/// The key is not updated, though; this matters for types that can be == without being identical.
364+
///
365+
/// # Examples
366+
/// ```
367+
/// use http_req::response::Headers;
368+
///
369+
/// let mut headers = Headers::new();
370+
/// headers.insert_raw("Accept-Language".to_string(), "en-US".to_string());
371+
/// ```
372+
pub fn insert_raw(&mut self, key: String, val: String) -> Option<String> {
373+
self.0.insert(Ascii::new(key), val)
374+
}
375+
358376
/// Creates default headers for a HTTP request
359377
///
360378
/// # Examples

0 commit comments

Comments
 (0)