refactor: adapters configured from config crate
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use axum::{
|
||||
extract::Request,
|
||||
extract::{Request, State},
|
||||
http::{HeaderValue, header},
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
@@ -19,15 +19,19 @@ pub fn extract_from_cookie(headers: &axum::http::HeaderMap) -> Option<String> {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn secure_flag() -> &'static str {
|
||||
if std::env::var("SECURE_COOKIES").as_deref() == Ok("true") {
|
||||
"; Secure"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
/// Router state for [`csrf_middleware`]: whether cookies carry the `Secure` attribute.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SecureCookies(pub bool);
|
||||
|
||||
pub(crate) fn secure_flag(secure_cookies: bool) -> &'static str {
|
||||
if secure_cookies { "; Secure" } else { "" }
|
||||
}
|
||||
|
||||
pub async fn csrf_middleware(mut req: Request, next: Next) -> Response {
|
||||
pub async fn csrf_middleware(
|
||||
State(SecureCookies(secure_cookies)): State<SecureCookies>,
|
||||
mut req: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
let existing = extract_from_cookie(req.headers());
|
||||
let (token, needs_set) = match existing {
|
||||
Some(t) => (t, false),
|
||||
@@ -42,7 +46,7 @@ pub async fn csrf_middleware(mut req: Request, next: Next) -> Response {
|
||||
let cookie = format!(
|
||||
"csrf={}; HttpOnly; Path=/; SameSite=Strict{}",
|
||||
token,
|
||||
secure_flag()
|
||||
secure_flag(secure_cookies)
|
||||
);
|
||||
if let Ok(val) = HeaderValue::from_str(&cookie) {
|
||||
response.headers_mut().append(header::SET_COOKIE, val);
|
||||
|
||||
@@ -25,12 +25,16 @@ use template_askama::{LoginTemplate, RegisterTemplate};
|
||||
|
||||
// ── HTML helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn set_cookie_header(token: &str, max_age: i64) -> (axum::http::HeaderName, HeaderValue) {
|
||||
fn set_cookie_header(
|
||||
token: &str,
|
||||
max_age: i64,
|
||||
secure_cookies: bool,
|
||||
) -> (axum::http::HeaderName, HeaderValue) {
|
||||
let val = format!(
|
||||
"token={}; HttpOnly; Path=/; SameSite=Strict; Max-Age={}{}",
|
||||
token,
|
||||
max_age,
|
||||
secure_flag()
|
||||
secure_flag(secure_cookies)
|
||||
);
|
||||
(
|
||||
SET_COOKIE,
|
||||
@@ -177,17 +181,18 @@ pub async fn post_login(
|
||||
{
|
||||
Ok(result) => {
|
||||
let max_age = (result.expires_at - Utc::now()).num_seconds().max(0);
|
||||
let cookie = set_cookie_header(&result.token, max_age);
|
||||
let cookie =
|
||||
set_cookie_header(&result.token, max_age, state.app_ctx.config.secure_cookies);
|
||||
([cookie], Redirect::to("/")).into_response()
|
||||
}
|
||||
Err(_) => Redirect::to("/login?error=Invalid+credentials").into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_logout() -> impl IntoResponse {
|
||||
pub async fn get_logout(State(state): State<AppState>) -> impl IntoResponse {
|
||||
let val = format!(
|
||||
"token=; HttpOnly; Path=/; SameSite=Strict; Max-Age=0{}",
|
||||
secure_flag()
|
||||
secure_flag(state.app_ctx.config.secure_cookies)
|
||||
);
|
||||
let cookie = (
|
||||
SET_COOKIE,
|
||||
@@ -246,7 +251,8 @@ pub async fn post_register(
|
||||
{
|
||||
Ok(result) => {
|
||||
let max_age = (result.expires_at - Utc::now()).num_seconds().max(0);
|
||||
let cookie = set_cookie_header(&result.token, max_age);
|
||||
let cookie =
|
||||
set_cookie_header(&result.token, max_age, state.app_ctx.config.secure_cookies);
|
||||
([cookie], Redirect::to("/")).into_response()
|
||||
}
|
||||
Err(_) => {
|
||||
|
||||
@@ -12,6 +12,8 @@ use crate::{handlers, state::AppState};
|
||||
|
||||
pub fn build_router(state: AppState, ap_router: Router) -> Router {
|
||||
let rate_limit = state.app_ctx.config.rate_limit;
|
||||
let secure_cookies = state.app_ctx.config.secure_cookies;
|
||||
let cors_origins = state.app_ctx.config.cors_origins.clone();
|
||||
|
||||
let ap_cfg = GovernorConfigBuilder::default()
|
||||
.with_extractor(PeerIp::default())
|
||||
@@ -23,8 +25,8 @@ pub fn build_router(state: AppState, ap_router: Router) -> Router {
|
||||
|
||||
Router::new()
|
||||
.route("/health", routing::get(health_handler))
|
||||
.merge(html_routes(rate_limit))
|
||||
.merge(api_routes(rate_limit))
|
||||
.merge(html_routes(rate_limit, secure_cookies))
|
||||
.merge(api_routes(rate_limit, &cors_origins))
|
||||
.nest_service("/static", ServeDir::new("static"))
|
||||
.nest_service(
|
||||
"/app",
|
||||
@@ -44,7 +46,7 @@ fn per_minute(n: u64) -> Quota {
|
||||
Quota::requests_per_minute(n)
|
||||
}
|
||||
|
||||
fn html_routes(rate_limit: u64) -> Router<AppState> {
|
||||
fn html_routes(rate_limit: u64, secure_cookies: bool) -> Router<AppState> {
|
||||
let auth = Router::new()
|
||||
.route(
|
||||
"/login",
|
||||
@@ -188,7 +190,10 @@ fn html_routes(rate_limit: u64) -> Router<AppState> {
|
||||
#[cfg(feature = "federation")]
|
||||
let base = base.merge(federation_html_routes());
|
||||
|
||||
base.layer(axum::middleware::from_fn(crate::csrf::csrf_middleware))
|
||||
base.layer(axum::middleware::from_fn_with_state(
|
||||
crate::csrf::SecureCookies(secure_cookies),
|
||||
crate::csrf::csrf_middleware,
|
||||
))
|
||||
}
|
||||
|
||||
fn social_html_routes() -> Router<AppState> {
|
||||
@@ -278,11 +283,10 @@ fn federation_html_routes() -> Router<AppState> {
|
||||
)
|
||||
}
|
||||
|
||||
fn cors_layer() -> CorsLayer {
|
||||
fn cors_layer(origins: &str) -> CorsLayer {
|
||||
use axum::http::{HeaderName, Method};
|
||||
use tower_http::cors::AllowOrigin;
|
||||
|
||||
let origins = std::env::var("CORS_ORIGINS").unwrap_or_default();
|
||||
let layer = CorsLayer::new()
|
||||
.allow_methods([
|
||||
Method::GET,
|
||||
@@ -319,7 +323,7 @@ fn cors_layer() -> CorsLayer {
|
||||
}
|
||||
}
|
||||
|
||||
fn api_routes(rate_limit: u64) -> Router<AppState> {
|
||||
fn api_routes(rate_limit: u64, cors_origins: &str) -> Router<AppState> {
|
||||
let cfg = GovernorConfigBuilder::default()
|
||||
.with_extractor(PeerIp::default())
|
||||
.expect_connect_info()
|
||||
@@ -506,7 +510,7 @@ fn api_routes(rate_limit: u64) -> Router<AppState> {
|
||||
Router::new()
|
||||
.nest("/api/v1", base.layer(GovernorLayer::new(cfg)))
|
||||
.nest("/api/v1", webhook_routes)
|
||||
.layer(cors_layer())
|
||||
.layer(cors_layer(cors_origins))
|
||||
}
|
||||
|
||||
fn social_api_routes() -> Router<AppState> {
|
||||
|
||||
@@ -843,6 +843,8 @@ pub(crate) fn build_test_state_from(
|
||||
base_url: "http://localhost:3000".to_string(),
|
||||
rate_limit: 20,
|
||||
refresh_ttl_seconds: 2_592_000,
|
||||
secure_cookies: false,
|
||||
cors_origins: String::new(),
|
||||
wrapup: application::config::WrapUpConfig {
|
||||
font_path: None,
|
||||
logo_path: None,
|
||||
|
||||
Reference in New Issue
Block a user