1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//! Implementation of http basic authentication
//!
//! See [AuthBasic] for the most commonly-used data structure

use crate::{get_header, Rejection, ERR_DECODE, ERR_DEFAULT, ERR_WRONG_BASIC};
use async_trait::async_trait;
use axum_core::extract::FromRequestParts;
use base64::Engine;
use http::{request::Parts, StatusCode};

/// Basic authentication extractor, containing an identifier as well as an optional password
///
/// This is enabled via the `auth-basic` feature
///
/// # Example
///
/// Though this structure can be used like any other axum extractor, we recommend this pattern:
///
/// ```no_run
/// use axum_auth::AuthBasic;
///
/// /// Takes basic auth details and shows a message
/// async fn handler(AuthBasic((id, password)): AuthBasic) -> String {
///     if let Some(password) = password {
///         format!("User '{}' with password '{}'", id, password)
///     } else {
///         format!("User '{}' without password", id)
///     }
/// }
/// ```
///
/// # Errors
///
/// There are a few errors which this extractor can make. By default, all invalid responses are `400 BAD REQUEST` with one of these messages:
///
/// - \`Authorization\` header could not be decoded – The header couldn't be decoded, probably missing a colon
/// - \`Authorization\` header must be for basic authentication – Someone tried to use bearer auth instead of basic auth
/// - \`Authorization\` header is missing – The header was required but it wasn't found
/// - \`Authorization\` header contains invalid characters – The header couldn't be processed because of invalid characters
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct AuthBasic(pub (String, Option<String>));

#[async_trait]
impl<B> FromRequestParts<B> for AuthBasic
where
    B: Send + Sync,
{
    type Rejection = Rejection;

    async fn from_request_parts(parts: &mut Parts, _: &B) -> Result<Self, Self::Rejection> {
        Self::decode_request_parts(parts)
    }
}

impl AuthBasicCustom for AuthBasic {
    const ERROR_CODE: StatusCode = ERR_DEFAULT;
    const ERROR_OVERWRITE: Option<&'static str> = None;

    fn from_header(contents: (String, Option<String>)) -> Self {
        Self(contents)
    }
}

/// Custom extractor trait for basic auth allowing you to implement custom responses
///
/// This is enabled via the `auth-basic` feature
///
/// # Usage
///
/// To create your own basic auth extractor using this create, you have to:
///
/// 1. Make the extractor struct, something like `struct Example((String, Option<String>));`
/// 2. Implement [FromRequestParts] that links to step 3, copy and paste this from the example below
/// 3. Implement [AuthBasicCustom] to generate your extractor with your custom options, see the example below
///
/// Once you've completed these steps, you should have a new extractor which is just as easy to use as [AuthBasic] but has all of your custom configuration options inside of it!
///
/// # Example
///
/// This is what a typical custom extractor should look like in full, copy-paste this and edit it:
///
/// ```rust
/// use axum_auth::{AuthBasicCustom, Rejection};
/// use http::{request::Parts, StatusCode};
/// use axum::extract::FromRequestParts;
/// use async_trait::async_trait;
///
/// /// Your custom basic auth returning a fun 418 for errors
/// struct MyCustomBasicAuth((String, Option<String>));
///
/// // this is where you define your custom options
/// impl AuthBasicCustom for MyCustomBasicAuth {
///     const ERROR_CODE: StatusCode = StatusCode::IM_A_TEAPOT; // <-- define custom status code here
///     const ERROR_OVERWRITE: Option<&'static str> = None; // <-- define overwriting message here
///
///     fn from_header(contents: (String, Option<String>)) -> Self {
///         Self(contents)
///     }
/// }
///
/// // this is just boilerplate, copy-paste this
/// #[async_trait]
/// impl<B> FromRequestParts<B> for MyCustomBasicAuth
/// where
///     B: Send + Sync,
/// {
///     type Rejection = Rejection;
///
///     async fn from_request_parts(parts: &mut Parts, _: &B) -> Result<Self, Self::Rejection> {
///         Self::decode_request_parts(parts)
///     }
/// }
/// ```
///
/// Some notes about this example for some more insight:
///
/// - There's no reason for the [FromRequestParts] to ever change out of this pattern unless you're doing something special
/// - It's recommended to use the `struct BasicExample((String, Option<String>));` pattern because it makes using it from routes easy
pub trait AuthBasicCustom: Sized {
    /// Error code to use instead of the typical `400 BAD REQUEST` error
    const ERROR_CODE: StatusCode;

    /// Message to overwrite all default ones with if required, leave as [None] ideally
    const ERROR_OVERWRITE: Option<&'static str>;

    /// Converts provided header contents to new instance of self; you need to implement this
    ///
    /// # Example
    ///
    /// With the typical `struct BasicExample((String, Option<String>));` pattern of structures, this can be implemented like so:
    ///
    /// ```rust
    /// use axum_auth::AuthBasicCustom;
    /// use http::StatusCode;
    ///
    /// struct BasicExample((String, Option<String>));
    ///
    /// impl AuthBasicCustom for BasicExample {
    ///     const ERROR_CODE: StatusCode = StatusCode::BAD_REQUEST;
    ///     const ERROR_OVERWRITE: Option<&'static str> = None;
    ///
    ///     fn from_header(contents: (String, Option<String>)) -> Self {
    ///         Self(contents)
    ///     }
    /// }
    /// ```
    ///
    /// All this method does is let you put the automatically contents of the header into your resulting structure.
    fn from_header(contents: (String, Option<String>)) -> Self;

    /// Decodes bearer token content into new instance of self from axum body parts; this is automatically implemented
    fn decode_request_parts(req: &mut Parts) -> Result<Self, Rejection> {
        // Get authorization header
        let authorization = get_header(req, Self::ERROR_CODE)?;

        // Check that its well-formed basic auth then decode and return
        let split = authorization.split_once(' ');
        match split {
            Some((name, contents)) if name == "Basic" => {
                let decoded = decode(contents, (Self::ERROR_CODE, ERR_DECODE))?;
                Ok(Self::from_header(decoded))
            }
            _ => Err((Self::ERROR_CODE, ERR_WRONG_BASIC)),
        }
    }
}

/// Decodes the two parts of basic auth using the colon
fn decode(input: &str, err: Rejection) -> Result<(String, Option<String>), Rejection> {
    // Decode from base64 into a string
    let decoded = base64::engine::general_purpose::STANDARD
        .decode(input)
        .map_err(|_| err)?;
    let decoded = String::from_utf8(decoded).map_err(|_| err)?;

    // Return depending on if password is present
    Ok(if let Some((id, password)) = decoded.split_once(':') {
        (id.to_string(), Some(password.to_string()))
    } else {
        (decoded.to_string(), None)
    })
}