M Cargo.lock => Cargo.lock +1 -1
@@ 1879,7 1879,7 @@ dependencies = [
[[package]]
name = "russet"
-version = "0.10.0"
+version = "0.10.1"
dependencies = [
"argon2",
"atom_syndication",
M Cargo.toml => Cargo.toml +1 -1
@@ 1,6 1,6 @@
[package]
name = "russet"
-version = "0.10.0"
+version = "0.10.1"
edition = "2021"
license = "AGPL-3.0"
M src/conf.rs => src/conf.rs +10 -0
@@ 44,8 44,17 @@ pub struct Config {
)]
pub feed_check_interval: Option<Duration>,
+ /// Disable logins.
+ ///
+ /// This option will go away once more robust rate-limiting of the login
+ /// endpoint is available. For now, it's a quick toggle to prevent
+ /// credential stuffing or resource exhaustion attacks.
+ #[arg(long)]
+ pub disable_logins: Option<bool>,
+
#[command(flatten)]
pub rate_limiting: RateLimitingConfig,
+
}
impl Default for Config {
fn default() -> Self {
@@ 56,6 65,7 @@ impl Default for Config {
listen_address: Some("127.0.0.1:9892".to_string()),
pepper: Some("IzvoEPMQIi82NSXTz7cZ".to_string()),
feed_check_interval: Some(Duration::from_secs(3_600)),
+ disable_logins: Some(false),
rate_limiting: RateLimitingConfig::default(),
}
}
M src/domain/mod.rs => src/domain/mod.rs +5 -0
@@ 15,6 15,7 @@ where Persistence: std::fmt::Debug {
readers: Vec<Box<dyn RussetFeedReader>>,
pepper: Vec<u8>,
pub feed_check_interval: Duration,
+ disable_logins: bool,
}
impl <Persistence> RussetDomainService<Persistence>
where Persistence: std::fmt::Debug {
@@ 23,6 24,7 @@ where Persistence: std::fmt::Debug {
readers: Vec<Box<dyn RussetFeedReader>>,
pepper: Vec<u8>,
feed_check_interval: Duration,
+ disable_logins: bool,
) -> Result<RussetDomainService<Persistence>> {
if feed_check_interval < MIN_CHECK_INTERVAL {
let interval = feed_check_interval.as_secs_f64();
@@ 35,6 37,7 @@ where Persistence: std::fmt::Debug {
readers,
pepper,
feed_check_interval,
+ disable_logins,
} )
}
}
@@ 45,6 48,8 @@ where Persistence: std::fmt::Debug {
.field("persistence", &self.persistence)
.field("readers", &self.readers)
.field("pepper", &"<redacted>")
+ .field("feed_check_interva", &self.feed_check_interval)
+ .field("disable_logins", &self.disable_logins)
.finish()
}
}
M src/domain/user.rs => src/domain/user.rs +1 -0
@@ 23,6 23,7 @@ where Persistence: RussetUserPersistenceLayer {
plaintext_password: String,
permanent_session: bool,
) -> Result<Option<Session>> {
+ if self.disable_logins { return Ok(None) }
let password_hash = Argon2::new_with_secret(
self.pepper.as_slice(),
argon2::Algorithm::Argon2id,
M src/main.rs => src/main.rs +3 -1
@@ 50,7 50,7 @@ async fn main() -> Result<()> {
// Commandline flags override all
let mut config = Config::parse();
- // If present, load config
+ // If present, load config file
if let Some(&ref config_file) = config.config_file.as_ref() {
let s = read_to_string(config_file)?;
let file_config = toml::from_str(&s)?;
@@ 69,6 69,7 @@ async fn main() -> Result<()> {
let pepper = config.pepper.expect("No pepper");
let feed_check_interval =
config.feed_check_interval.expect("No feed_check_interval");
+ let disable_logins = config.disable_logins.expect("No disable_logins");
let global_concurrent_limit = config
.rate_limiting
.global_concurrent_limit
@@ 88,6 89,7 @@ async fn main() -> Result<()> {
readers,
pepper.as_bytes().to_vec(),
feed_check_interval,
+ disable_logins,
)?);
match command {
M src/persistence/mod.rs => src/persistence/mod.rs +6 -1
@@ 57,10 57,15 @@ pub trait RussetEntryPersistenceLayer: Send + Sync + std::fmt::Debug + 'static {
fn get_and_increment_fetch_index(&self)
-> impl Future<Output = Result<u32>> + Send;
- /// get all the entries for all the feeds to which the given user is subscribed.
+ /// Get entries for all the feeds to which the given user is subscribed.
fn get_entries_for_user(&self, user_id: &UserId, pagination: &Pagination)
-> impl Future<Output = impl IntoIterator<Item = Result<(Entry, Option<UserEntry>)>>> + Send;
+ /// Get entries for the given feed to which the given user is subscribed
+ fn get_entries_for_user_feed(&self, user_id: &UserId, feed_id: &FeedId, pagination: &Pagination)
+ -> impl Future<Output = impl IntoIterator<Item = Result<(Entry, Option<UserEntry>)>>> + Send;
+
+ /// Atomically get an entry and set the userentry for the given entry and user.
fn get_entry_and_set_userentry(
&self,
entry_id: &EntryId,
M src/persistence/sql/entry.rs => src/persistence/sql/entry.rs +11 -0
@@ 186,6 186,17 @@ impl RussetEntryPersistenceLayer for SqlDatabase {
}
#[tracing::instrument]
+ async fn get_entries_for_user_feed(
+ &self,
+ user_id: &UserId,
+ feed_id: &FeedId,
+ pagination: &Pagination,
+ ) -> impl IntoIterator<Item = Result<(Entry, Option<UserEntry>)>> {
+ todo!();
+ vec![]
+ }
+
+ #[tracing::instrument]
async fn get_entry_and_set_userentry(
&self,
entry_id: &EntryId,