feat: add separate auth middleware for web routes
parent
b3e49be299
commit
3071685950
|
@ -8,7 +8,7 @@ use axum::{
|
|||
use crate::server::{
|
||||
error::{AppError, AppResult},
|
||||
gpodder::{
|
||||
auth_middleware,
|
||||
auth_api_middleware,
|
||||
format::{Format, StringWithFormat},
|
||||
models,
|
||||
},
|
||||
|
@ -19,7 +19,7 @@ pub fn router(ctx: Context) -> Router<Context> {
|
|||
Router::new()
|
||||
.route("/{username}", get(get_devices))
|
||||
.route("/{username}/{id}", post(post_device))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
|
||||
}
|
||||
|
||||
async fn get_devices(
|
||||
|
|
|
@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::server::{
|
||||
error::{AppError, AppResult},
|
||||
gpodder::{
|
||||
auth_middleware,
|
||||
auth_api_middleware,
|
||||
format::{Format, StringWithFormat},
|
||||
models,
|
||||
models::UpdatedUrlsResponse,
|
||||
|
@ -24,7 +24,7 @@ pub fn router(ctx: Context) -> Router<Context> {
|
|||
"/{username}",
|
||||
post(post_episode_actions).get(get_episode_actions),
|
||||
)
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
|
||||
}
|
||||
|
||||
async fn post_episode_actions(
|
||||
|
|
|
@ -9,7 +9,7 @@ use serde::Deserialize;
|
|||
use crate::server::{
|
||||
error::{AppError, AppResult},
|
||||
gpodder::{
|
||||
auth_middleware,
|
||||
auth_api_middleware,
|
||||
format::{Format, StringWithFormat},
|
||||
models::{SubscriptionDelta, SubscriptionDeltaResponse, UpdatedUrlsResponse},
|
||||
},
|
||||
|
@ -22,7 +22,7 @@ pub fn router(ctx: Context) -> Router<Context> {
|
|||
"/{username}/{id}",
|
||||
post(post_subscription_changes).get(get_subscription_changes),
|
||||
)
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
|
||||
}
|
||||
|
||||
pub async fn post_subscription_changes(
|
||||
|
|
|
@ -8,7 +8,7 @@ use axum::{
|
|||
use crate::server::{
|
||||
error::{AppError, AppResult},
|
||||
gpodder::{
|
||||
auth_middleware,
|
||||
auth_api_middleware,
|
||||
format::{Format, StringWithFormat},
|
||||
models::{SyncStatus, SyncStatusDelta},
|
||||
},
|
||||
|
@ -21,7 +21,7 @@ pub fn router(ctx: Context) -> Router<Context> {
|
|||
"/{username}",
|
||||
get(get_sync_status).post(post_sync_status_changes),
|
||||
)
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
|
||||
}
|
||||
|
||||
pub async fn get_sync_status(
|
||||
|
|
|
@ -36,8 +36,13 @@ pub fn router(ctx: Context) -> Router<Context> {
|
|||
))
|
||||
}
|
||||
|
||||
/// This middleware accepts
|
||||
pub async fn auth_middleware(State(ctx): State<Context>, mut req: Request, next: Next) -> Response {
|
||||
/// Middleware that can authenticate both with session cookies and basic auth. If basic auth is
|
||||
/// used, no session is created. If authentication fails, the server returns a 401.
|
||||
pub async fn auth_api_middleware(
|
||||
State(ctx): State<Context>,
|
||||
mut req: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
// SAFETY: this extractor's error type is Infallible
|
||||
let mut jar: CookieJar = req.extract_parts().await.unwrap();
|
||||
let mut auth_user = None;
|
||||
|
|
|
@ -7,7 +7,7 @@ use axum::{
|
|||
|
||||
use crate::server::{
|
||||
error::{AppError, AppResult},
|
||||
gpodder::{auth_middleware, format::StringWithFormat},
|
||||
gpodder::{auth_api_middleware, format::StringWithFormat},
|
||||
Context,
|
||||
};
|
||||
|
||||
|
@ -18,7 +18,7 @@ pub fn router(ctx: Context) -> Router<Context> {
|
|||
get(get_device_subscriptions).put(put_device_subscriptions),
|
||||
)
|
||||
.route("/{username}", get(get_user_subscriptions))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
|
||||
}
|
||||
|
||||
pub async fn get_device_subscriptions(
|
||||
|
|
|
@ -10,7 +10,8 @@ use axum::{
|
|||
extract::Request,
|
||||
http::StatusCode,
|
||||
middleware::Next,
|
||||
response::{IntoResponse, Response},
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use http_body_util::BodyExt;
|
||||
|
@ -27,6 +28,7 @@ pub fn app(ctx: Context) -> Router {
|
|||
.merge(gpodder::router(ctx.clone()))
|
||||
.nest("/static", r#static::router())
|
||||
.nest("/_", web::router(ctx.clone()))
|
||||
.route("/", get(|| async { Redirect::to("/_") }))
|
||||
.layer(axum::middleware::from_fn(header_logger))
|
||||
.layer(axum::middleware::from_fn(body_logger))
|
||||
.layer(TraceLayer::new_for_http())
|
||||
|
|
|
@ -1,13 +1,73 @@
|
|||
use axum::{extract::State, http::HeaderMap, routing::get, Router};
|
||||
use axum::{
|
||||
extract::{Request, State},
|
||||
http::HeaderMap,
|
||||
middleware::{self, Next},
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
routing::get,
|
||||
RequestExt, Router,
|
||||
};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use cookie::Cookie;
|
||||
|
||||
use crate::web::{Page, TemplateExt, TemplateResponse, View};
|
||||
|
||||
use super::Context;
|
||||
use super::{error::AppError, Context};
|
||||
|
||||
pub fn router(_ctx: Context) -> Router<Context> {
|
||||
Router::new().route("/", get(get_index))
|
||||
const SESSION_ID_COOKIE: &str = "sessionid";
|
||||
|
||||
pub fn router(ctx: Context) -> Router<Context> {
|
||||
Router::new()
|
||||
.route("/", get(get_index))
|
||||
.layer(middleware::from_fn_with_state(
|
||||
ctx.clone(),
|
||||
auth_web_middleware,
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_index(State(ctx): State<Context>, headers: HeaderMap) -> TemplateResponse<Page<View>> {
|
||||
View::Index.page(&headers).response(&ctx.tera)
|
||||
}
|
||||
|
||||
/// Middleware that authenticates the current user via the session token. If the credentials are
|
||||
/// invalid, the user is redirected to the login page.
|
||||
pub async fn auth_web_middleware(
|
||||
State(ctx): State<Context>,
|
||||
mut req: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
// SAFETY: this extractor's error type is Infallible
|
||||
let mut jar: CookieJar = req.extract_parts().await.unwrap();
|
||||
let redirect = Redirect::to("/_/login");
|
||||
|
||||
if let Some(session_id) = jar
|
||||
.get(SESSION_ID_COOKIE)
|
||||
.and_then(|c| c.value().parse::<i64>().ok())
|
||||
{
|
||||
match tokio::task::spawn_blocking(move || {
|
||||
let session = ctx.store.get_session(session_id)?;
|
||||
ctx.store.refresh_session(&session)?;
|
||||
|
||||
Ok(session)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
Ok(session) => {
|
||||
req.extensions_mut().insert(session.user);
|
||||
|
||||
(jar, next.run(req).await).into_response()
|
||||
}
|
||||
Err(gpodder::AuthErr::UnknownSession) => {
|
||||
jar = jar.add(
|
||||
Cookie::build((SESSION_ID_COOKIE, String::new()))
|
||||
.max_age(cookie::time::Duration::ZERO),
|
||||
);
|
||||
|
||||
(jar, redirect).into_response()
|
||||
}
|
||||
Err(err) => AppError::from(err).into_response(),
|
||||
}
|
||||
} else {
|
||||
redirect.into_response()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,10 @@ pub fn initialize_tera() -> tera::Result<tera::Tera> {
|
|||
|
||||
tera.add_raw_templates([
|
||||
(BASE_TEMPLATE, include_str!("templates/base.html")),
|
||||
(View::Index.template(), include_str!("templates/index.html")),
|
||||
(
|
||||
View::Index.template(),
|
||||
include_str!("templates/views/index.html"),
|
||||
),
|
||||
])?;
|
||||
|
||||
Ok(tera)
|
||||
|
|
|
@ -7,7 +7,7 @@ pub enum View {
|
|||
impl Template for View {
|
||||
fn template(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Index => "index.html",
|
||||
Self::Index => "views/index.html",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue