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)
})
}