Compare commits

...

3 Commits

7 changed files with 86 additions and 135 deletions

View File

@ -12,6 +12,8 @@ create table sessions (
references users (id)
on delete cascade,
last_seen bigint not null,
unique (id, user_id)
);

View File

@ -1,3 +0,0 @@
-- This file should undo anything in `up.sql`
alter table sessions
drop column last_seen;

View File

@ -1,3 +0,0 @@
-- Your SQL goes here
alter table sessions
add column last_seen bigint not null default 0;

View File

@ -1,6 +1,5 @@
use chrono::DateTime;
use diesel::prelude::*;
use rand::Rng;
use super::SqliteRepository;
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 {
fn get_user(&self, username: &str) -> Result<Option<gpodder::models::User>, AuthErr> {
Ok(users::table

View File

@ -1,3 +1,4 @@
use chrono::{DateTime, Utc};
use diesel::prelude::*;
use super::SqliteRepository;
@ -95,4 +96,35 @@ impl gpodder::DeviceRepository for SqliteRepository {
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!()
}
}

View File

@ -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 {
/// Retrieve the session with the given session ID
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>;
/// 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(
&self,
user: &User,
device_id: &str,
patch: DevicePatch,
) -> 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 {
@ -101,7 +114,8 @@ pub trait SubscriptionRepository {
/// Return all subscriptions for a given user
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(
&self,
user: &User,
@ -110,7 +124,8 @@ pub trait SubscriptionRepository {
time_changed: DateTime<Utc>,
) -> 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(
&self,
user: &User,

View File

@ -94,6 +94,22 @@ impl GpodderRepository {
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,