trillium_basic_auth/
lib.rs1#![forbid(unsafe_code)]
2#![deny(
3 clippy::dbg_macro,
4 missing_copy_implementations,
5 rustdoc::missing_crate_level_docs,
6 missing_debug_implementations,
7 missing_docs,
8 nonstandard_style,
9 unused_qualifications
10)]
11use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
23use trillium::{
24 async_trait, Conn, Handler,
25 KnownHeaderName::{Authorization, WwwAuthenticate},
26 Status,
27};
28
29#[derive(Clone, Debug)]
31pub struct BasicAuth {
32 credentials: Credentials,
33 realm: Option<String>,
34
35 expected_header: String,
37 www_authenticate: String,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct Credentials {
43 username: String,
44 password: String,
45}
46
47impl Credentials {
48 fn new(username: &str, password: &str) -> Self {
49 Self {
50 username: String::from(username),
51 password: String::from(password),
52 }
53 }
54
55 fn expected_header(&self) -> String {
56 format!(
57 "Basic {}",
58 BASE64.encode(format!("{}:{}", self.username, self.password))
59 )
60 }
61
62 }
87
88impl BasicAuth {
89 pub fn new(username: &str, password: &str) -> Self {
91 let credentials = Credentials::new(username, password);
92 let expected_header = credentials.expected_header();
93 let realm = None;
94 Self {
95 expected_header,
96 credentials,
97 realm,
98 www_authenticate: String::from("Basic"),
99 }
100 }
101
102 pub fn with_realm(mut self, realm: &str) -> Self {
104 self.www_authenticate = format!("Basic realm=\"{}\"", realm.replace('\"', "\\\""));
105 self.realm = Some(String::from(realm));
106 self
107 }
108
109 fn is_allowed(&self, conn: &Conn) -> bool {
110 conn.request_headers().get_str(Authorization) == Some(&*self.expected_header)
111 }
112
113 fn deny(&self, conn: Conn) -> Conn {
114 conn.with_status(Status::Unauthorized)
115 .with_response_header(WwwAuthenticate, self.www_authenticate.clone())
116 .halt()
117 }
118}
119
120#[async_trait]
121impl Handler for BasicAuth {
122 async fn run(&self, conn: Conn) -> Conn {
123 if self.is_allowed(&conn) {
124 conn.with_state(self.credentials.clone())
125 } else {
126 self.deny(conn)
127 }
128 }
129}