Move Authenticated into a middlware to reduce cost of multiple Authenticateds in a route
This commit is contained in:
parent
7143cf8ff0
commit
cf31172632
|
@ -12,6 +12,7 @@ actix-session = "0.4.0"
|
|||
actix-web = "3.3.2"
|
||||
anyhow = "1.0"
|
||||
bcrypt = "0.9.0"
|
||||
event-listener = "2.5.1"
|
||||
futures = "0.3.8"
|
||||
hyaenidae-toolkit = { version = "0.1.0", path = "../toolkit" }
|
||||
log = "0.4.11"
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
use actix_session::Session;
|
||||
use crate::{store::User, State};
|
||||
use actix_session::{Session, UserSession};
|
||||
use actix_web::{
|
||||
dev::Payload,
|
||||
dev::{Payload, Service, ServiceRequest, ServiceResponse, Transform},
|
||||
http::{header::LOCATION, StatusCode},
|
||||
web::Data,
|
||||
FromRequest, HttpRequest, HttpResponse, ResponseError,
|
||||
FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
|
||||
};
|
||||
use event_listener::Event;
|
||||
use futures::future::{ok, LocalBoxFuture, Ready};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use futures::future::LocalBoxFuture;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{store::User, State};
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub(crate) struct CookieData {
|
||||
accepted: bool,
|
||||
|
@ -53,6 +58,7 @@ impl UserData {
|
|||
|
||||
pub struct AcceptedCookies(());
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Authenticated {
|
||||
user: User,
|
||||
}
|
||||
|
@ -91,17 +97,15 @@ impl FromRequest for AcceptedCookies {
|
|||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
let session_fut = Session::extract(req);
|
||||
let session = req.get_session();
|
||||
let state_fut = Data::<State>::extract(req);
|
||||
|
||||
Box::pin(async move {
|
||||
let session = session_fut.await?;
|
||||
let state = state_fut.await?;
|
||||
|
||||
if CookieData::accepted(&session) {
|
||||
Ok(AcceptedCookies(()))
|
||||
} else {
|
||||
log::debug!("Browser has not accepted cookies");
|
||||
Err(ServerError(state.pages.not_found_path()).into())
|
||||
}
|
||||
})
|
||||
|
@ -114,31 +118,125 @@ impl FromRequest for Authenticated {
|
|||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
let session_fut = Session::extract(req);
|
||||
let opt = req
|
||||
.extensions()
|
||||
.get::<AuthState>()
|
||||
.map(|auth_state| (auth_state.clone(), auth_state.2.listen()));
|
||||
|
||||
let state_fut = Data::<State>::extract(req);
|
||||
|
||||
Box::pin(async move {
|
||||
let session = session_fut.await?;
|
||||
let state = state_fut.await?;
|
||||
let (auth_state, listen_fut) = match opt {
|
||||
Some(auth_state) => auth_state,
|
||||
None => {
|
||||
let state = state_fut.await?;
|
||||
return Err(ServerError(state.pages.not_found_path()).into());
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(auth) = auth_state.0.borrow().as_ref() {
|
||||
return Ok(auth.clone());
|
||||
}
|
||||
|
||||
if !*auth_state.1.borrow() {
|
||||
listen_fut.await;
|
||||
|
||||
if let Some(auth) = auth_state.0.borrow().as_ref() {
|
||||
return Ok(auth.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let state = state_fut.await?;
|
||||
Err(ServerError(state.pages.not_found_path()).into())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Auth(pub State);
|
||||
pub struct AuthMiddleware<S>(S, State);
|
||||
#[derive(Clone)]
|
||||
struct AuthState(
|
||||
Rc<RefCell<Option<Authenticated>>>,
|
||||
Rc<RefCell<bool>>,
|
||||
Rc<Event>,
|
||||
);
|
||||
struct DropGuard(Rc<Event>);
|
||||
|
||||
fn auth_state() -> (AuthState, DropGuard) {
|
||||
let event = Rc::new(Event::new());
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
let flag = Rc::new(RefCell::new(false));
|
||||
|
||||
(AuthState(state, flag, Rc::clone(&event)), DropGuard(event))
|
||||
}
|
||||
|
||||
impl Drop for DropGuard {
|
||||
fn drop(&mut self) {
|
||||
self.0.notify(usize::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> Transform<S> for Auth
|
||||
where
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type InitError = ();
|
||||
type Transform = AuthMiddleware<S>;
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ok(AuthMiddleware(service, self.0.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> Service for AuthMiddleware<S>
|
||||
where
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.0.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
let session = req.get_session();
|
||||
let state = self.1.clone();
|
||||
|
||||
let (auth_state, drop_guard) = auth_state();
|
||||
req.extensions_mut().insert(auth_state.clone());
|
||||
|
||||
let fut = self.0.call(req);
|
||||
|
||||
Box::pin(async move {
|
||||
if let Some(UserData { id }) = UserData::data(&session)? {
|
||||
let res = state.user_store.user_by_id(id).await;
|
||||
|
||||
let user_opt = match res {
|
||||
Ok(user_opt) => user_opt,
|
||||
Err(_) => {
|
||||
log::debug!("Error fetching user data from store");
|
||||
log::warn!("Error fetching user data from store");
|
||||
return Err(ServerError(state.pages.internal_server_error_path()).into());
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(user) = user_opt {
|
||||
return Ok(Authenticated { user });
|
||||
*auth_state.0.borrow_mut() = Some(Authenticated { user });
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("No user data found for browser");
|
||||
Err(ServerError(state.pages.not_found_path()).into())
|
||||
*auth_state.1.borrow_mut() = true;
|
||||
drop(drop_guard);
|
||||
|
||||
fut.await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ mod forms;
|
|||
mod store;
|
||||
|
||||
pub use {
|
||||
extractors::{AcceptedCookies, Authenticated},
|
||||
extractors::{AcceptedCookies, Auth, Authenticated},
|
||||
forms::{
|
||||
cookies::{cookies, cookies_page, CookiesArgs, CookiesPageArgs, CookiesState},
|
||||
delete_user::{
|
||||
|
|
|
@ -4,7 +4,7 @@ use actix_web::{
|
|||
middleware::{Compress, Logger},
|
||||
web, App, HttpResponse, HttpServer,
|
||||
};
|
||||
use hyaenidae_accounts::Authenticated;
|
||||
use hyaenidae_accounts::{Auth, Authenticated};
|
||||
use sled::Db;
|
||||
use std::{fmt, time::SystemTime};
|
||||
|
||||
|
@ -49,6 +49,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
.wrap(Logger::default())
|
||||
.wrap(Compress::default())
|
||||
.data(state.clone())
|
||||
.wrap(Auth(accounts_state.clone()))
|
||||
.data(accounts_state)
|
||||
.data(SystemTime::now())
|
||||
.wrap(hyaenidae_accounts::cookie_middlware(&accounts_config))
|
||||
|
|
Loading…
Reference in a new issue