use base64;
#[cfg(feature = "log")]
use log::trace;
use rocket::http::Status;
use rocket::outcome::Outcome;
use rocket::request::{self, FromRequest, Request};
#[derive(Debug)]
pub enum BasicAuthError {
BadCount,
Invalid,
}
fn decode_to_creds<T: Into<String>>(base64_encoded: T) -> Option<(String, String)> {
let decoded_creds = match base64::decode(base64_encoded.into()) {
Ok(cred_bytes) => String::from_utf8(cred_bytes).unwrap(),
Err(_) => return None,
};
if let Some((username, password)) = decoded_creds.split_once(":") {
#[cfg(feature = "log")]
{
const TRUNCATE_LEN: usize = 64;
let mut s = username.to_string();
let fmt_id = if username.len() > TRUNCATE_LEN {
s.truncate(TRUNCATE_LEN);
format!("{}.. (truncated to {})", s, TRUNCATE_LEN)
} else {
s
};
trace!(
"Decoded basic authentication credentials for user of id {}",
fmt_id
);
}
Some((username.to_owned(), password.to_owned()))
} else {
None
}
}
#[derive(Debug)]
pub struct BasicAuth {
pub username: String,
pub password: String,
}
impl BasicAuth {
pub fn new<T: Into<String>>(auth_header: T) -> Option<Self> {
let key = auth_header.into();
if key.len() < 7 || &key[..6] != "Basic " {
return None;
}
let (username, password) = decode_to_creds(&key[6..])?;
Some(Self { username, password })
}
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for BasicAuth {
type Error = BasicAuthError;
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
#[cfg(feature = "log")]
trace!("Basic authorization requested, starting decode process");
let keys: Vec<_> = request.headers().get("Authorization").collect();
match keys.len() {
0 => Outcome::Forward(Status::Unauthorized),
1 => match BasicAuth::new(keys[0]) {
Some(auth_header) => Outcome::Success(auth_header),
None => Outcome::Error((Status::BadRequest, BasicAuthError::Invalid)),
},
_ => Outcome::Error((Status::BadRequest, BasicAuthError::BadCount)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_to_creds_check() {
assert_eq!(
decode_to_creds("bmFtZTpwYXNzd29yZA=="),
Some(("name".to_string(), "password".to_string()))
);
assert_eq!(
decode_to_creds("bmFtZTpwYXNzOndvcmQ="),
Some(("name".to_string(), "pass:word".to_string()))
);
assert_eq!(
decode_to_creds("ZW1wdHlwYXNzOg=="),
Some(("emptypass".to_string(), "".to_string()))
);
assert_eq!(
decode_to_creds("Og=="),
Some(("".to_string(), "".to_string()))
);
assert_eq!(decode_to_creds("bm9jb2xvbg=="), None);
}
}