-
Pierre Jarriges authoredPierre Jarriges authored
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(());
}
}
}