tallyweb_frontend/
middleware.rs

1use std::future::{ready, Ready};
2
3use actix_web::{
4    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
5    http::header,
6    Error, HttpResponse,
7};
8use futures_util::future::LocalBoxFuture;
9
10use super::UserSession;
11
12// There are two steps in middleware processing.
13// 1. Middleware initialization, middleware factory gets called with
14//    next service in chain as parameter.
15// 2. Middleware's call method gets called with normal request.
16pub struct CheckSession;
17
18// Middleware factory is `Transform` trait
19// `S` - type of the next service
20// `B` - type of response's body
21impl<S, B> Transform<S, ServiceRequest> for CheckSession
22where
23    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
24    S::Future: 'static,
25    B: 'static,
26{
27    type Response = ServiceResponse<B>;
28    type Error = Error;
29    type InitError = ();
30    type Transform = CheckSessionMW<S>;
31    type Future = Ready<Result<Self::Transform, Self::InitError>>;
32
33    fn new_transform(&self, service: S) -> Self::Future {
34        ready(Ok(CheckSessionMW { service }))
35    }
36}
37
38pub struct CheckSessionMW<S> {
39    service: S,
40}
41
42impl<S, B> CheckSessionMW<S>
43where
44    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
45    S::Future: 'static,
46    B: 'static,
47{
48}
49
50impl<S, B> Service<ServiceRequest> for CheckSessionMW<S>
51where
52    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
53    S::Future: 'static,
54    B: 'static,
55{
56    type Response = ServiceResponse<B>;
57    type Error = Error;
58    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
59
60    forward_ready!(service);
61
62    fn call(&self, req: ServiceRequest) -> Self::Future {
63        let get_session = |req: &ServiceRequest| {
64            let cookie = req
65                .cookie("session")
66                .ok_or(actix_web::error::ErrorUnauthorized(
67                    "Missing `session` cookie",
68                ))?
69                .value()
70                .to_string();
71
72            let pool = req
73                .app_data::<actix_web::web::Data<backend::PgPool>>()
74                .ok_or(actix_web::error::ErrorInternalServerError(
75                    "Missing DB pool",
76                ))?;
77
78            let session: UserSession = serde_json::from_str(&cookie)
79                .map_err(|err| actix_web::error::ErrorBadRequest(err))?;
80
81            Ok((session.clone(), pool.clone().into_inner().clone()))
82        };
83
84        let (session, pool) = match get_session(&req) {
85            Ok(res) => res,
86            Err(err) => return Box::pin(async { Err(err) }),
87        };
88
89        let fut = self.service.call(req);
90
91        Box::pin(async move {
92            match backend::auth::check_user(&pool, &session.username, session.token).await {
93                Ok(backend::auth::SessionState::Valid) => fut.await,
94                Ok(backend::auth::SessionState::Expired) => {
95                    let (req, resp) = fut.await?.into_parts();
96                    let resp = HttpResponse::Ok()
97                        .insert_header(("serverfnredirect", "/login"))
98                        .insert_header((header::LOCATION, "/login"))
99                        .message_body(resp.into_body())?;
100                    Ok(ServiceResponse::new(req, resp))
101                }
102                Err(err) => Err(actix_web::error::ErrorUnauthorized(err)),
103            }
104        })
105    }
106}