Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
Refactor authentication and session handling
Browse files Browse the repository at this point in the history
  • Loading branch information
0rzech committed Apr 12, 2024
1 parent 8c8f9fd commit d264def
Show file tree
Hide file tree
Showing 14 changed files with 65 additions and 84 deletions.
78 changes: 43 additions & 35 deletions src/session/middleware.rs → src/authentication/middleware.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use crate::session::state::TypedSession;
use crate::session_state::TypedSession;
use anyhow::anyhow;
use axum::http::{header::LOCATION, HeaderValue, Request, Response, StatusCode};
use axum::{
async_trait,
extract::FromRequestParts,
http::{header::LOCATION, request::Parts, HeaderValue, Request, Response, StatusCode},
};
use std::{
future::Future,
pin::Pin,
Expand All @@ -9,41 +13,48 @@ use std::{
use tower::{Layer, Service};
use tower_sessions::Session;
use tracing::Instrument;
use uuid::Uuid;

#[derive(Debug, Clone)]
pub struct AuthorizedSessionLayer {
protected_paths: &'static [&'static str],
}
#[derive(Clone, Debug)]
pub struct SessionUserId(pub Uuid);

#[async_trait]
impl<S> FromRequestParts<S> for SessionUserId
where
S: Send + Sync,
{
type Rejection = StatusCode;

impl AuthorizedSessionLayer {
pub fn new(protected_paths: &'static [&'static str]) -> Self {
Self { protected_paths }
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
parts.extensions.get::<SessionUserId>().cloned().ok_or({
tracing::error!("User id not found in session");
StatusCode::INTERNAL_SERVER_ERROR
})
}
}

#[derive(Clone, Debug)]
pub struct AuthorizedSessionLayer;

impl<S> Layer<S> for AuthorizedSessionLayer {
type Service = AuthorizedSession<S>;

fn layer(&self, inner: S) -> Self::Service {
AuthorizedSession {
inner,
protected_paths: self.protected_paths,
}
AuthorizedSession { inner }
}
}

#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub struct AuthorizedSession<S> {
inner: S,
protected_paths: &'static [&'static str],
}

impl<S> AuthorizedSession<S> {
fn see_other<ResBody>() -> Response<ResBody>
where
ResBody: Default,
{
tracing::info!("User id not found in session");
tracing::info!("User is not logged in");
let mut res = Response::default();
*res.status_mut() = StatusCode::SEE_OTHER;
res.headers_mut()
Expand Down Expand Up @@ -81,31 +92,28 @@ where

fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
let span = tracing::info_span!("call");
let protected_paths = self.protected_paths;
let clone = self.inner.clone();
let mut inner = std::mem::replace(&mut self.inner, clone);

Box::pin(
async move {
if protected_paths.contains(&req.uri().path()) {
let Some(session) = req
.extensions()
.get::<Session>()
.cloned()
.map(TypedSession::new)
else {
return Ok(Self::internal_server_error(anyhow!("Session not found")));
};
let Some(session) = req
.extensions()
.get::<Session>()
.cloned()
.map(TypedSession::new)
else {
return Ok(Self::internal_server_error(anyhow!("Session not found")));
};

match session.get_user_id().await {
Ok(Some(user_id)) => {
tracing::info!("User id `{user_id}` found in session");
req.extensions_mut().insert(user_id);
}
Ok(None) => return Ok(Self::see_other()),
Err(e) => return Ok(Self::internal_server_error(e)),
};
}
match session.get_user_id().await {
Ok(Some(user_id)) => {
tracing::info!("User id `{user_id}` found in session");
req.extensions_mut().insert(SessionUserId(user_id));
}
Ok(None) => return Ok(Self::see_other()),
Err(e) => return Ok(Self::internal_server_error(e)),
};

inner.call(req).await
}
Expand Down
2 changes: 2 additions & 0 deletions src/authentication/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod middleware;
pub mod password;
File renamed without changes.
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub mod domain;
pub mod email_client;
pub mod request_id;
pub mod routes;
pub mod session;
pub mod session_state;
pub mod startup;
pub mod telemetry;
pub mod utils;
2 changes: 1 addition & 1 deletion src/routes/admin/dashboard.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
app_state::AppState,
session::extract::SessionUserId,
authentication::middleware::SessionUserId,
utils::{e500, HttpError},
};
use anyhow::{Context, Error};
Expand Down
2 changes: 1 addition & 1 deletion src/routes/admin/logout.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
session::state::TypedSession,
session_state::TypedSession,
utils::{e500, HttpError},
};
use axum::response::Redirect;
Expand Down
15 changes: 10 additions & 5 deletions src/routes/admin/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::app_state::AppState;
use crate::{app_state::AppState, authentication::middleware::AuthorizedSessionLayer};
use axum::{
routing::{get, post},
Router,
Expand All @@ -13,8 +13,13 @@ mod password;

pub fn router() -> Router<AppState> {
Router::new()
.route("/admin/dashboard", get(admin_dashboard))
.route("/admin/password", get(change_password_form))
.route("/admin/password", post(change_password))
.route("/admin/logout", post(log_out))
.nest(
"/admin",
Router::new()
.route("/dashboard", get(admin_dashboard))
.route("/password", get(change_password_form))
.route("/password", post(change_password))
.route("/logout", post(log_out)),
)
.layer(AuthorizedSessionLayer)
}
6 changes: 4 additions & 2 deletions src/routes/admin/password/action.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::{
app_state::AppState,
authentication::{
change_password as auth_change_password, validate_credentials, AuthError, Credentials,
middleware::SessionUserId,
password::{
change_password as auth_change_password, validate_credentials, AuthError, Credentials,
},
},
routes::admin::dashboard::get_username,
session::extract::SessionUserId,
utils::{e500, HttpError},
};
use axum::{extract::State, response::Redirect, Form};
Expand Down
4 changes: 2 additions & 2 deletions src/routes/login/action.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
app_state::AppState,
authentication::{validate_credentials, AuthError, Credentials},
session::state::TypedSession,
authentication::password::{validate_credentials, AuthError, Credentials},
session_state::TypedSession,
};
use axum::{
extract::State,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/newsletters.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
app_state::AppState,
authentication::{validate_credentials, AuthError, Credentials},
authentication::password::{validate_credentials, AuthError, Credentials},
domain::{SubscriberEmail, SubscriptionStatus},
};
use anyhow::Context;
Expand Down
28 changes: 0 additions & 28 deletions src/session/extract.rs

This file was deleted.

3 changes: 0 additions & 3 deletions src/session/mod.rs

This file was deleted.

File renamed without changes.
5 changes: 0 additions & 5 deletions src/startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use crate::{
email_client::EmailClient,
request_id::RequestUuid,
routes::{admin, health_check, home, login, newsletters, subscriptions, subscriptions_confirm},
session::middleware::AuthorizedSessionLayer,
telemetry::request_span,
};
use anyhow::anyhow;
Expand Down Expand Up @@ -137,10 +136,6 @@ async fn run(
.merge(login::router())
.merge(admin::router())
.with_state(app_state)
.layer(AuthorizedSessionLayer::new(&[
"/admin/dashboard",
"/admin/password",
]))
.layer(MessagesManagerLayer)
.layer(
SessionManagerLayer::new(RedisStore::new(redis_pool))
Expand Down

0 comments on commit d264def

Please sign in to comment.