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
//! Authentication errors that occur in the crate's source code must be handled.
//! Due to rusts limited capabilites to register / overwrite code from other traits
//! some helper functions are provided to customize the error responses.

use std::collections::HashMap;

use actix_web::dev::HttpResponseBuilder;
use actix_web::http::{header, StatusCode};
use actix_web::{error, HttpResponse};
use once_cell::sync::Lazy;

use crate::authentication::error::error_type::AuthenticationError;

// Status code must be 100 <= code <= 1000
static AUTH_ERROR_STATUS_CODE_MAPPING: Lazy<HashMap<AuthenticationError, u16>> = Lazy::new(|| {
    let mut error_codes: HashMap<AuthenticationError, u16> = HashMap::new();
    add_env_error_code(AuthenticationError::InvalidAuthentication, &mut error_codes);
    add_env_error_code(AuthenticationError::InvalidToken, &mut error_codes);
    add_env_error_code(
        AuthenticationError::InvalidAuthorizationHeader,
        &mut error_codes,
    );
    add_env_error_code(AuthenticationError::UsernameNotFound, &mut error_codes);
    add_env_error_code(
        AuthenticationError::AuthorizationHeaderNotSet,
        &mut error_codes,
    );
    error_codes
});

static AUTH_ERROR_MESSAGE_MAPPING: Lazy<HashMap<AuthenticationError, String>> = Lazy::new(|| {
    let mut error_messages: HashMap<AuthenticationError, String> = HashMap::new();
    add_env_error_message(
        AuthenticationError::InvalidAuthentication,
        "invalid authentication".to_string(),
        &mut error_messages,
    );
    add_env_error_message(
        AuthenticationError::InvalidToken,
        "access denied".to_string(),
        &mut error_messages,
    );
    add_env_error_message(
        AuthenticationError::InvalidAuthorizationHeader,
        "invalid authorization header".to_string(),
        &mut error_messages,
    );
    add_env_error_message(
        AuthenticationError::UsernameNotFound,
        "access denied".to_string(),
        &mut error_messages,
    );
    add_env_error_message(
        AuthenticationError::AuthorizationHeaderNotSet,
        "authorization header not set".to_string(),
        &mut error_messages,
    );
    error_messages
});

static AUTH_ERROR_CONTENT_TYPE: Lazy<String> =
    Lazy::new(|| match std::env::var("AUTH_ERROR_CONTENT_TYPE") {
        Ok(content_type) => content_type,
        _ => "text/html; charset=utf-8".to_string(),
    });

fn add_env_error_code(
    error: AuthenticationError,
    error_codes: &mut HashMap<AuthenticationError, u16>,
) {
    match std::env::var(format!("{}_code", error)) {
        Ok(code) => error_codes.insert(
            error,
            code.parse::<u16>().expect("Invalid status code mapping"),
        ),
        _ => error_codes.insert(error, 401),
    };
}

fn add_env_error_message(
    error: AuthenticationError,
    default_message: String,
    error_messages: &mut HashMap<AuthenticationError, String>,
) {
    match std::env::var(format!("{}_message", error)) {
        Ok(message) => error_messages.insert(error, message),
        _ => error_messages.insert(error, default_message),
    };
}

/// Errors have a predfined HTTP-Status code that is returned in case an error occurs.
/// This status code can be overwritten by calling this function.
/// The status code must be in the range: 100 <= code <= 1000
pub fn overwrite_auth_error_status_code(error: AuthenticationError, status_code: u16) {
    assert!((100..=1000).contains(&status_code), "Invalid status code");
    std::env::set_var(format!("{}_code", error), status_code.to_string());
}

/// Errors have a predfined text message that is returned in case an error occurs.
/// This message can be overwritten by calling this function.
pub fn overwrite_auth_error_message(error: AuthenticationError, message: String) {
    std::env::set_var(format!("{}_message", error), message);
}

/// Error responses return the content type header `text/html; charset=utf-8` by default.
/// The header value can be overwritten by calling this function.
pub fn set_auth_error_content_type(content_type: String) {
    std::env::set_var("AUTH_ERROR_CONTENT_TYPE", content_type);
}

impl error::ResponseError for AuthenticationError {
    fn status_code(&self) -> StatusCode {
        match *self {
            AuthenticationError::InvalidAuthentication => {
                dynamic_status_code(&AuthenticationError::InvalidAuthentication)
            }
            AuthenticationError::AuthorizationHeaderNotSet => {
                dynamic_status_code(&AuthenticationError::AuthorizationHeaderNotSet)
            }
            AuthenticationError::InvalidAuthorizationHeader => {
                dynamic_status_code(&AuthenticationError::InvalidAuthorizationHeader)
            }
            AuthenticationError::UsernameNotFound => {
                dynamic_status_code(&AuthenticationError::UsernameNotFound)
            }
            AuthenticationError::InvalidToken => {
                dynamic_status_code(&AuthenticationError::InvalidToken)
            }
        }
    }

    fn error_response(&self) -> HttpResponse {
        HttpResponseBuilder::new(self.status_code())
            .set_header(header::CONTENT_TYPE, AUTH_ERROR_CONTENT_TYPE.to_string())
            .body(dynamic_error_message(self))
    }
}

fn dynamic_status_code(error: &AuthenticationError) -> StatusCode {
    StatusCode::from_u16(
        *AUTH_ERROR_STATUS_CODE_MAPPING
            .get(error)
            .expect("Status code mapping missing"),
    )
    .expect("Invalid status code mapping found")
}

fn dynamic_error_message(error: &AuthenticationError) -> String {
    AUTH_ERROR_MESSAGE_MAPPING
        .get(error)
        .expect("Error message mapping missing")
        .clone()
}