use axum::{ extract::{Path, State}, routing::post, Router, }; use axum_extra::{ extract::{cookie::Cookie, CookieJar}, headers::{authorization::Basic, Authorization, UserAgent}, TypedHeader, }; use cookie::time::Duration; use gpodder::AuthErr; use crate::server::{ error::{AppError, AppResult}, gpodder::SESSION_ID_COOKIE, Context, }; pub fn router() -> Router { Router::new() .route("/{username}/login.json", post(post_login)) .route("/{_username}/logout.json", post(post_logout)) } async fn post_login( State(ctx): State, Path(username): Path, jar: CookieJar, TypedHeader(auth): TypedHeader>, user_agent: Option>, ) -> AppResult { // These should be the same according to the spec if username != auth.username() { return Err(AppError::BadRequest); } // If a session token is present, we check if it's valid first and do nothing if it is if let Some(session_id) = jar .get(SESSION_ID_COOKIE) .and_then(|c| c.value().parse::().ok()) { let ctx = ctx.clone(); match tokio::task::spawn_blocking(move || { let session = ctx.store.get_session(session_id)?; ctx.store.refresh_session(&session)?; Ok(session) }) .await .unwrap() { Ok(_) => { return Ok(jar); } Err(gpodder::AuthErr::UnknownSession) => {} Err(err) => { return Err(AppError::from(err)); } } } let session = tokio::task::spawn_blocking(move || { let user = ctx .store .validate_credentials(auth.username(), auth.password())?; let user_agent = user_agent.map(|header| header.to_string()); let session = ctx.store.create_session(&user, user_agent)?; Ok::<_, AuthErr>(session) }) .await .unwrap()?; Ok(jar.add( Cookie::build((SESSION_ID_COOKIE, session.id.to_string())) .secure(false) .same_site(cookie::SameSite::Strict) .http_only(true) .path("/api") .max_age(Duration::days(365)), )) } async fn post_logout( State(ctx): State, Path(_username): Path, jar: CookieJar, ) -> AppResult { if let Some(session_id) = jar.get(SESSION_ID_COOKIE) { let session_id: i64 = session_id .value() .parse() .map_err(|_| AppError::BadRequest)?; // TODO reintroduce username check tokio::task::spawn_blocking(move || ctx.store.remove_session(session_id)) .await .unwrap()?; Ok(jar.remove(SESSION_ID_COOKIE)) } else { Ok(jar) } }