actix_web_security/authentication/error/
error_handler.rs

1//! Authentication errors that occur in the crate's source code must be handled.
2//! Due to rusts limited capabilites to register / overwrite code from other traits
3//! some helper functions are provided to customize the error responses.
4
5use std::collections::HashMap;
6
7use actix_web::dev::HttpResponseBuilder;
8use actix_web::http::{header, StatusCode};
9use actix_web::{error, HttpResponse};
10use once_cell::sync::Lazy;
11
12use crate::authentication::error::error_type::AuthenticationError;
13
14// Status code must be 100 <= code <= 1000
15static AUTH_ERROR_STATUS_CODE_MAPPING: Lazy<HashMap<AuthenticationError, u16>> = Lazy::new(|| {
16    let mut error_codes: HashMap<AuthenticationError, u16> = HashMap::new();
17    add_env_error_code(AuthenticationError::InvalidAuthentication, &mut error_codes);
18    add_env_error_code(AuthenticationError::InvalidToken, &mut error_codes);
19    add_env_error_code(
20        AuthenticationError::InvalidAuthorizationHeader,
21        &mut error_codes,
22    );
23    add_env_error_code(AuthenticationError::UsernameNotFound, &mut error_codes);
24    add_env_error_code(
25        AuthenticationError::AuthorizationHeaderNotSet,
26        &mut error_codes,
27    );
28    error_codes
29});
30
31static AUTH_ERROR_MESSAGE_MAPPING: Lazy<HashMap<AuthenticationError, String>> = Lazy::new(|| {
32    let mut error_messages: HashMap<AuthenticationError, String> = HashMap::new();
33    add_env_error_message(
34        AuthenticationError::InvalidAuthentication,
35        "invalid authentication".to_string(),
36        &mut error_messages,
37    );
38    add_env_error_message(
39        AuthenticationError::InvalidToken,
40        "access denied".to_string(),
41        &mut error_messages,
42    );
43    add_env_error_message(
44        AuthenticationError::InvalidAuthorizationHeader,
45        "invalid authorization header".to_string(),
46        &mut error_messages,
47    );
48    add_env_error_message(
49        AuthenticationError::UsernameNotFound,
50        "access denied".to_string(),
51        &mut error_messages,
52    );
53    add_env_error_message(
54        AuthenticationError::AuthorizationHeaderNotSet,
55        "authorization header not set".to_string(),
56        &mut error_messages,
57    );
58    error_messages
59});
60
61static AUTH_ERROR_CONTENT_TYPE: Lazy<String> =
62    Lazy::new(|| match std::env::var("AUTH_ERROR_CONTENT_TYPE") {
63        Ok(content_type) => content_type,
64        _ => "text/html; charset=utf-8".to_string(),
65    });
66
67fn add_env_error_code(
68    error: AuthenticationError,
69    error_codes: &mut HashMap<AuthenticationError, u16>,
70) {
71    match std::env::var(format!("{}_code", error)) {
72        Ok(code) => error_codes.insert(
73            error,
74            code.parse::<u16>().expect("Invalid status code mapping"),
75        ),
76        _ => error_codes.insert(error, 401),
77    };
78}
79
80fn add_env_error_message(
81    error: AuthenticationError,
82    default_message: String,
83    error_messages: &mut HashMap<AuthenticationError, String>,
84) {
85    match std::env::var(format!("{}_message", error)) {
86        Ok(message) => error_messages.insert(error, message),
87        _ => error_messages.insert(error, default_message),
88    };
89}
90
91/// Errors have a predfined HTTP-Status code that is returned in case an error occurs.
92/// This status code can be overwritten by calling this function.
93/// The status code must be in the range: 100 <= code <= 1000
94pub fn overwrite_auth_error_status_code(error: AuthenticationError, status_code: u16) {
95    assert!((100..=1000).contains(&status_code), "Invalid status code");
96    std::env::set_var(format!("{}_code", error), status_code.to_string());
97}
98
99/// Errors have a predfined text message that is returned in case an error occurs.
100/// This message can be overwritten by calling this function.
101pub fn overwrite_auth_error_message(error: AuthenticationError, message: String) {
102    std::env::set_var(format!("{}_message", error), message);
103}
104
105/// Error responses return the content type header `text/html; charset=utf-8` by default.
106/// The header value can be overwritten by calling this function.
107pub fn set_auth_error_content_type(content_type: String) {
108    std::env::set_var("AUTH_ERROR_CONTENT_TYPE", content_type);
109}
110
111impl error::ResponseError for AuthenticationError {
112    fn status_code(&self) -> StatusCode {
113        match *self {
114            AuthenticationError::InvalidAuthentication => {
115                dynamic_status_code(&AuthenticationError::InvalidAuthentication)
116            }
117            AuthenticationError::AuthorizationHeaderNotSet => {
118                dynamic_status_code(&AuthenticationError::AuthorizationHeaderNotSet)
119            }
120            AuthenticationError::InvalidAuthorizationHeader => {
121                dynamic_status_code(&AuthenticationError::InvalidAuthorizationHeader)
122            }
123            AuthenticationError::UsernameNotFound => {
124                dynamic_status_code(&AuthenticationError::UsernameNotFound)
125            }
126            AuthenticationError::InvalidToken => {
127                dynamic_status_code(&AuthenticationError::InvalidToken)
128            }
129        }
130    }
131
132    fn error_response(&self) -> HttpResponse {
133        HttpResponseBuilder::new(self.status_code())
134            .set_header(header::CONTENT_TYPE, AUTH_ERROR_CONTENT_TYPE.to_string())
135            .body(dynamic_error_message(self))
136    }
137}
138
139fn dynamic_status_code(error: &AuthenticationError) -> StatusCode {
140    StatusCode::from_u16(
141        *AUTH_ERROR_STATUS_CODE_MAPPING
142            .get(error)
143            .expect("Status code mapping missing"),
144    )
145    .expect("Invalid status code mapping found")
146}
147
148fn dynamic_error_message(error: &AuthenticationError) -> String {
149    AUTH_ERROR_MESSAGE_MAPPING
150        .get(error)
151        .expect("Error message mapping missing")
152        .clone()
153}