Compare commits
	
		
			3 Commits 
		
	
	
		
			2249d986eb
			...
			320a46c0f3
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
									
								
								 | 
						320a46c0f3 | |
| 
							
							
								
									
								
								 | 
						0849c88796 | |
| 
							
							
								
									
								
								 | 
						12c1c72d4d | 
| 
						 | 
					@ -12,6 +12,8 @@ create table sessions (
 | 
				
			||||||
        references users (id)
 | 
					        references users (id)
 | 
				
			||||||
        on delete cascade,
 | 
					        on delete cascade,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    last_seen bigint not null,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    unique (id, user_id)
 | 
					    unique (id, user_id)
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
-- This file should undo anything in `up.sql`
 | 
					 | 
				
			||||||
alter table sessions
 | 
					 | 
				
			||||||
    drop column last_seen;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
-- Your SQL goes here
 | 
					 | 
				
			||||||
alter table sessions
 | 
					 | 
				
			||||||
    add column last_seen bigint not null default 0;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,5 @@
 | 
				
			||||||
use chrono::DateTime;
 | 
					use chrono::DateTime;
 | 
				
			||||||
use diesel::prelude::*;
 | 
					use diesel::prelude::*;
 | 
				
			||||||
use rand::Rng;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::SqliteRepository;
 | 
					use super::SqliteRepository;
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
| 
						 | 
					@ -30,113 +29,6 @@ impl From<db::User> for gpodder::User {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl gpodder::AuthRepository for SqliteRepository {
 | 
					 | 
				
			||||||
    fn validate_credentials(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        username: &str,
 | 
					 | 
				
			||||||
        password: &str,
 | 
					 | 
				
			||||||
    ) -> Result<gpodder::models::User, AuthErr> {
 | 
					 | 
				
			||||||
        if let Some(user) = users::table
 | 
					 | 
				
			||||||
            .select(db::User::as_select())
 | 
					 | 
				
			||||||
            .filter(users::username.eq(username))
 | 
					 | 
				
			||||||
            .first(&mut self.pool.get()?)
 | 
					 | 
				
			||||||
            .optional()?
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if user.verify_password(password) {
 | 
					 | 
				
			||||||
                Ok(gpodder::User {
 | 
					 | 
				
			||||||
                    id: user.id,
 | 
					 | 
				
			||||||
                    username: user.username,
 | 
					 | 
				
			||||||
                    password_hash: user.password_hash,
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                Err(gpodder::AuthErr::InvalidPassword)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Err(gpodder::AuthErr::UnknownUser)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn validate_session(&self, session_id: i64) -> Result<gpodder::User, gpodder::AuthErr> {
 | 
					 | 
				
			||||||
        match sessions::dsl::sessions
 | 
					 | 
				
			||||||
            .inner_join(users::table)
 | 
					 | 
				
			||||||
            .filter(sessions::id.eq(session_id))
 | 
					 | 
				
			||||||
            .select(db::User::as_select())
 | 
					 | 
				
			||||||
            .get_result(&mut self.pool.get()?)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Ok(user) => Ok(gpodder::User {
 | 
					 | 
				
			||||||
                id: user.id,
 | 
					 | 
				
			||||||
                username: user.username,
 | 
					 | 
				
			||||||
                password_hash: user.password_hash,
 | 
					 | 
				
			||||||
            }),
 | 
					 | 
				
			||||||
            Err(diesel::result::Error::NotFound) => Err(gpodder::AuthErr::UnknownSession),
 | 
					 | 
				
			||||||
            Err(err) => Err(gpodder::AuthErr::Other(Box::new(err))),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn create_session(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        username: &str,
 | 
					 | 
				
			||||||
        password: &str,
 | 
					 | 
				
			||||||
    ) -> Result<(i64, gpodder::models::User), gpodder::AuthErr> {
 | 
					 | 
				
			||||||
        if let Some(user) = users::table
 | 
					 | 
				
			||||||
            .select(db::User::as_select())
 | 
					 | 
				
			||||||
            .filter(users::username.eq(username))
 | 
					 | 
				
			||||||
            .first(&mut self.pool.get()?)
 | 
					 | 
				
			||||||
            .optional()?
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if user.verify_password(password) {
 | 
					 | 
				
			||||||
                let id: i64 = rand::thread_rng().gen();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let session_id = db::Session {
 | 
					 | 
				
			||||||
                    id,
 | 
					 | 
				
			||||||
                    user_id: user.id,
 | 
					 | 
				
			||||||
                    last_seen: chrono::Utc::now().timestamp(),
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                .insert_into(sessions::table)
 | 
					 | 
				
			||||||
                .returning(sessions::id)
 | 
					 | 
				
			||||||
                .get_result(&mut self.pool.get()?)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Ok((
 | 
					 | 
				
			||||||
                    session_id,
 | 
					 | 
				
			||||||
                    gpodder::User {
 | 
					 | 
				
			||||||
                        id: user.id,
 | 
					 | 
				
			||||||
                        username: user.username,
 | 
					 | 
				
			||||||
                        password_hash: user.password_hash,
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                ))
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                Err(gpodder::AuthErr::InvalidPassword)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            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(())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl gpodder::AuthStore for SqliteRepository {
 | 
					impl gpodder::AuthStore for SqliteRepository {
 | 
				
			||||||
    fn get_user(&self, username: &str) -> Result<Option<gpodder::models::User>, AuthErr> {
 | 
					    fn get_user(&self, username: &str) -> Result<Option<gpodder::models::User>, AuthErr> {
 | 
				
			||||||
        Ok(users::table
 | 
					        Ok(users::table
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
 | 
					use chrono::{DateTime, Utc};
 | 
				
			||||||
use diesel::prelude::*;
 | 
					use diesel::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::SqliteRepository;
 | 
					use super::SqliteRepository;
 | 
				
			||||||
| 
						 | 
					@ -95,4 +96,35 @@ impl gpodder::DeviceRepository for SqliteRepository {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn merge_sync_groups(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        user: &gpodder::User,
 | 
				
			||||||
 | 
					        device_ids: Vec<&str>,
 | 
				
			||||||
 | 
					    ) -> Result<i64, gpodder::AuthErr> {
 | 
				
			||||||
 | 
					        todo!()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn remove_from_sync_group(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        user: &gpodder::User,
 | 
				
			||||||
 | 
					        device_ids: Vec<&str>,
 | 
				
			||||||
 | 
					    ) -> Result<(), gpodder::AuthErr> {
 | 
				
			||||||
 | 
					        todo!()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn synchronize_sync_group(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        group_id: i64,
 | 
				
			||||||
 | 
					        time_changed: DateTime<Utc>,
 | 
				
			||||||
 | 
					    ) -> Result<(), gpodder::AuthErr> {
 | 
				
			||||||
 | 
					        todo!()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn devices_by_sync_group(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        user: &gpodder::User,
 | 
				
			||||||
 | 
					    ) -> Result<(Vec<gpodder::Device>, Vec<Vec<gpodder::Device>>), gpodder::AuthErr> {
 | 
				
			||||||
 | 
					        todo!()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,24 +38,6 @@ impl<T> Store for T where
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub trait AuthRepository {
 | 
					 | 
				
			||||||
    /// Validate the given session ID and return its user.
 | 
					 | 
				
			||||||
    fn validate_session(&self, session_id: i64) -> Result<models::User, AuthErr>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Validate the credentials, returning the user if the credentials are correct.
 | 
					 | 
				
			||||||
    fn validate_credentials(&self, username: &str, password: &str)
 | 
					 | 
				
			||||||
        -> Result<models::User, AuthErr>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Create a new session for the given user.
 | 
					 | 
				
			||||||
    fn create_session(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        username: &str,
 | 
					 | 
				
			||||||
        password: &str,
 | 
					 | 
				
			||||||
    ) -> Result<(i64, models::User), AuthErr>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn remove_session(&self, username: &str, session_id: i64) -> Result<(), AuthErr>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub trait AuthStore {
 | 
					pub trait AuthStore {
 | 
				
			||||||
    /// Retrieve the session with the given session ID
 | 
					    /// Retrieve the session with the given session ID
 | 
				
			||||||
    fn get_session(&self, session_id: i64) -> Result<Option<models::Session>, AuthErr>;
 | 
					    fn get_session(&self, session_id: i64) -> Result<Option<models::Session>, AuthErr>;
 | 
				
			||||||
| 
						 | 
					@ -81,13 +63,44 @@ pub trait DeviceRepository {
 | 
				
			||||||
    fn devices_for_user(&self, user: &User) -> Result<Vec<Device>, AuthErr>;
 | 
					    fn devices_for_user(&self, user: &User) -> Result<Vec<Device>, AuthErr>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Update the information for the given device. If the device doesn't exist yet, it should be
 | 
					    /// Update the information for the given device. If the device doesn't exist yet, it should be
 | 
				
			||||||
    /// created.
 | 
					    /// created without a sync group.
 | 
				
			||||||
    fn update_device_info(
 | 
					    fn update_device_info(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        user: &User,
 | 
					        user: &User,
 | 
				
			||||||
        device_id: &str,
 | 
					        device_id: &str,
 | 
				
			||||||
        patch: DevicePatch,
 | 
					        patch: DevicePatch,
 | 
				
			||||||
    ) -> Result<(), AuthErr>;
 | 
					    ) -> Result<(), AuthErr>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Add the devices to the same sync group by:
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// * Merging the sync groups of all devices already in a sync group
 | 
				
			||||||
 | 
					    /// * Adding all devices not yet in a sync group to the newly merged sync group
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Returns
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// ID of the final sync group
 | 
				
			||||||
 | 
					    fn merge_sync_groups(&self, user: &User, device_ids: Vec<&str>) -> Result<i64, AuthErr>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Synchronize the sync group by adding or removing subscriptions to each device so that each
 | 
				
			||||||
 | 
					    /// device's subscription state is the same
 | 
				
			||||||
 | 
					    fn synchronize_sync_group(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        group_id: i64,
 | 
				
			||||||
 | 
					        time_changed: DateTime<Utc>,
 | 
				
			||||||
 | 
					    ) -> Result<(), AuthErr>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Remove the devices from their respective sync groups
 | 
				
			||||||
 | 
					    fn remove_from_sync_group(&self, user: &User, device_ids: Vec<&str>) -> Result<(), AuthErr>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Return all devices for the user, grouped per sync group
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Returns
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// A tuple (unsynced devices, sync groups)
 | 
				
			||||||
 | 
					    fn devices_by_sync_group(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        user: &User,
 | 
				
			||||||
 | 
					    ) -> Result<(Vec<Device>, Vec<Vec<Device>>), AuthErr>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub trait SubscriptionRepository {
 | 
					pub trait SubscriptionRepository {
 | 
				
			||||||
| 
						 | 
					@ -101,7 +114,8 @@ pub trait SubscriptionRepository {
 | 
				
			||||||
    /// Return all subscriptions for a given user
 | 
					    /// Return all subscriptions for a given user
 | 
				
			||||||
    fn subscriptions_for_user(&self, user: &User) -> Result<Vec<models::Subscription>, AuthErr>;
 | 
					    fn subscriptions_for_user(&self, user: &User) -> Result<Vec<models::Subscription>, AuthErr>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Replace the list of subscriptions for a device with the given list
 | 
					    /// Replace the list of subscriptions for a device and all devices in its sync group with the
 | 
				
			||||||
 | 
					    /// given list
 | 
				
			||||||
    fn set_subscriptions_for_device(
 | 
					    fn set_subscriptions_for_device(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        user: &User,
 | 
					        user: &User,
 | 
				
			||||||
| 
						 | 
					@ -110,7 +124,8 @@ pub trait SubscriptionRepository {
 | 
				
			||||||
        time_changed: DateTime<Utc>,
 | 
					        time_changed: DateTime<Utc>,
 | 
				
			||||||
    ) -> Result<(), AuthErr>;
 | 
					    ) -> Result<(), AuthErr>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Update the list of subscriptions for a device by adding and removing the given URLs
 | 
					    /// Update the list of subscriptions for a device and all devices in its sync group by adding
 | 
				
			||||||
 | 
					    /// and removing the given URLs
 | 
				
			||||||
    fn update_subscriptions_for_device(
 | 
					    fn update_subscriptions_for_device(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        user: &User,
 | 
					        user: &User,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -94,6 +94,22 @@ impl GpodderRepository {
 | 
				
			||||||
        self.store.update_device_info(user, device_id, patch)
 | 
					        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(
 | 
					    pub fn subscriptions_for_device(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        user: &models::User,
 | 
					        user: &models::User,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue