maxcountryman / axum-sessions

🥠 Cookie-based sessions for Axum via async-session.
MIT License
74 stars 18 forks source link

How to retrieve a readable session in middleware? #22

Closed avdb13 closed 1 year ago

avdb13 commented 1 year ago

The example on docs.rs cites the following code:

async fn handle(request: Request<Body>) -> Result<Response<Body>, Infallible> {
    let session_handle = request.extensions().get::<SessionHandle>().unwrap();
    let session = session_handle.read().await;
    // Use the session as you'd like.

    Ok(Response::new(Body::empty()))
}

This is what I have:

pub async fn flash<B>(mut req: Request<B>, next: Next<B>) -> Result<impl IntoResponse> {
    let session_handle = req.extensions().get::<SessionHandle>().unwrap();
    let session = session_handle.read().await;

    let payload = session.get::<bool>("signed_in").unwrap_or(false);

    req.extensions_mut().insert(payload);
    Ok(next.run(req).await)
}

image

However, I want to embed state in the Request so that the next handler knows whether the user is logged in or not. I'm using Askama for templating in case this is relevant.

maxcountryman commented 1 year ago

You might want to look at how axum-login does this: you'll want to pull the SessionHandle off the request and then can acquire either a readable or writable guard.

avdb13 commented 1 year ago

https://github.com/maxcountryman/axum-login/blob/a27fe5e2b18cb1d3faf6590fdcc829d77824fdc3/src/auth.rs#L106 Is this the relevant snippet? I don't seem to have an extract_parts method on my Request. Is this part of the underlying ReqBody?

maxcountryman commented 1 year ago

Yes that should be: it's the middleware itself which is hopefully helpful here.

extract_parts is via RequestExt.

avdb13 commented 1 year ago

Thanks, this is what I ended up with:

pub async fn flash<S: Send + 'static>(
    mut req: Request<S>,
    next: Next<S>,
) -> Result<impl IntoResponse> {
    let Extension(session_handle): Extension<SessionHandle> = req.extract_parts().await?;
    let session = session_handle.read().await;
    let payload = session.get::<bool>("signed_in").unwrap_or(false);

    req.extensions_mut().insert(payload);
    Ok(next.run(req).await)
}

It seems like extract_parts takes &mut self, does this extract all parts or just the one matching the type SessionHandle? Dropping SessionHandle in this scope is fine but I want to make sure all other metadata about Request is preserved.

maxcountryman commented 1 year ago

It seems like extract_parts takes &mut self, does this extract all parts or just the one matching the type SessionHandle?

That's a good question: I don't know the answer. It might require looking at the implementation more closely (I don't recall if the docs mention this).

avdb13 commented 1 year ago

Okay, I ran into another problem. This is what my router looks like:

    let hidden = Router::new()
        .route("/login", get(handlers::get_login).post(handlers::login))
        .route("/signup", get(handlers::get_signup).post(handlers::signup))
        .route("/logout", get(handlers::logout));

    Router::new()
        .merge(SpaRouter::new("/static", "ui/static"))
        .route("/", get(handlers::get_root))
        .nest("/.api", hidden)
        .layer(session_layer)
        .route_layer(middleware::from_fn(flash))
        .layer(TraceLayer::new_for_http())
        .with_state(app)

Whenever I visit get_login I get error: Missing request extension. Does the session_layer get inherited by the child router, hidden?

maxcountryman commented 1 year ago

I think your session layer should come before the flash middle, no?

avdb13 commented 1 year ago

Yeah I managed to get that far. Now I figured out that my hidden router doesn't have access to my own middleware and probably neither to session_layer.

avdb13 commented 1 year ago

Ended up with a deadlock in my POST handler, trying to figure out what happened in gdb I saw a thread dying shortly after logging in (probably the thread holding the Session).

maxcountryman commented 1 year ago

Are you drop'ing the Session?

avdb13 commented 1 year ago

Sorry for the late reply. Skimmed through #13 before I realized I didn't drop my reader after spawning a writer in a function call where the reader was still in scope.