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)]
11
12#[cfg(test)]
23#[doc = include_str!("../README.md")]
24mod readme {}
25
26use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
27use trillium::{
28 Conn, Handler,
29 KnownHeaderName::{Authorization, WwwAuthenticate},
30 Status,
31};
32
33#[derive(Clone, Debug)]
35pub struct BasicAuth {
36 credentials: Credentials,
37 realm: Option<String>,
38
39 expected_header: String,
41 www_authenticate: String,
42}
43
44#[derive(Clone, Debug, PartialEq, Eq, fieldwork::Fieldwork)]
46#[fieldwork(get)]
47pub struct Credentials {
48 username: String,
50
51 password: String,
53}
54
55impl Credentials {
56 fn new(username: &str, password: &str) -> Self {
57 Self {
58 username: String::from(username),
59 password: String::from(password),
60 }
61 }
62
63 fn expected_header(&self) -> String {
64 format!(
65 "Basic {}",
66 BASE64.encode(format!("{}:{}", self.username, self.password))
67 )
68 }
69
70 }
95
96impl BasicAuth {
97 pub fn new(username: &str, password: &str) -> Self {
99 let credentials = Credentials::new(username, password);
100 let expected_header = credentials.expected_header();
101 let realm = None;
102 Self {
103 expected_header,
104 credentials,
105 realm,
106 www_authenticate: String::from("Basic"),
107 }
108 }
109
110 pub fn with_realm(mut self, realm: &str) -> Self {
112 self.www_authenticate = format!("Basic realm=\"{}\"", realm.replace('\"', "\\\""));
113 self.realm = Some(String::from(realm));
114 self
115 }
116
117 fn is_allowed(&self, conn: &Conn) -> bool {
118 conn.request_headers().get_str(Authorization) == Some(&*self.expected_header)
119 }
120
121 fn deny(&self, conn: Conn) -> Conn {
122 conn.with_status(Status::Unauthorized)
123 .with_response_header(WwwAuthenticate, self.www_authenticate.clone())
124 .halt()
125 }
126}
127
128impl Handler for BasicAuth {
129 async fn run(&self, conn: Conn) -> Conn {
130 if self.is_allowed(&conn) {
131 conn.with_state(self.credentials.clone())
132 } else {
133 self.deny(conn)
134 }
135 }
136}