feat: migrate auth to repository system

episode-actions
Jef Roosens 2025-02-27 23:08:53 +01:00
parent adda030c3b
commit 2be126a7b3
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
4 changed files with 47 additions and 47 deletions

View File

@ -4,7 +4,7 @@ use rand::Rng;
use super::SqliteRepository; use super::SqliteRepository;
use crate::{ use crate::{
db::{self, schema::*}, db::{self, schema::*},
gpodder, gpodder::{self, AuthErr},
}; };
impl From<diesel::r2d2::PoolError> for gpodder::AuthErr { impl From<diesel::r2d2::PoolError> for gpodder::AuthErr {
@ -72,4 +72,28 @@ impl gpodder::AuthRepository for SqliteRepository {
Err(gpodder::AuthErr::UnknownUser) Err(gpodder::AuthErr::UnknownUser)
} }
} }
fn remove_session(&self, username: &str, session_id: i64) -> Result<(), gpodder::AuthErr> {
let conn = &mut self.pool.get()?;
if let Some(user) = sessions::table
.inner_join(users::table)
.filter(sessions::id.eq(session_id))
.select(db::User::as_select())
.get_result(conn)
.optional()?
{
if user.username == username {
Ok(
diesel::delete(sessions::table.filter(sessions::id.eq(session_id)))
.execute(conn)
.map(|_| ())?,
)
} else {
Err(AuthErr::UnknownUser)
}
} else {
Ok(())
}
}
} }

View File

@ -19,6 +19,8 @@ pub trait AuthRepository: Send + Sync {
username: &str, username: &str,
password: &str, password: &str,
) -> Result<(i64, models::User), AuthErr>; ) -> Result<(i64, models::User), AuthErr>;
fn remove_session(&self, username: &str, session_id: i64) -> Result<(), AuthErr>;
} }
pub trait DeviceRepository: Send + Sync { pub trait DeviceRepository: Send + Sync {

View File

@ -13,7 +13,7 @@ use axum_extra::{
}; };
use crate::{ use crate::{
db::{Session, User}, gpodder::AuthRepository,
server::{ server::{
error::{AppError, AppResult}, error::{AppError, AppResult},
gpodder::SESSION_ID_COOKIE, gpodder::SESSION_ID_COOKIE,
@ -38,20 +38,14 @@ async fn post_login(
return Err(AppError::BadRequest); return Err(AppError::BadRequest);
} }
let session = tokio::task::spawn_blocking(move || { let (session_id, _) = tokio::task::spawn_blocking(move || {
let user = User::by_username(&ctx.pool, auth.username())?.ok_or(AppError::NotFound)?; ctx.repo.create_session(auth.username(), auth.password())
if user.verify_password(auth.password()) {
Ok(Session::new_for_user(&ctx.pool, user.id)?)
} else {
Err(AppError::Unauthorized)
}
}) })
.await .await
.unwrap()?; .unwrap()?;
Ok(jar.add( Ok(jar.add(
Cookie::build((SESSION_ID_COOKIE, session.id.to_string())).expires(Expiration::Session), Cookie::build((SESSION_ID_COOKIE, session_id.to_string())).expires(Expiration::Session),
)) ))
} }
@ -66,21 +60,7 @@ async fn post_logout(
.parse() .parse()
.map_err(|_| AppError::BadRequest)?; .map_err(|_| AppError::BadRequest)?;
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || ctx.repo.remove_session(&username, session_id))
if let Some(session) = Session::by_id(&ctx.pool, session_id)? {
let user = session.user(&ctx.pool)?.ok_or(AppError::NotFound)?;
// The requested user to logout should be the same as the one linked to the session
// ID
if username == user.username {
Ok(session.remove(&ctx.pool)?)
} else {
Err(AppError::BadRequest)
}
} else {
Ok(false)
}
})
.await .await
.unwrap()?; .unwrap()?;

View File

@ -20,7 +20,10 @@ use axum_extra::{
}; };
use tower_http::set_header::SetResponseHeaderLayer; use tower_http::set_header::SetResponseHeaderLayer;
use crate::{db, gpodder, server::error::AppError}; use crate::{
gpodder::{self, AuthRepository},
server::error::AppError,
};
use super::Context; use super::Context;
@ -50,13 +53,13 @@ pub async fn auth_middleware(State(ctx): State<Context>, mut req: Request, next:
.get(SESSION_ID_COOKIE) .get(SESSION_ID_COOKIE)
.and_then(|c| c.value().parse::<i64>().ok()) .and_then(|c| c.value().parse::<i64>().ok())
{ {
match tokio::task::spawn_blocking(move || db::Session::user_from_id(&ctx.pool, session_id)) match tokio::task::spawn_blocking(move || ctx.repo.validate_session(session_id))
.await .await
.unwrap() .unwrap()
.map_err(AppError::Db) .map_err(AppError::from)
{ {
Ok(user) => { Ok(user) => {
auth_user = user; auth_user = Some(user);
} }
Err(err) => { Err(err) => {
return err.into_response(); return err.into_response();
@ -67,21 +70,15 @@ pub async fn auth_middleware(State(ctx): State<Context>, mut req: Request, next:
.await .await
{ {
match tokio::task::spawn_blocking(move || { match tokio::task::spawn_blocking(move || {
let user = ctx.repo.create_session(auth.username(), auth.password())
db::User::by_username(&ctx.pool, auth.username())?.ok_or(AppError::NotFound)?;
if user.verify_password(auth.password()) {
Ok((db::Session::new_for_user(&ctx.pool, user.id)?, user))
} else {
Err(AppError::Unauthorized)
}
}) })
.await .await
.unwrap() .unwrap()
.map_err(AppError::from)
{ {
Ok((session, user)) => { Ok((session_id, user)) => {
auth_user = Some(user); auth_user = Some(user);
new_session_id = Some(session.id); new_session_id = Some(session_id);
} }
Err(err) => { Err(err) => {
return err.into_response(); return err.into_response();
@ -90,11 +87,8 @@ pub async fn auth_middleware(State(ctx): State<Context>, mut req: Request, next:
} }
if let Some(user) = auth_user { if let Some(user) = auth_user {
req.extensions_mut().insert(user.clone()); req.extensions_mut().insert(user);
req.extensions_mut().insert(gpodder::User {
username: user.username,
id: user.id,
});
let res = next.run(req).await; let res = next.run(req).await;
if let Some(session_id) = new_session_id { if let Some(session_id) = new_session_id {