feat: implement auth middleware

image-uploads
Jef Roosens 2025-01-11 17:35:20 +01:00
parent a65b647f65
commit df741c931b
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
8 changed files with 211 additions and 6 deletions

145
Cargo.lock generated
View File

@ -106,7 +106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core", "axum-core 0.4.5",
"axum-macros", "axum-macros",
"bytes", "bytes",
"futures-util", "futures-util",
@ -116,7 +116,7 @@ dependencies = [
"hyper", "hyper",
"hyper-util", "hyper-util",
"itoa", "itoa",
"matchit", "matchit 0.7.3",
"memchr", "memchr",
"mime", "mime",
"percent-encoding", "percent-encoding",
@ -134,6 +134,33 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "axum"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
dependencies = [
"axum-core 0.5.0",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"itoa",
"matchit 0.8.4",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"sync_wrapper",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "axum-core" name = "axum-core"
version = "0.4.5" version = "0.4.5"
@ -155,6 +182,48 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "axum-core"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
dependencies = [
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-extra"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b"
dependencies = [
"axum 0.8.1",
"axum-core 0.5.0",
"bytes",
"cookie",
"futures-util",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"serde",
"tower",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "axum-macros" name = "axum-macros"
version = "0.4.2" version = "0.4.2"
@ -249,7 +318,8 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
name = "calathea" name = "calathea"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum 0.7.9",
"axum-extra",
"chrono", "chrono",
"r2d2", "r2d2",
"r2d2_sqlite", "r2d2_sqlite",
@ -314,6 +384,17 @@ dependencies = [
"phf_codegen", "phf_codegen",
] ]
[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"percent-encoding",
"time",
"version_check",
]
[[package]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.7" version = "0.8.7"
@ -373,6 +454,15 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
]
[[package]] [[package]]
name = "deunicode" name = "deunicode"
version = "1.6.0" version = "1.6.0"
@ -736,6 +826,12 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "matchit"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
@ -788,6 +884,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@ -957,6 +1059,12 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.20" version = "0.2.20"
@ -1321,6 +1429,37 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "time"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
]
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.42.0" version = "1.42.0"

View File

@ -9,6 +9,7 @@ name = "calathea"
[dependencies] [dependencies]
axum = { version = "0.7.9", features = ["macros"] } axum = { version = "0.7.9", features = ["macros"] }
axum-extra = { version = "0.10.0", features = ["cookie"] }
chrono = { version = "0.4.39", features = ["serde"] } chrono = { version = "0.4.39", features = ["serde"] }
r2d2 = "0.8.10" r2d2 = "0.8.10"
r2d2_sqlite = "0.25.0" r2d2_sqlite = "0.25.0"

View File

@ -1,6 +1,7 @@
mod comment; mod comment;
mod event; mod event;
mod plant; mod plant;
mod session;
mod user; mod user;
use r2d2_sqlite::{rusqlite, SqliteConnectionManager}; use r2d2_sqlite::{rusqlite, SqliteConnectionManager};
@ -10,6 +11,8 @@ use std::{error::Error, fmt};
pub use comment::{Comment, NewComment}; pub use comment::{Comment, NewComment};
pub use event::{Event, EventType, NewEvent, EVENT_TYPES}; pub use event::{Event, EventType, NewEvent, EVENT_TYPES};
pub use plant::{NewPlant, Plant}; pub use plant::{NewPlant, Plant};
pub use session::Session;
pub use user::{NewUser, User};
pub type DbPool = r2d2::Pool<SqliteConnectionManager>; pub type DbPool = r2d2::Pool<SqliteConnectionManager>;

19
src/db/session.rs 100644
View File

@ -0,0 +1,19 @@
use super::{DbError, DbPool, User};
pub struct Session {
id: u64,
user_id: i32,
}
impl Session {
pub fn user_from_id(pool: &DbPool, id: u64) -> Result<Option<super::User>, DbError> {
let conn = pool.get()?;
let mut stmt = conn.prepare("select users.* from sessions inner join users on sessions.user_id = users.id where sessions.id = $1")?;
match stmt.query_row((id,), User::from_row) {
Ok(user) => Ok(Some(user)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(err) => Err(DbError::Db(err)),
}
}
}

View File

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use super::{DbError, DbPool}; use super::{DbError, DbPool};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Clone)]
pub struct User { pub struct User {
id: i32, id: i32,
username: String, username: String,

View File

@ -7,11 +7,12 @@ use r2d2_sqlite::SqliteConnectionManager;
use tera::Tera; use tera::Tera;
use tower_http::compression::CompressionLayer; use tower_http::compression::CompressionLayer;
const MIGRATIONS: [&str; 4] = [ const MIGRATIONS: [&str; 5] = [
include_str!("migrations/000_initial.sql"), include_str!("migrations/000_initial.sql"),
include_str!("migrations/001_plants.sql"), include_str!("migrations/001_plants.sql"),
include_str!("migrations/002_comments.sql"), include_str!("migrations/002_comments.sql"),
include_str!("migrations/003_events.sql"), include_str!("migrations/003_events.sql"),
include_str!("migrations/004_auth.sql"),
]; ];
#[derive(Clone)] #[derive(Clone)]

36
src/server/auth.rs 100644
View File

@ -0,0 +1,36 @@
use axum::{
extract::{Request, State},
http::StatusCode,
middleware::Next,
response::{IntoResponse, Response},
};
use axum_extra::extract::CookieJar;
use crate::db::Session;
use super::error::AppError;
pub async fn auth_middleware(
State(ctx): State<crate::Context>,
mut req: Request,
next: Next,
) -> Response {
let jar = CookieJar::from_headers(req.headers());
if let Some(session_id) = jar
.get("session_id")
.and_then(|c| c.value().parse::<u64>().ok())
{
match Session::user_from_id(&ctx.pool, session_id) {
Ok(Some(user)) => {
req.extensions_mut().insert(user);
next.run(req).await
}
Ok(None) => StatusCode::UNAUTHORIZED.into_response(),
Err(err) => AppError::Db(err).into_response(),
}
} else {
StatusCode::UNAUTHORIZED.into_response()
}
}

View File

@ -1,3 +1,4 @@
mod auth;
mod comments; mod comments;
mod error; mod error;
mod events; mod events;
@ -6,6 +7,7 @@ mod plants;
use axum::{ use axum::{
extract::State, extract::State,
http::{header::VARY, HeaderMap, HeaderValue}, http::{header::VARY, HeaderMap, HeaderValue},
middleware,
response::Html, response::Html,
routing::get, routing::get,
Router, Router,
@ -50,10 +52,14 @@ pub fn render_view(
pub fn app(ctx: crate::Context, static_dir: &str) -> axum::Router { pub fn app(ctx: crate::Context, static_dir: &str) -> axum::Router {
let router = Router::new() let router = Router::new()
.route("/", get(get_index))
.nest("/plants", plants::app()) .nest("/plants", plants::app())
.nest("/comments", comments::app()) .nest("/comments", comments::app())
.nest("/events", events::app()) .nest("/events", events::app())
.layer(middleware::from_fn_with_state(
ctx.clone(),
auth::auth_middleware,
))
.route("/", get(get_index))
.nest_service("/static", ServeDir::new(static_dir)) .nest_service("/static", ServeDir::new(static_dir))
.with_state(ctx.clone()); .with_state(ctx.clone());