Pour tout problème contactez-nous par mail : support@froggit.fr | La FAQ :grey_question: | Rejoignez-nous sur le Chat :speech_balloon:

Skip to content
Snippets Groups Projects
middleware.rs 4.24 KiB
use crate::{
    model::{AdminAuthCredentials, Administrator},
    AppState,
};
use actix_web::{cookie::SameSite, http::Cookie, web::Form, HttpMessage, HttpRequest};
use wither::{bson::doc, prelude::Model};

/// Returns a Secure actix_web::http::Cookie.
pub fn get_auth_cookie(name: &'static str, value: String) -> Cookie<'static> {
    Cookie::build(name, value)
        .secure(true)
        .http_only(true)
        .same_site(SameSite::Strict)
        .path("/")
        .finish()
}

/// This is not a real middleware as it is meant to be executed only after having processed the request and not before.
/// It must be registered in the actix App instance with app_data.
/// ```
/// App::new()
///     .app_data(Data::new(AuthenticatedAdminMiddleware::new("some-auth-cookie-name")))
/// ```
/// If a service need to perform an authentication before doing anything, this "pseudo-middleware" should be run before anything else in the function.
/// Example:
/// ```
/// #[post("/some-url")]
/// pub async fn some_service(
///     app_state: Data<AppState>,
///     middleware: Data<AuthenticatedAdminMiddleware<'_>>,
///     req: HttpRequest,
/// ) -> impl Responder {
///     if middleware.exec(&app_state, &req, None).await.is_err() {
///         return HttpResponse::Unauthorized().finish();
///     }
///     ... Authenticated action ....
/// }
/// ```
#[derive(Debug, Clone)]
pub struct AuthenticatedAdminMiddleware<'a> {
    /// The name of the authentication cookie
    pub cookie_name: &'a str,
}

impl<'a> AuthenticatedAdminMiddleware<'a> {
    pub fn new(cookie_name: &'a str) -> Self {
        AuthenticatedAdminMiddleware { cookie_name }
    }

    /// Performs Administrator authentication from form data with username and password
    /// Returns an authentication Cookie instance if the authentication succeeds, or an error.
    async fn try_auth_from_form_data(
        &self,
        app_state: &AppState,
        form_data: Form<AdminAuthCredentials>,
    ) -> Result<Cookie<'static>, ()> {
        match Administrator::authenticated(app_state, form_data.into_inner()).await {
            Ok(ref mut admin) => {
                let auth_token = app_state.encryption.random_ascii_lc_string(256);
                admin.auth_token = Some(app_state.encryption.encrypt(&auth_token));

                if admin
                    .save(&app_state.db, Some(doc!("_id": admin.id().unwrap())))
                    .await
                    .is_err()
                {
                    println!("Failed to update admin auth_token");
                    return Err(());
                }
                let cookie = get_auth_cookie("kuadrado-admin-auth", auth_token.to_owned());

                return Ok(cookie);
            }
            Err(_) => return Err(()),
        }
    }

    /// Performs Administrator authentication from the authentication cookie value
    async fn try_auth_from_auth_cookie(
        &self,
        app_state: &AppState,
        cookie: &Cookie<'static>,
    ) -> Result<Cookie<'static>, ()> {
        match Administrator::authenticated_with_cookie(app_state, &cookie).await {
            Ok(_) => return Ok(cookie.clone()),
            Err(_) => return Err(()),
        }
    }

    /// The function that must be called in order to execute the verification.
    /// Example :
    /// ```
    /// #[post("/some-url")]
    /// pub async fn some_service(
    ///     app_state: Data<AppState>,
    ///     middleware: Data<AuthenticatedAdminMiddleware<'_>>,
    ///     req: HttpRequest,
    /// ) -> impl Responder {
    ///     if middleware.exec(&app_state, &req, None).await.is_err() {
    ///         return HttpResponse::Unauthorized().finish();
    ///     }
    ///     ... Authenticated actions ....
    /// }
    /// ```
    pub async fn exec(
        &self,
        app_state: &AppState,
        req: &HttpRequest,
        auth_form_data: Option<Form<AdminAuthCredentials>>,
    ) -> Result<Cookie<'static>, ()> {
        let auth_cookie = req.cookie(self.cookie_name);
        if let Some(form_data) = auth_form_data {
            return self.try_auth_from_form_data(app_state, form_data).await;
        } else if let Some(cookie) = auth_cookie {
            return self.try_auth_from_auth_cookie(app_state, &cookie).await;
        } else {
            return Err(());
        }
    }
}