use std::sync::Arc; use argon2::{Argon2, PasswordHash, PasswordVerifier}; use chrono::{DateTime, TimeDelta, Utc}; use rand::Rng; use super::{models, AuthErr, Store}; const MAX_SESSION_AGE: i64 = 60 * 60 * 24 * 7; #[derive(Clone)] pub struct GpodderRepository { store: Arc, } impl GpodderRepository { pub fn new(store: impl Store + Send + Sync + 'static) -> Self { Self { store: Arc::new(store), } } pub fn get_session(&self, session_id: i64) -> Result { let session = self .store .get_session(session_id)? .ok_or(AuthErr::UnknownSession)?; // Expired sessions still in the database are considered removed if Utc::now() - session.last_seen > TimeDelta::new(MAX_SESSION_AGE, 0).unwrap() { Err(AuthErr::UnknownSession) } else { Ok(session) } } pub fn validate_credentials( &self, username: &str, password: &str, ) -> Result { let user = self.store.get_user(username)?.ok_or(AuthErr::UnknownUser)?; let password_hash = PasswordHash::new(&user.password_hash).unwrap(); if Argon2::default() .verify_password(password.as_bytes(), &password_hash) .is_ok() { Ok(user) } else { Err(AuthErr::InvalidPassword) } } pub fn create_session(&self, user: &models::User) -> Result { let session = models::Session { id: rand::thread_rng().gen(), last_seen: Utc::now(), user: user.clone(), }; self.store.insert_session(&session)?; 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 { 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, AuthErr> { self.store.devices_for_user(user) } pub fn update_device_info( &self, user: &models::User, device_id: &str, patch: models::DevicePatch, ) -> Result<(), AuthErr> { self.store.update_device_info(user, device_id, patch) } pub fn update_device_sync_status( &self, user: &models::User, sync: Vec>, unsync: Vec<&str>, ) -> Result<(), AuthErr> { todo!("perform diff devices to sync and unsync") } pub fn devices_by_sync_group( &self, user: &models::User, ) -> Result<(Vec, Vec>), AuthErr> { self.store.devices_by_sync_group(user) } pub fn subscriptions_for_device( &self, user: &models::User, device_id: &str, ) -> Result, AuthErr> { self.store.subscriptions_for_device(user, device_id) } pub fn subscriptions_for_user( &self, user: &models::User, ) -> Result, AuthErr> { self.store.subscriptions_for_user(user) } pub fn set_subscriptions_for_device( &self, user: &models::User, device_id: &str, urls: Vec, ) -> Result, AuthErr> { let time_changed = Utc::now(); self.store .set_subscriptions_for_device(user, device_id, urls, time_changed)?; Ok(time_changed + TimeDelta::seconds(1)) } pub fn update_subscriptions_for_device( &self, user: &models::User, device_id: &str, add: Vec, remove: Vec, ) -> Result, AuthErr> { let time_changed = Utc::now(); self.store .update_subscriptions_for_device(user, device_id, add, remove, time_changed)?; Ok(time_changed + TimeDelta::seconds(1)) } pub fn subscription_updates_for_device( &self, user: &models::User, device_id: &str, since: DateTime, ) -> Result< ( DateTime, Vec, Vec, ), AuthErr, > { let now = chrono::Utc::now(); let (added, removed) = self .store .subscription_updates_for_device(user, device_id, since)?; let max_time_changed = added .iter() .chain(removed.iter()) .map(|s| s.time_changed) .max() .unwrap_or(now); Ok((max_time_changed + TimeDelta::seconds(1), added, removed)) } pub fn add_episode_actions( &self, user: &models::User, actions: Vec, ) -> Result, AuthErr> { let time_changed = Utc::now(); self.store .add_episode_actions(user, actions, time_changed)?; Ok(time_changed + TimeDelta::seconds(1)) } pub fn episode_actions_for_user( &self, user: &models::User, since: Option>, podcast: Option, device: Option, aggregated: bool, ) -> Result<(DateTime, Vec), AuthErr> { let now = chrono::Utc::now(); let actions = self .store .episode_actions_for_user(user, since, podcast, device, aggregated)?; let max_time_changed = actions.iter().map(|a| a.time_changed).max().unwrap_or(now); Ok((max_time_changed + TimeDelta::seconds(1), actions)) } }