From 1e49ad62407f70df15de3361ffbb7b8628510df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Bardon?= Date: Sun, 25 Aug 2024 19:03:04 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Migrate=20from=20Rocket=20?= =?UTF-8?q?to=20Axum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Cargo.lock | 100 ------------------ src/Cargo.toml | 2 +- src/helpers/src/config.rs | 1 - src/orangutan-server/src/main.rs | 30 +----- src/orangutan-server/src/request_guards.rs | 45 ++------ .../src/routes/debug_routes.rs | 6 +- src/orangutan-server/src/routes/main_route.rs | 15 ++- src/orangutan-server/src/util/mod.rs | 10 +- 8 files changed, 30 insertions(+), 179 deletions(-) diff --git a/src/Cargo.lock b/src/Cargo.lock index 2f47407..ee9927b 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -17,41 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -378,16 +343,6 @@ dependencies = [ "phf_codegen", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - [[package]] name = "const-oid" version = "0.9.6" @@ -400,11 +355,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ - "aes-gcm", - "base64 0.22.1", "percent-encoding", - "rand", - "subtle", "time", "version_check", ] @@ -456,19 +407,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", "typenum", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -705,16 +646,6 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - [[package]] name = "gimli" version = "0.29.0" @@ -937,15 +868,6 @@ dependencies = [ "serde", ] -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - [[package]] name = "iso8601-duration" version = "0.2.0" @@ -1339,18 +1261,6 @@ dependencies = [ "spki", ] -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -2102,16 +2012,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - [[package]] name = "urlencoding" version = "2.1.3" diff --git a/src/Cargo.toml b/src/Cargo.toml index 3b0d86e..8a8657a 100644 --- a/src/Cargo.toml +++ b/src/Cargo.toml @@ -10,7 +10,7 @@ resolver = "2" [workspace.dependencies] axum = { version = "0.7", features = ["macros"] } -axum-extra = { version = "0.9", features = ["cookie-private"] } +axum-extra = { version = "0.9", features = ["cookie"] } base64 = "0.22" biscuit-auth = "5" chrono = "0.4" diff --git a/src/helpers/src/config.rs b/src/helpers/src/config.rs index 89d8a4a..8ef65aa 100644 --- a/src/helpers/src/config.rs +++ b/src/helpers/src/config.rs @@ -6,7 +6,6 @@ pub const THEME_NAME: &str = "Orangutan"; pub const DATA_FILE_EXTENSION: &str = "orangutan"; pub const DEFAULT_PROFILE: &str = "_default"; pub const ROOT_KEY_NAME: &'static str = "_biscuit_root"; -pub const COOKIE_KEY_ENV_VAR_NAME: &'static str = "COOKIE_ENCRYPTION_KEY"; pub(super) const WEBSITE_DIR_NAME: &'static str = "website"; diff --git a/src/orangutan-server/src/main.rs b/src/orangutan-server/src/main.rs index 566cb7c..4a82e89 100644 --- a/src/orangutan-server/src/main.rs +++ b/src/orangutan-server/src/main.rs @@ -7,19 +7,16 @@ use std::{fs, process::ExitCode}; use axum::{ body::Body, - extract::FromRef, http::{Request, StatusCode}, middleware, response::{IntoResponse, Response}, Router, }; -use axum_extra::extract::cookie::Key; use orangutan_helpers::{ - config::COOKIE_KEY_ENV_VAR_NAME, generate::{self, *}, website_id::WebsiteId, }; -use request_guards::{handle_refresh_token, migrate_token, REVOKED_TOKENS}; +use request_guards::{handle_refresh_token, REVOKED_TOKENS}; use tera::Tera; use tokio::runtime::Handle; use tower::Service; @@ -40,17 +37,10 @@ use crate::{config::NOT_FOUND_FILE, routes::update_content_routes, util::error}; #[derive(Clone)] struct AppState { website_root: WebsiteRoot, - cookie_key: Key, #[cfg(feature = "templating")] tera: Tera, } -impl FromRef for Key { - fn from_ref(state: &AppState) -> Self { - state.cookie_key.clone() - } -} - #[tokio::main] async fn main() -> ExitCode { let website_root = match WebsiteRoot::try_from_env() { @@ -63,14 +53,6 @@ async fn main() -> ExitCode { let mut app_state = AppState { website_root, - // FIXME: Use predefined key. - cookie_key: Key::from( - std::env::var(COOKIE_KEY_ENV_VAR_NAME) - .expect(&format!( - "Environment variable '{COOKIE_KEY_ENV_VAR_NAME}' not defined." - )) - .as_bytes(), - ), #[cfg(feature = "templating")] tera: Default::default(), }; @@ -93,15 +75,7 @@ async fn main() -> ExitCode { let app = Router::new() .nest("/", routes::router()) .layer(TraceLayer::new_for_http()) - .route_layer(middleware::from_fn_with_state( - app_state.clone(), - handle_refresh_token, - )) - // NOTE: Layers are ran in reverse order of insertion. - .route_layer(middleware::from_fn_with_state( - app_state.clone(), - migrate_token, - )) + .route_layer(middleware::from_fn(handle_refresh_token)) .with_state(app_state); // .register("/", catchers![unauthorized, forbidden, not_found]) diff --git a/src/orangutan-server/src/request_guards.rs b/src/orangutan-server/src/request_guards.rs index 772c196..997062b 100644 --- a/src/orangutan-server/src/request_guards.rs +++ b/src/orangutan-server/src/request_guards.rs @@ -7,15 +7,12 @@ use std::{ }; use axum::{ - extract::{rejection::QueryRejection, FromRef, FromRequestParts, Query, Request, State}, + extract::{rejection::QueryRejection, FromRequestParts, Query, Request}, http::{request, HeaderMap, HeaderValue, Uri}, middleware::Next, response::{IntoResponse, Redirect, Response}, }; -use axum_extra::{ - either::Either, - extract::{cookie::Key, CookieJar, PrivateCookieJar}, -}; +use axum_extra::{either::Either, extract::CookieJar}; use biscuit_auth::{macros::authorizer, Biscuit}; use lazy_static::lazy_static; use serde::Deserialize; @@ -24,7 +21,6 @@ use tracing::{debug, trace}; use crate::{ config::*, util::{add_cookie, add_padding, profiles}, - AppState, }; lazy_static! { @@ -71,7 +67,6 @@ impl IntoResponse for TokenError { impl FromRequestParts for Token where S: Send + Sync, - Key: FromRef, { type Rejection = TokenError; @@ -116,9 +111,7 @@ where } // Check cookies - // NOTE: For some reason, `rustc` can't figure out the type here… so we need to explicit it. - // It worked before, so it will probably wagically work again later. It just doesn't work now. - let cookies = PrivateCookieJar::::from_request_parts(parts, state) + let cookies = CookieJar::from_request_parts(parts, state) .await .map_err(|err| match err {}) .unwrap(); @@ -170,29 +163,6 @@ where } } -pub async fn migrate_token( - mut cookies: CookieJar, - mut private_cookies: PrivateCookieJar, - req: Request, - next: Next, -) -> Either { - // trace!("Running token migration middleware…"); - let Some(cookie) = cookies.get(TOKEN_COOKIE_NAME).cloned() else { - trace!("Token migration middleware found no outdated token, forwarding…"); - return Either::E1(next.run(req).await); - }; - trace!("Migrating outdated token…"); - - cookies = cookies.remove(cookie.clone()); - private_cookies = private_cookies.add(cookie.clone()); - - Either::E2(( - cookies, - private_cookies, - Redirect::to(&req.uri().to_string()), - )) -} - #[derive(Deserialize)] pub struct RefreshTokenQuery { #[serde(default)] @@ -202,8 +172,7 @@ pub struct RefreshTokenQuery { } pub async fn handle_refresh_token( - State(_app_state): State, - cookies: PrivateCookieJar, + cookies: CookieJar, Query(RefreshTokenQuery { refresh_token, force, @@ -212,7 +181,7 @@ pub async fn handle_refresh_token( token: Option, req: Request, next: Next, -) -> Result, crate::Error> { +) -> Result, crate::Error> { trace!("Running refresh token middleware…"); let Some(refresh_token) = refresh_token else { trace!("Refresh token middleware found no refresh token, forwarding…"); @@ -280,8 +249,8 @@ pub async fn handle_refresh_token( fn redirect_to_same_page_without_query_param( uri: &Uri, query: &mut HashMap, - cookies: PrivateCookieJar, - ) -> Result<(PrivateCookieJar, Redirect), crate::Error> { + cookies: CookieJar, + ) -> Result<(CookieJar, Redirect), crate::Error> { query.remove(REFRESH_TOKEN_QUERY_PARAM_NAME); // TODO: Check if we need to URL-encode keys and values or if they are still encoded. let query_segs: Vec = query.iter().map(|(k, v)| format!("{k}={v}")).collect(); diff --git a/src/orangutan-server/src/routes/debug_routes.rs b/src/orangutan-server/src/routes/debug_routes.rs index ce93b84..f068926 100644 --- a/src/orangutan-server/src/routes/debug_routes.rs +++ b/src/orangutan-server/src/routes/debug_routes.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, RwLock}; use axum::{routing::get, Router}; -use axum_extra::extract::PrivateCookieJar; +use axum_extra::extract::CookieJar; use chrono::{DateTime, Utc}; use lazy_static::lazy_static; @@ -48,10 +48,10 @@ pub(super) fn templates() -> Vec<(&'static str, &'static str)> { } // #[axum::debug_handler] -async fn clear_cookies(cookie_jar: PrivateCookieJar) -> (PrivateCookieJar, String) { +async fn clear_cookies(cookie_jar: CookieJar) -> (CookieJar, String) { let mut empty_jar = cookie_jar.clone(); for cookie in cookie_jar.iter() { - empty_jar = empty_jar.remove(cookie); + empty_jar = empty_jar.remove(cookie.clone()); } (empty_jar, "Success".to_string()) diff --git a/src/orangutan-server/src/routes/main_route.rs b/src/orangutan-server/src/routes/main_route.rs index e61680d..34598ae 100644 --- a/src/orangutan-server/src/routes/main_route.rs +++ b/src/orangutan-server/src/routes/main_route.rs @@ -36,7 +36,6 @@ async fn handle_request( token: Option, headers: HeaderMap, ) -> Result, Error>, Error> { - // FIXME: Handle error let path = uri.path(); trace!("GET {}", &path); @@ -48,10 +47,20 @@ async fn handle_request( // Log access only if the page is HTML. // WARN: This solution is far from perfect as someone requesting a page without setting the `Accept` header // would not be logged even though they'd get the file back. - let a = "FIXME: Remove force unwrap"; let accept = headers .get(ACCEPT) - .map(|value| Mime::from_str(value.to_str().expect("FIXME: Remove this force unwrap")).ok()) + .map(|value| { + value + .to_str() + .inspect_err(|err| debug!("{value:?} could not be mapped to a string: {err}")) + .ok() + }) + .flatten() + .map(|value| { + Mime::from_str(value) + .inspect_err(|err| debug!("{value:?} could not be mapped to a MIME type: {err}")) + .ok() + }) .flatten(); if accept.is_some_and(|m| m.type_() == mime::HTML) { log_access(user_profiles.to_owned(), path.to_owned()); diff --git a/src/orangutan-server/src/util/mod.rs b/src/orangutan-server/src/util/mod.rs index b60982d..f60c2b9 100644 --- a/src/orangutan-server/src/util/mod.rs +++ b/src/orangutan-server/src/util/mod.rs @@ -5,7 +5,7 @@ mod website_root; use axum_extra::extract::{ cookie::{Cookie, SameSite}, - PrivateCookieJar, + CookieJar, }; use biscuit_auth::{ builder::{Fact, Term}, @@ -59,13 +59,13 @@ pub fn add_padding(base64_string: &str) -> String { } } -/// Returns a new [PrivateCookieJar] which _must_ be returned from the handler +/// Returns a new [CookieJar] which _must_ be returned from the handler /// as part of the response for the changes to be propagated. -/// See [PrivateCookieJar]'s documentation for examples. +/// See [CookieJar]'s documentation for examples. pub fn add_cookie( biscuit: &Biscuit, - cookies: PrivateCookieJar, -) -> Result { + cookies: CookieJar, +) -> Result { let base64 = biscuit.to_base64().map_err(|err| { crate::Error::InternalServerError(format!("Error setting token cookie: {err}")) })?;