Compare commits

...

7 Commits

Author SHA1 Message Date
stoat-release[bot]
6c920de03a chore(main): release 0.13.5 (#759)
* chore(main): release 0.13.5

* chore: update Cargo.lock

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

---------

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-17 11:02:21 -07:00
Angelo Kontaxis
c902077cf5 fix: dont panic on hash missing when deleting files (#755)
Signed-off-by: Zomatree <me@zomatree.live>
2026-05-17 10:59:56 -07:00
Tom
19ee535f45 Merge commit from fork
* fix: cache dns & block more ranges

* fix: idle time instead of ttl

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

---------

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>
2026-05-17 10:55:38 -07:00
stoat-release[bot]
ee4575470b chore(main): release 0.13.4 (#754)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-16 18:27:44 +01:00
Paul Makles
6cfee1f601 fix: add TLS feature to livekit-api crate (#753) 2026-05-16 18:23:45 +01:00
stoat-release[bot]
ab9b8ccfca chore(main): release 0.13.3 (#750)
* chore(main): release 0.13.3

* chore: update Cargo.lock

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

---------

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-15 12:22:27 -07:00
Tom
7647cfc8d9 fix: don't automatically set up rabbitmq in delta (#749)
fix: don't declare queues which seem to cause the backend to crash in prod
for now, these exchanges/queues/bindings will need to be declared manually. Hopefully the lapin rewrite will fix this.

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>
2026-05-15 12:17:36 -07:00
27 changed files with 239 additions and 83 deletions

View File

@@ -1,3 +1,3 @@
{
".": "0.13.2"
".": "0.13.5"
}

View File

@@ -1,5 +1,27 @@
# Changelog
## [0.13.5](https://github.com/stoatchat/stoatchat/compare/v0.13.4...v0.13.5) (2026-05-17)
### Bug Fixes
* dont panic on hash missing when deleting files ([#755](https://github.com/stoatchat/stoatchat/issues/755)) ([c902077](https://github.com/stoatchat/stoatchat/commit/c902077cf51076fee11712eb732dc8a8f786fc4b))
## [0.13.4](https://github.com/stoatchat/stoatchat/compare/v0.13.3...v0.13.4) (2026-05-16)
### Bug Fixes
* add TLS feature to livekit-api crate ([#753](https://github.com/stoatchat/stoatchat/issues/753)) ([6cfee1f](https://github.com/stoatchat/stoatchat/commit/6cfee1f601c1e084df7c8f1e7a5e8a560d1dd514))
## [0.13.3](https://github.com/stoatchat/stoatchat/compare/v0.13.2...v0.13.3) (2026-05-15)
### Bug Fixes
* don't automatically set up rabbitmq in delta ([#749](https://github.com/stoatchat/stoatchat/issues/749)) ([7647cfc](https://github.com/stoatchat/stoatchat/commit/7647cfc8d93aba99f5faef13eb3d970097540d76))
* don't declare queues which seem to cause the backend to crash in prod ([7647cfc](https://github.com/stoatchat/stoatchat/commit/7647cfc8d93aba99f5faef13eb3d970097540d76))
## [0.13.2](https://github.com/stoatchat/stoatchat/compare/v0.13.1...v0.13.2) (2026-05-11)

82
Cargo.lock generated
View File

@@ -570,7 +570,7 @@ dependencies = [
"futures-util",
"log",
"pin-project-lite 0.2.17",
"tungstenite",
"tungstenite 0.17.3",
]
[[package]]
@@ -5048,11 +5048,14 @@ dependencies = [
"prost",
"rand 0.9.2",
"reqwest 0.12.28",
"rustls-native-certs 0.6.3",
"scopeguard",
"serde",
"serde_json",
"sha2",
"thiserror 2.0.18",
"tokio-rustls 0.24.1",
"tokio-tungstenite",
"url",
]
@@ -7419,16 +7422,22 @@ dependencies = [
"http-body 1.0.1",
"http-body-util",
"hyper 1.9.0",
"hyper-rustls 0.27.7",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite 0.2.17",
"quinn",
"rustls 0.23.37",
"rustls-native-certs 0.8.3",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 1.0.2",
"tokio 1.51.0",
"tokio-rustls 0.26.4",
"tower",
"tower-http 0.6.8",
"tower-service",
@@ -7504,7 +7513,7 @@ dependencies = [
[[package]]
name = "revolt-autumn"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"axum",
"axum-macros",
@@ -7545,7 +7554,7 @@ dependencies = [
[[package]]
name = "revolt-bonfire"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"async-channel 2.5.0",
"async-std",
@@ -7576,7 +7585,7 @@ dependencies = [
[[package]]
name = "revolt-coalesced"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"indexmap 2.13.1",
"lru",
@@ -7585,7 +7594,7 @@ dependencies = [
[[package]]
name = "revolt-config"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"async-std",
"cached",
@@ -7602,7 +7611,7 @@ dependencies = [
[[package]]
name = "revolt-crond"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"futures-lite",
"iso8601-timestamp",
@@ -7622,7 +7631,7 @@ dependencies = [
[[package]]
name = "revolt-database"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"amqprs",
"async-lock 2.8.0",
@@ -7673,7 +7682,7 @@ dependencies = [
[[package]]
name = "revolt-delta"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"amqprs",
"async-channel 2.5.0",
@@ -7722,7 +7731,7 @@ dependencies = [
[[package]]
name = "revolt-files"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"aes-gcm",
"anyhow",
@@ -7750,7 +7759,7 @@ dependencies = [
[[package]]
name = "revolt-gifbox"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"axum",
"axum-extra",
@@ -7773,7 +7782,7 @@ dependencies = [
[[package]]
name = "revolt-january"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"async-recursion",
"axum",
@@ -7803,7 +7812,7 @@ dependencies = [
[[package]]
name = "revolt-models"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"indexmap 2.13.1",
"iso8601-timestamp",
@@ -7822,14 +7831,14 @@ dependencies = [
[[package]]
name = "revolt-parser"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"logos",
]
[[package]]
name = "revolt-permissions"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"async-std",
"async-trait",
@@ -7844,7 +7853,7 @@ dependencies = [
[[package]]
name = "revolt-presence"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"async-std",
"log",
@@ -7856,7 +7865,7 @@ dependencies = [
[[package]]
name = "revolt-pushd"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"amqprs",
"anyhow",
@@ -7887,7 +7896,7 @@ dependencies = [
[[package]]
name = "revolt-ratelimits"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"async-trait",
"authifier",
@@ -7904,7 +7913,7 @@ dependencies = [
[[package]]
name = "revolt-result"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"axum",
"log",
@@ -7920,7 +7929,7 @@ dependencies = [
[[package]]
name = "revolt-voice-ingress"
version = "0.13.2"
version = "0.13.5"
dependencies = [
"amqprs",
"async-std",
@@ -9891,6 +9900,21 @@ dependencies = [
"tokio 1.51.0",
]
[[package]]
name = "tokio-tungstenite"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
dependencies = [
"futures-util",
"log",
"rustls 0.21.12",
"rustls-native-certs 0.6.3",
"tokio 1.51.0",
"tokio-rustls 0.24.1",
"tungstenite 0.20.1",
]
[[package]]
name = "tokio-util"
version = "0.7.18"
@@ -10146,6 +10170,26 @@ dependencies = [
"utf-8",
]
[[package]]
name = "tungstenite"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
dependencies = [
"byteorder",
"bytes 1.11.1",
"data-encoding",
"http 0.2.12",
"httparse",
"log",
"rand 0.8.5",
"rustls 0.21.12",
"sha1",
"thiserror 1.0.69",
"url",
"utf-8",
]
[[package]]
name = "typed-builder"
version = "0.22.0"

View File

@@ -192,13 +192,13 @@ futures-lite = "2.6.1"
vergen = "7.5.0"
# Local packages
revolt-coalesced = { version = "0.13.2", path = "crates/core/coalesced" }
revolt-config = { version = "0.13.2", path = "crates/core/config" }
revolt-database = { version = "0.13.2", path = "crates/core/database" }
revolt-files = { version = "0.13.2", path = "crates/core/files" }
revolt-models = { version = "0.13.2", path = "crates/core/models" }
revolt-parser = { version = "0.13.2", path = "crates/core/parser" }
revolt-permissions = { version = "0.13.2", path = "crates/core/permissions" }
revolt-presence = { version = "0.13.2", path = "crates/core/presence" }
revolt-ratelimits = { version = "0.13.2", path = "crates/core/ratelimits" }
revolt-result = { version = "0.13.2", path = "crates/core/result" }
revolt-coalesced = { version = "0.13.5", path = "crates/core/coalesced" }
revolt-config = { version = "0.13.5", path = "crates/core/config" }
revolt-database = { version = "0.13.5", path = "crates/core/database" }
revolt-files = { version = "0.13.5", path = "crates/core/files" }
revolt-models = { version = "0.13.5", path = "crates/core/models" }
revolt-parser = { version = "0.13.5", path = "crates/core/parser" }
revolt-permissions = { version = "0.13.5", path = "crates/core/permissions" }
revolt-presence = { version = "0.13.5", path = "crates/core/presence" }
revolt-ratelimits = { version = "0.13.5", path = "crates/core/ratelimits" }
revolt-result = { version = "0.13.5", path = "crates/core/result" }

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-bonfire"
version = "0.13.2"
version = "0.13.5"
license = "AGPL-3.0-or-later"
edition = "2021"
publish = false

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-coalesced"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>", "Zomatree <me@zomatree.live>"]

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-config"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-database"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -98,6 +98,6 @@ authifier = { workspace = true }
amqprs = { workspace = true }
# Voice
livekit-api = { workspace = true, optional = true }
livekit-api = { workspace = true, features = ["rustls-tls-native-roots"], optional = true }
livekit-protocol = { workspace = true, optional = true }
livekit-runtime = { workspace = true, features = ["tokio"], optional = true }

View File

@@ -47,7 +47,7 @@ impl AMQP {
.expect("Failed to open RabbitMQ channel");
let mut resp = AMQP::new(connection, channel);
resp.configure_channels().await?;
//resp.configure_channels().await?;
Ok(resp)
}

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-files"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-models"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-parser"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "MIT"
authors = ["Zomatree <me@zomatree.live>", "Paul Makles <me@insrt.uk>"]

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-permissions"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-presence"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-ratelimits"
version = "0.13.2"
version = "0.13.5"
edition = "2024"
license = "MIT"
authors = ["Zomatree <me@zomatree.live>", "Paul Makles <me@insrt.uk>"]

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-result"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-crond"
version = "0.13.2"
version = "0.13.5"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
edition = "2021"

View File

@@ -11,22 +11,24 @@ pub async fn task(db: Database) -> Result<()> {
let files = db.fetch_deleted_attachments().await?;
for file in files {
let count = db
.count_file_hash_references(file.hash.as_ref().expect("no `hash` present"))
.await?;
// No other files reference this file on disk anymore
if count <= 1 {
let file_hash = db
.fetch_attachment_hash(file.hash.as_ref().expect("no `hash` present"))
if let Some(hash) = &file.hash {
let count = db
.count_file_hash_references(hash)
.await?;
// Delete from S3
delete_from_s3(&file_hash.bucket_id, &file_hash.path).await?;
// No other files reference this file on disk anymore
if count <= 1 {
let file_hash = db
.fetch_attachment_hash(hash)
.await?;
// Delete the hash
db.delete_attachment_hash(&file_hash.id).await?;
info!("Deleted file hash {}", file_hash.id);
// Delete from S3
delete_from_s3(&file_hash.bucket_id, &file_hash.path).await?;
// Delete the hash
db.delete_attachment_hash(&file_hash.id).await?;
info!("Deleted file hash {}", file_hash.id);
}
}
// Delete the file

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-pushd"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "AGPL-3.0-or-later"
publish = false

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-voice-ingress"
version = "0.13.2"
version = "0.13.5"
license = "AGPL-3.0-or-later"
edition = "2021"
publish = false

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-delta"
version = "0.13.2"
version = "0.13.5"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018"

View File

@@ -120,9 +120,9 @@ pub async fn web() -> Rocket<Build> {
.expect("Failed to declare exchange");
let mut amqp = AMQP::new(connection, channel);
amqp.configure_channels()
.await
.expect("Failed to configure channels");
// amqp.configure_channels()
// .await
// .expect("Failed to configure channels");
// Launch background task workers
revolt_database::tasks::start_workers(db.clone(), amqp.clone());

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-autumn"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "AGPL-3.0-or-later"
publish = false

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-gifbox"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "AGPL-3.0-or-later"
publish = false

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-january"
version = "0.13.2"
version = "0.13.5"
edition = "2021"
license = "AGPL-3.0-or-later"
publish = false

View File

@@ -4,6 +4,7 @@ use mime::Mime;
use pdk_ip_filter_lib::IpFilter;
use regex::Regex;
use reqwest::{
dns::{Addrs, Name, Resolve},
header::{self, CONTENT_TYPE},
redirect, Client, Response,
};
@@ -11,6 +12,7 @@ use revolt_config::{config, report_internal_error};
use revolt_files::{create_thumbnail, decode_image, image_size_vec, is_valid_image, video_size};
use revolt_models::v0::{Embed, Image, ImageSize, Video};
use revolt_result::{create_error, Error, Result, ToRevoltError};
use std::net::{IpAddr, SocketAddr};
use std::{
io::{Cursor, Write},
str::FromStr,
@@ -21,6 +23,7 @@ use url::{Host, Url};
lazy_static! {
/// Request client
static ref CLIENT: Client = reqwest::Client::builder()
.dns_resolver(CachedDnsResolver {})
.timeout(Duration::from_secs(10)) // TODO config
.connect_timeout(Duration::from_secs(5)) // TODO config
.redirect(redirect::Policy::none())
@@ -58,18 +61,73 @@ lazy_static! {
.time_to_live(Duration::from_secs(60)) // For up to 1 minute
.build();
static ref DNS_CACHE: moka::future::Cache<String, Vec<SocketAddr>> = moka::future::Cache::builder()
.max_capacity(10_000)
.time_to_idle(Duration::from_secs(30))
.build();
static ref IP_BLOCKLIST: IpFilter = IpFilter::block(&[
"0.0.0.0/8",
"10.0.0.0/8",
"192.168.0.0/16",
"127.0.0.0/8",
"172.16.0.0/12",
"169.254.0.0/16",
"::1",
"fc00::/7"
"fc00::/7",
]
).unwrap();
}
#[derive(Clone)]
pub struct IPRequest {
url: Url,
ip: IpAddr,
pub blocked: bool,
}
impl From<IPRequest> for Url {
fn from(value: IPRequest) -> Self {
let mut url = value.url.clone();
url.set_host(Some(&value.ip.to_string()))
.map(|_| url)
.unwrap_or(value.url)
}
}
struct CachedDnsResolver {}
impl reqwest::dns::Resolve for CachedDnsResolver {
fn resolve(&self, name: Name) -> reqwest::dns::Resolving {
Box::pin(async move {
{
if let Some(addrs) = DNS_CACHE.get(&name.as_str().to_string()).await {
let resp: Addrs = Box::new(addrs.clone().into_iter());
return Ok(resp);
}
}
let mut lookup = name.as_str().to_string();
if !lookup.contains(":") {
lookup += ":0";
}
let fallback: Vec<SocketAddr> = tokio::net::lookup_host(&lookup)
.await
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { Box::new(e) })?
.collect();
{
DNS_CACHE
.insert(name.as_str().to_string().clone(), fallback.clone())
.await;
let addrs: Addrs = Box::new(fallback.clone().into_iter());
Ok(addrs)
}
})
}
}
/// Information about a successful request
pub struct Request {
response: Response,
@@ -275,7 +333,12 @@ impl Request {
let mut url = url;
let url_host_str = url.host_str().ok_or(create_error!(ProxyError))?.to_string();
Request::url_is_blacklisted(&url).await?;
let mut blocker = Request::url_is_blacklisted(&url).await?;
if blocker.blocked {
return Err(create_error!(InvalidOperation));
}
let mut redirect_count = 0;
loop {
@@ -304,9 +367,13 @@ impl Request {
let location = location.to_str().map_err(|_| create_error!(ProxyError))?;
url = Url::from_str(location).to_internal_error()?;
if !Request::url_is_blacklisted(&url).await? {
continue;
blocker = Request::url_is_blacklisted(&url).await?;
if blocker.blocked {
return Err(create_error!(InvalidOperation));
}
continue;
} else {
return Err(create_error!(ProxyError));
}
@@ -351,17 +418,25 @@ impl Request {
Ok(Request::exists(proper_url).await)
}
pub async fn url_is_blacklisted(url: &Url) -> Result<bool> {
pub async fn url_is_blacklisted(url: &Url) -> Result<IPRequest> {
let resolved_address: IpAddr;
if let Some(host) = url.host() {
match host {
Host::Ipv4(ipv4) => {
let url_str = ipv4.to_string();
if !IP_BLOCKLIST.is_allowed(&url_str) {
resolved_address = ipv4.into();
if !IP_BLOCKLIST.is_allowed(&ipv4.to_string()) {
return Err(create_error!(InvalidOperation));
}
}
Host::Ipv6(ipv6) => {
resolved_address = ipv6.into();
if !IP_BLOCKLIST.is_allowed(&ipv6.to_string()) {
return Err(create_error!(InvalidOperation));
}
}
Host::Domain(domain) => {
let mut domain = domain.to_string();
let domain = domain.to_string();
let config = config().await;
@@ -372,14 +447,22 @@ impl Request {
return Err(create_error!(InvalidOperation));
}
if !domain.contains(":") {
domain += ":80";
}
// Second step: resolve the IP and check the blocklist
if let Ok(mut resolved_ip) = tokio::net::lookup_host(domain.clone()).await {
let resolver = CachedDnsResolver {};
if let Ok(mut resolved_ip) = resolver
.resolve(
Name::from_str(&domain)
.map_err(|_| create_error!(ProxyError))
.unwrap(),
)
.await
{
if let Some(resolved_ip) = resolved_ip.next() {
if !IP_BLOCKLIST.is_allowed(&resolved_ip.ip().to_string()) {
resolved_address = resolved_ip.ip();
let resolved_string = resolved_address.to_string();
if !IP_BLOCKLIST.is_allowed(&resolved_string)
|| resolved_string.contains("::ffff:")
{
return Err(create_error!(InvalidOperation));
}
} else {
@@ -389,10 +472,15 @@ impl Request {
return Err(create_error!(ProxyError));
}
}
_ => (),
}
} else {
return Err(create_error!(ProxyError));
};
Ok(false)
Ok(IPRequest {
url: url.clone(),
ip: resolved_address,
blocked: false,
})
}
}

View File

@@ -1 +1 @@
0.13.2
0.13.5