215 lines
5.9 KiB
Rust
215 lines
5.9 KiB
Rust
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<dyn Store + Send + Sync>,
|
|
}
|
|
|
|
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<models::Session, AuthErr> {
|
|
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<models::User, AuthErr> {
|
|
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<models::Session, AuthErr> {
|
|
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<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)
|
|
}
|
|
|
|
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<Vec<&str>>,
|
|
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<models::Device>, Vec<Vec<models::Device>>), AuthErr> {
|
|
self.store.devices_by_sync_group(user)
|
|
}
|
|
|
|
pub fn subscriptions_for_device(
|
|
&self,
|
|
user: &models::User,
|
|
device_id: &str,
|
|
) -> Result<Vec<models::Subscription>, AuthErr> {
|
|
self.store.subscriptions_for_device(user, device_id)
|
|
}
|
|
|
|
pub fn subscriptions_for_user(
|
|
&self,
|
|
user: &models::User,
|
|
) -> Result<Vec<models::Subscription>, AuthErr> {
|
|
self.store.subscriptions_for_user(user)
|
|
}
|
|
|
|
pub fn set_subscriptions_for_device(
|
|
&self,
|
|
user: &models::User,
|
|
device_id: &str,
|
|
urls: Vec<String>,
|
|
) -> Result<DateTime<Utc>, 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<String>,
|
|
remove: Vec<String>,
|
|
) -> Result<DateTime<Utc>, 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<Utc>,
|
|
) -> Result<
|
|
(
|
|
DateTime<Utc>,
|
|
Vec<models::Subscription>,
|
|
Vec<models::Subscription>,
|
|
),
|
|
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<models::EpisodeAction>,
|
|
) -> Result<DateTime<Utc>, 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<DateTime<Utc>>,
|
|
podcast: Option<String>,
|
|
device: Option<String>,
|
|
aggregated: bool,
|
|
) -> Result<(DateTime<Utc>, Vec<models::EpisodeAction>), 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))
|
|
}
|
|
}
|