otter/src/server/gpodder/advanced/auth.rs

107 lines
2.8 KiB
Rust

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<Context> {
Router::new()
.route("/{username}/login.json", post(post_login))
.route("/{_username}/logout.json", post(post_logout))
}
async fn post_login(
State(ctx): State<Context>,
Path(username): Path<String>,
jar: CookieJar,
TypedHeader(auth): TypedHeader<Authorization<Basic>>,
user_agent: Option<TypedHeader<UserAgent>>,
) -> AppResult<CookieJar> {
// 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::<i64>().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<Context>,
Path(_username): Path<String>,
jar: CookieJar,
) -> AppResult<CookieJar> {
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)
}
}