authlogic/
middleware.rs

1use actix_web::{
2    body::MessageBody,
3    cookie::{time::Duration, Cookie, SameSite},
4    dev::{ServiceRequest, ServiceResponse},
5    http::header::{HeaderName, HeaderValue},
6    middleware::Next,
7    Error,
8};
9
10use crate::{
11    app::App,
12    maybe_auth::MaybeAuth,
13    sessions::authenticate_by_session_token,
14    token_actions::AuthTokenAction,
15    users::UserID, AppTypes,
16};
17
18pub async fn middleware<A: App>(
19    mut request: ServiceRequest,
20    next: Next<impl MessageBody>,
21) -> Result<ServiceResponse<impl MessageBody>, Error>
22    where A: actix_web::FromRequest<Error = <A as AppTypes>::Error>,
23{
24    let mut app = request.extract::<A>()
25        .await?;
26
27    // Authenticate by the cookie, if there is one
28    let auth = authenticate_by_session_token(&mut app, &request)
29        .await?;
30
31    match &auth {
32        MaybeAuth::Authenticated(auth) => {
33            log::debug!("Authenticated as user #{}, session #{} ({})", auth.user.id(), auth.session_id, auth.user_state);
34        }
35        MaybeAuth::Unauthenticated => {
36            log::debug!("Not authenticated");
37        }
38    }
39
40    // Make the authentication state available to the application
41    auth.insert_into_request(&request);
42
43    // Call the wrapped handler
44    let mut response = next.call(request)
45        .await?;
46
47    // Tell the client not to cache the session token.
48    // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#web-content-caching
49    response.headers_mut().append(
50        HeaderName::from_static("cache-control"),
51        HeaderValue::from_static("no-cache=\"Set-Cookie, Set-Cookie2\""),
52    );
53
54    // Issue or revoke the cookie, if necessary. If an action is inserted into
55    // the request object, perform that action; otherwise perform the action
56    // indicated by the earlier call to `authenticate_by_session_token`.
57    let cookie_name = A::session_token_cookie_name(&app);
58
59    match AuthTokenAction::take_from_request(response.request()) {
60        AuthTokenAction::Issue(token) => {
61            log::debug!("Issuing session cookie");
62
63            // Issue a cookie with the appropriate attributes
64            // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#cookies
65            let mut cookie = Cookie::new(cookie_name, &token.0);
66
67            // HTTP-only cookies are not visible to client-side JavaScript.
68            cookie.set_http_only(true);
69
70            // Only send this cookie over HTTPS connections.
71            cookie.set_secure(true);
72
73            cookie.set_same_site(if A::session_token_cookie_same_site_strict(&app) {
74                // The client should only send this cookie when making requests
75                // from the same site.
76                SameSite::Strict
77            } else {
78                // The client should only send this cookie when making requests
79                // from the same site, or when making safe requests from other
80                // sites (e.g. GET requests by following links).
81                SameSite::Lax
82            });
83
84            // TODO: Domain and Path options
85
86            let duration = Duration::hours(A::session_expire_after_hours(&app) as i64);
87            cookie.set_max_age(duration);
88
89            response.response_mut()
90                .add_cookie(&cookie)?;
91        }
92        AuthTokenAction::Revoke => {
93            log::debug!("Revoking session cookie");
94
95            // Revoke cookie by setting new empty cookie of the same name.
96            let cookie = Cookie::new(cookie_name, "");
97            response.response_mut()
98                .add_removal_cookie(&cookie)?;
99        }
100        AuthTokenAction::DoNothing => {}
101    }
102
103    Ok(response)
104}