Compare commits
	
		
			No commits in common. "65e83ecb1fac44975c4ab8a27ebbbc161dd6561c" and "330877c8c587b2790aec9c0e5d0115899f155a6b" have entirely different histories. 
		
	
	
		
			65e83ecb1f
			...
			330877c8c5
		
	
		| 
						 | 
				
			
			@ -1,5 +1,3 @@
 | 
			
		|||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
use crate::{db, server};
 | 
			
		||||
 | 
			
		||||
pub fn serve(config: &crate::config::Config) -> u8 {
 | 
			
		||||
| 
						 | 
				
			
			@ -13,7 +11,7 @@ pub fn serve(config: &crate::config::Config) -> u8 {
 | 
			
		|||
    let ctx = server::Context {
 | 
			
		||||
        store: crate::gpodder::GpodderRepository::new(repo),
 | 
			
		||||
    };
 | 
			
		||||
    let app = server::app(ctx.clone());
 | 
			
		||||
    let app = server::app(ctx);
 | 
			
		||||
 | 
			
		||||
    let rt = tokio::runtime::Builder::new_multi_thread()
 | 
			
		||||
        .enable_all()
 | 
			
		||||
| 
						 | 
				
			
			@ -23,28 +21,7 @@ pub fn serve(config: &crate::config::Config) -> u8 {
 | 
			
		|||
    let address = format!("{}:{}", config.domain, config.port);
 | 
			
		||||
    tracing::info!("Starting server on {address}");
 | 
			
		||||
 | 
			
		||||
    let session_removal_duration = Duration::from_secs(config.session_cleanup_interval);
 | 
			
		||||
 | 
			
		||||
    rt.block_on(async {
 | 
			
		||||
        tokio::task::spawn(async move {
 | 
			
		||||
            let mut interval = tokio::time::interval(session_removal_duration);
 | 
			
		||||
 | 
			
		||||
            loop {
 | 
			
		||||
                interval.tick().await;
 | 
			
		||||
 | 
			
		||||
                tracing::info!("Performing session cleanup");
 | 
			
		||||
 | 
			
		||||
                match ctx.store.remove_old_sessions() {
 | 
			
		||||
                    Ok(n) => {
 | 
			
		||||
                        tracing::info!("Removed {} old sessions", n);
 | 
			
		||||
                    }
 | 
			
		||||
                    Err(err) => {
 | 
			
		||||
                        tracing::error!("Error occured during session cleanup: {}", err);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let listener = tokio::net::TcpListener::bind(address).await.unwrap();
 | 
			
		||||
        axum::serve(listener, app.into_make_service())
 | 
			
		||||
            .await
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,8 +10,6 @@ pub struct Config {
 | 
			
		|||
    pub domain: String,
 | 
			
		||||
    #[serde(default = "default_port")]
 | 
			
		||||
    pub port: u16,
 | 
			
		||||
    #[serde(default = "default_session_cleanup_interval")]
 | 
			
		||||
    pub session_cleanup_interval: u64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn default_data_dir() -> PathBuf {
 | 
			
		||||
| 
						 | 
				
			
			@ -25,8 +23,3 @@ fn default_domain() -> String {
 | 
			
		|||
fn default_port() -> u16 {
 | 
			
		||||
    8080
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn default_session_cleanup_interval() -> u64 {
 | 
			
		||||
    // Default is once a day
 | 
			
		||||
    60 * 60 * 24
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -181,29 +181,4 @@ impl gpodder::AuthStore for SqliteRepository {
 | 
			
		|||
        .execute(&mut self.pool.get()?)
 | 
			
		||||
        .map(|_| ())?)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn refresh_session(
 | 
			
		||||
        &self,
 | 
			
		||||
        session: &gpodder::Session,
 | 
			
		||||
        timestamp: DateTime<chrono::Utc>,
 | 
			
		||||
    ) -> Result<(), AuthErr> {
 | 
			
		||||
        if diesel::update(sessions::table.filter(sessions::id.eq(session.id)))
 | 
			
		||||
            .set(sessions::last_seen.eq(timestamp.timestamp()))
 | 
			
		||||
            .execute(&mut self.pool.get()?)?
 | 
			
		||||
            == 0
 | 
			
		||||
        {
 | 
			
		||||
            Err(AuthErr::UnknownSession)
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn remove_old_sessions(&self, min_last_seen: DateTime<chrono::Utc>) -> Result<usize, AuthErr> {
 | 
			
		||||
        let min_last_seen = min_last_seen.timestamp();
 | 
			
		||||
 | 
			
		||||
        Ok(
 | 
			
		||||
            diesel::delete(sessions::table.filter(sessions::last_seen.lt(min_last_seen)))
 | 
			
		||||
                .execute(&mut self.pool.get()?)?,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,10 @@
 | 
			
		|||
pub mod models;
 | 
			
		||||
mod repository;
 | 
			
		||||
 | 
			
		||||
use std::fmt::Display;
 | 
			
		||||
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
pub use models::*;
 | 
			
		||||
pub use repository::GpodderRepository;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum AuthErr {
 | 
			
		||||
    UnknownSession,
 | 
			
		||||
    UnknownUser,
 | 
			
		||||
| 
						 | 
				
			
			@ -15,19 +12,6 @@ pub enum AuthErr {
 | 
			
		|||
    Other(Box<dyn std::error::Error + Sync + Send>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Display for AuthErr {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::UnknownUser => write!(f, "unknown user"),
 | 
			
		||||
            Self::UnknownSession => write!(f, "unknown session"),
 | 
			
		||||
            Self::InvalidPassword => write!(f, "invalid password"),
 | 
			
		||||
            Self::Other(err) => err.fmt(f),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::error::Error for AuthErr {}
 | 
			
		||||
 | 
			
		||||
pub trait Store:
 | 
			
		||||
    AuthStore + DeviceRepository + SubscriptionRepository + EpisodeActionRepository
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -68,12 +52,6 @@ pub trait AuthStore {
 | 
			
		|||
 | 
			
		||||
    /// Remove the session with the given session ID
 | 
			
		||||
    fn remove_session(&self, session_id: i64) -> Result<(), AuthErr>;
 | 
			
		||||
 | 
			
		||||
    /// Update the session's timestamp
 | 
			
		||||
    fn refresh_session(&self, session: &Session, timestamp: DateTime<Utc>) -> Result<(), AuthErr>;
 | 
			
		||||
 | 
			
		||||
    /// Remove any sessions whose last_seen timestamp is before the given minimum value
 | 
			
		||||
    fn remove_old_sessions(&self, min_last_seen: DateTime<Utc>) -> Result<usize, AuthErr>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait DeviceRepository {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ impl GpodderRepository {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_session(&self, session_id: i64) -> Result<models::Session, AuthErr> {
 | 
			
		||||
    pub fn validate_session(&self, session_id: i64) -> Result<models::Session, AuthErr> {
 | 
			
		||||
        let session = self
 | 
			
		||||
            .store
 | 
			
		||||
            .get_session(session_id)?
 | 
			
		||||
| 
						 | 
				
			
			@ -65,22 +65,10 @@ impl GpodderRepository {
 | 
			
		|||
        Ok(session)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn refresh_session(&self, session: &models::Session) -> Result<(), AuthErr> {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
 | 
			
		||||
        self.store.refresh_session(session, now)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn remove_session(&self, session_id: i64) -> Result<(), AuthErr> {
 | 
			
		||||
        self.store.remove_session(session_id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn remove_old_sessions(&self) -> Result<usize, AuthErr> {
 | 
			
		||||
        let min_last_seen = Utc::now() - TimeDelta::seconds(MAX_SESSION_AGE);
 | 
			
		||||
 | 
			
		||||
        self.store.remove_old_sessions(min_last_seen)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn devices_for_user(&self, user: &models::User) -> Result<Vec<models::Device>, AuthErr> {
 | 
			
		||||
        self.store.devices_for_user(user)
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,11 +4,13 @@ use axum::{
 | 
			
		|||
    Router,
 | 
			
		||||
};
 | 
			
		||||
use axum_extra::{
 | 
			
		||||
    extract::{cookie::Cookie, CookieJar},
 | 
			
		||||
    extract::{
 | 
			
		||||
        cookie::{Cookie, Expiration},
 | 
			
		||||
        CookieJar,
 | 
			
		||||
    },
 | 
			
		||||
    headers::{authorization::Basic, Authorization},
 | 
			
		||||
    TypedHeader,
 | 
			
		||||
};
 | 
			
		||||
use cookie::time::Duration;
 | 
			
		||||
 | 
			
		||||
use crate::server::{
 | 
			
		||||
    error::{AppError, AppResult},
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +45,7 @@ async fn post_login(
 | 
			
		|||
    .unwrap()?;
 | 
			
		||||
 | 
			
		||||
    Ok(jar.add(
 | 
			
		||||
        Cookie::build((SESSION_ID_COOKIE, session.id.to_string())).max_age(Duration::days(365)),
 | 
			
		||||
        Cookie::build((SESSION_ID_COOKIE, session.id.to_string())).expires(Expiration::Session),
 | 
			
		||||
    ))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,15 +47,10 @@ pub async fn auth_middleware(State(ctx): State<Context>, mut req: Request, next:
 | 
			
		|||
        .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()
 | 
			
		||||
        let ctx_clone = ctx.clone();
 | 
			
		||||
        match tokio::task::spawn_blocking(move || ctx_clone.store.validate_session(session_id))
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
        {
 | 
			
		||||
            Ok(session) => {
 | 
			
		||||
                auth_user = Some(session.user);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue