107 lines
2.8 KiB
Rust
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)
|
|
}
|
|
}
|