Compare commits

..
41 changed files with 80 additions and 213 deletions
@@ -44,7 +44,7 @@ runs:
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }}
@@ -79,7 +79,7 @@ runs:
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }}
+1 -1
View File
@@ -71,7 +71,7 @@ runs:
- name: Install timelord-cli and git-warp-time
if: steps.check-binaries.outputs.need-install == 'true'
uses: https://github.com/taiki-e/install-action@41049aa56687c35e0afa74eed4f09cec4f9afabf # v2
uses: https://github.com/taiki-e/install-action@a6b2e2dcd845ddd7f509ce4f3ed3d922b80cc5d9 # v2
with:
tool: git-warp-time,timelord-cli@3.0.1
+1 -7
View File
@@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
container: [ "ubuntu-latest", "ubuntu-previous", "debian-latest", "debian-oldstable" ]
arch: [ "amd64", "arm64" ]
arch: ["amd64", "arm64"]
container:
image: "ghcr.io/tcpipuk/act-runner:${{ matrix.container }}"
@@ -165,12 +165,6 @@ jobs:
apt-get update -y
# Build dependencies for rocksdb
apt-get install -y liburing-dev clang
# For arm64 builds, install cross-compiled dependencies.
# libstdc++6:arm64 is needed by dpkg-shlibdeps to resolve the
# dynamically-linked libstdc++ when cargo-deb computes $auto depends.
if [ "${{ matrix.arch }}" = "arm64" ]; then
apt-get install -y liburing-dev:arm64 libstdc++6:arm64
fi
- name: Run cargo-deb
id: cargo-deb
-40
View File
@@ -1,43 +1,3 @@
# Continuwuity 26.7.0 (2026-07-27)
## Features
- Build and publish arm64 .deb packages alongside amd64 for all supported Debian and Ubuntu releases. (#1235)
- Dehydrated devices are now visible in the account panel. Contributed by @ginger. (#1970)
- Introduce `accepted_ip_sources` as a multiple options variant of `request_ip_source`, allowing for more advanced deployments and making fallbacks an explicit choice. Contributed by @Omar007 (#1985)
- Added an admin command to issue an access token for a bot account, to allow legacy bots to function while legacy authentication is disabled. Contributed by @ginger (#2044)
- Added support for the OAuth2 device authorization flow. Contributed by @ginger
- Added support for the stable mutual rooms query endpoint. Contributed by @ginger
- Fetch the joined member count once per event instead of once per notified user.
## Bugfixes
- Fix joining restricted rooms over federation failing with signature verification error. (fix-federation-signature)
- Fixed the client space hierarchy endpoint returning a 500 "Space hierarchy is unreasonably large" error for cyclic space graphs (e.g. a space containing itself). Rooms are now deduplicated during traversal as required by the spec, and the traversal depth is bounded even when the client does not specify `max_depth`. (space-hierarchy-cycle)
- Fixed simplified sliding sync holding account data for up to 30 seconds, which made encryption setup and cross-signing resets appear to hang. (sss-account-data-longpoll)
- Fixed local invites and invite acceptances not being reflected in sync promptly. Contributed by @eleboucher (wake-local-member-sync)
- Fixed the deeplink redirect for deleting devices. Contributed by @koen (#1965)
- Fix status code for oauth registration. Contributed by @n00byking (#1984)
- Exempt m.room.create from auth_events check. Contributed by @eleboucher (#1987)
- Fixed `create` being returned as a supported prompt value regardless of if registration is enabled or not. Contributed by @ginger (#1994)
- Fixed high CPU usage when multiple clients from the same account were connected at once. Each sync woke the account's other sync loops, causing them to wake each other in a loop. (#2006)
- Fixed MSC4190 appservice device creation registering a random device ID instead of the requested one (and dropping the requested display name), which prevented encrypted mautrix bridges from starting on OIDC-enabled servers and leaked an orphan device on the bridge bot per startup attempt. (#2015)
- Deactivated users and appservice puppets are no longer counted by `/_continuwuity/local_user_count`. Contributed by @ginger. (#2040)
- Re-introduced admin room registration alerts that were accidentally removed in the OAuth2 update. (#2057)
- Appservices are now properly able to create devices for E2EE.
- Appservices may now specify both the unstable and stable `device_id` query parameters in a request. The stable parameter will take priority. Contributed by @ginger.
- Fixed `roomuserid_lastnotificationread` being aliased to the highlight count table, which clobbered highlight counts when setting a read marker. Contributed by @eleboucher
- Fixed freshly left room failing to sync.
- Fixed newly created rooms failing to sync properly in clients using legacy sync.
- Fixed newly joined rooms failing to sync their full state (including the room name) to clients using legacy sync.
- Fixed requests returning `500 Internal Server Error` when the header selected by `request_ip_source` is absent, duplicated, or malformed (for example Envoy omitting `X-Envoy-External-Address` on internal requests). The client IP now falls back to the connection peer address instead of failing the request. Contributed by @eleboucher
- Resolve alias service by correct name for auto-join. Contributed by @eleboucher
## Improved Documentation
- Updated an out-of-date statement about Oracle Linux release cadences. (#1999)
# Continuwuity 26.6.2 (2026-07-12)
## Bugfixes
Generated
+12 -12
View File
@@ -816,7 +816,7 @@ dependencies = [
[[package]]
name = "conduwuit"
version = "26.7.2"
version = "26.6.2"
dependencies = [
"aws-lc-rs",
"clap",
@@ -854,7 +854,7 @@ dependencies = [
[[package]]
name = "conduwuit_admin"
version = "26.7.2"
version = "26.6.2"
dependencies = [
"assign",
"clap",
@@ -880,7 +880,7 @@ dependencies = [
[[package]]
name = "conduwuit_api"
version = "26.7.2"
version = "26.6.2"
dependencies = [
"assign",
"async-trait",
@@ -918,7 +918,7 @@ dependencies = [
[[package]]
name = "conduwuit_build_metadata"
version = "26.7.2"
version = "26.6.2"
dependencies = [
"built",
"cargo_metadata",
@@ -926,7 +926,7 @@ dependencies = [
[[package]]
name = "conduwuit_core"
version = "26.7.2"
version = "26.6.2"
dependencies = [
"argon2",
"arrayvec",
@@ -994,7 +994,7 @@ dependencies = [
[[package]]
name = "conduwuit_database"
version = "26.7.2"
version = "26.6.2"
dependencies = [
"async-channel",
"conduwuit_core",
@@ -1015,7 +1015,7 @@ dependencies = [
[[package]]
name = "conduwuit_macros"
version = "26.7.2"
version = "26.6.2"
dependencies = [
"cargo_toml",
"itertools 0.15.0",
@@ -1026,7 +1026,7 @@ dependencies = [
[[package]]
name = "conduwuit_router"
version = "26.7.2"
version = "26.6.2"
dependencies = [
"assign",
"axum",
@@ -1063,7 +1063,7 @@ dependencies = [
[[package]]
name = "conduwuit_service"
version = "26.7.2"
version = "26.6.2"
dependencies = [
"askama",
"assign",
@@ -1115,7 +1115,7 @@ dependencies = [
[[package]]
name = "conduwuit_web"
version = "26.7.2"
version = "26.6.2"
dependencies = [
"askama",
"assign",
@@ -4765,7 +4765,7 @@ dependencies = [
[[package]]
name = "ruminuwuity"
version = "26.7.2"
version = "26.6.2"
dependencies = [
"assign",
"ruma",
@@ -6874,7 +6874,7 @@ dependencies = [
[[package]]
name = "xtask"
version = "26.7.2"
version = "26.6.2"
dependencies = [
"askama",
"cargo_metadata",
+1 -1
View File
@@ -12,7 +12,7 @@ license = "Apache-2.0"
# See also `rust-toolchain.toml`
readme = "README.md"
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
version = "26.7.2"
version = "26.6.2"
[workspace.metadata.crane]
name = "conduwuit"
+1
View File
@@ -0,0 +1 @@
Added support for the OAuth2 device authorization flow. Contributed by @ginger
+1
View File
@@ -0,0 +1 @@
Fixed requests returning `500 Internal Server Error` when the header selected by `request_ip_source` is absent, duplicated, or malformed (for example Envoy omitting `X-Envoy-External-Address` on internal requests). The client IP now falls back to the connection peer address instead of failing the request. Contributed by @eleboucher
+1
View File
@@ -0,0 +1 @@
Added support for the stable mutual rooms query endpoint. Contributed by @ginger
+1
View File
@@ -0,0 +1 @@
Appservices are now properly able to create devices for E2EE.
+1
View File
@@ -0,0 +1 @@
Resolve alias service by correct name for auto-join. Contributed by @eleboucher
+1
View File
@@ -0,0 +1 @@
Fixed newly created rooms failing to sync properly in clients using legacy sync.
+1
View File
@@ -0,0 +1 @@
Fixed newly joined rooms failing to sync their full state (including the room name) to clients using legacy sync.
+1
View File
@@ -0,0 +1 @@
Fixed `roomuserid_lastnotificationread` being aliased to the highlight count table, which clobbered highlight counts when setting a read marker. Contributed by @eleboucher
+1
View File
@@ -0,0 +1 @@
Fixed freshly left room failing to sync.
+1
View File
@@ -0,0 +1 @@
Appservices may now specify both the unstable and stable `device_id` query parameters in a request. The stable parameter will take priority. Contributed by @ginger.
+1
View File
@@ -0,0 +1 @@
Fetch the joined member count once per event instead of once per notified user.
+1
View File
@@ -0,0 +1 @@
Build and publish arm64 .deb packages alongside amd64 for all supported Debian and Ubuntu releases.
+1
View File
@@ -0,0 +1 @@
Fixed the deeplink redirect for deleting devices. Contributed by @koen
+1
View File
@@ -0,0 +1 @@
Dehydrated devices are now visible in the account panel. Contributed by @ginger.
+1
View File
@@ -0,0 +1 @@
Fix status code for oauth registration. Contributed by @n00byking
+1
View File
@@ -0,0 +1 @@
Introduce `accepted_ip_sources` as a multiple options variant of `request_ip_source`, allowing for more advanced deployments and making fallbacks an explicit choice. Contributed by @Omar007
+1
View File
@@ -0,0 +1 @@
Exempt m.room.create from auth_events check. Contributed by @eleboucher
+1
View File
@@ -0,0 +1 @@
Fixed `create` being returned as a supported prompt value regardless of if registration is enabled or not. Contributed by @ginger
+1
View File
@@ -0,0 +1 @@
Updated an out-of-date statement about Oracle Linux release cadences.
+1
View File
@@ -0,0 +1 @@
Fixed high CPU usage when multiple clients from the same account were connected at once. Each sync woke the account's other sync loops, causing them to wake each other in a loop.
+1
View File
@@ -0,0 +1 @@
Fixed MSC4190 appservice device creation registering a random device ID instead of the requested one (and dropping the requested display name), which prevented encrypted mautrix bridges from starting on OIDC-enabled servers and leaked an orphan device on the bridge bot per startup attempt.
+1
View File
@@ -0,0 +1 @@
Deactivated users and appservice puppets are no longer counted by `/_continuwuity/local_user_count`. Contributed by @ginger.
+1
View File
@@ -0,0 +1 @@
Added an admin command to issue an access token for a bot account, to allow legacy bots to function while legacy authentication is disabled.
+1
View File
@@ -0,0 +1 @@
Re-introduced admin room registration alerts that were accidentally removed in the OAuth2 update.
@@ -0,0 +1 @@
Fix joining restricted rooms over federation failing with signature verification error.
-1
View File
@@ -1 +0,0 @@
**TODO - embargoed until next release** (maintainers see security issue 10 when writing rls notes). Contributed by @eleboucher.
@@ -0,0 +1 @@
Fixed the client space hierarchy endpoint returning a 500 "Space hierarchy is unreasonably large" error for cyclic space graphs (e.g. a space containing itself). Rooms are now deduplicated during traversal as required by the spec, and the traversal depth is bounded even when the client does not specify `max_depth`.
@@ -0,0 +1 @@
Fixed simplified sliding sync holding account data for up to 30 seconds, which made encryption setup and cross-signing resets appear to hang.
@@ -0,0 +1 @@
Fixed local invites and invite acceptances not being reflected in sync promptly. Contributed by @eleboucher
-4
View File
@@ -8,10 +8,6 @@ ## `!admin users create-user`
Create a new user
## `!admin users issue-token`
Issue an access token for a user. This command will not work on shadow users, such as appservice puppets or accounts imported from an identity provider
## `!admin users reset-password`
Reset user password
+22 -111
View File
@@ -1,6 +1,6 @@
use std::{
cmp::{self, Ordering},
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
ops::Deref,
time::Duration,
};
@@ -138,13 +138,6 @@ pub(crate) async fn sync_events_v5_route(
let (all_joined_rooms, all_invited_rooms, all_knocked_rooms) =
join3(all_joined_rooms, all_invited_rooms, all_knocked_rooms).await;
let allowed_rooms: BTreeSet<OwnedRoomId> = all_joined_rooms
.iter()
.chain(all_invited_rooms.iter())
.chain(all_knocked_rooms.iter())
.cloned()
.collect();
let all_joined_rooms = all_joined_rooms.iter().map(AsRef::as_ref);
let all_invited_rooms = all_invited_rooms.iter().map(AsRef::as_ref);
let all_knocked_rooms = all_knocked_rooms.iter().map(AsRef::as_ref);
@@ -197,14 +190,13 @@ pub(crate) async fn sync_events_v5_route(
)
.await;
fetch_subscriptions(services, sync_info, &known_rooms, &allowed_rooms, &mut todo_rooms).await;
fetch_subscriptions(services, sync_info, &known_rooms, &mut todo_rooms).await;
response.rooms = process_rooms(
services,
sender_user,
next_batch,
all_invited_rooms.clone(),
all_knocked_rooms.clone(),
&todo_rooms,
&mut response,
&body,
@@ -222,7 +214,6 @@ pub(crate) async fn sync_events_v5_route(
let no_room_data = response.rooms.iter().all(|(id, r)| {
r.timeline.is_empty()
&& r.required_state.is_empty()
&& r.invite_state.is_none()
&& !response.extensions.receipts.rooms.contains_key(id)
});
@@ -256,17 +247,10 @@ async fn fetch_subscriptions(
services: &Services,
(sender_user, sender_device, globalsince, body): SyncInfo<'_>,
known_rooms: &KnownRooms,
allowed_rooms: &BTreeSet<OwnedRoomId>,
todo_rooms: &mut TodoRooms,
) {
let mut known_subscription_rooms = BTreeSet::new();
for (room_id, room) in &body.room_subscriptions {
// Silently ignore subscriptions to rooms the user is not a member of
// (joined or invited).
if !allowed_rooms.contains(room_id) {
continue;
}
let not_exists = services.rooms.metadata.exists(room_id).eq(&false);
let is_disabled = services.rooms.metadata.is_disabled(room_id);
@@ -425,13 +409,11 @@ async fn handle_lists<'a, Rooms, AllRooms>(
BTreeMap::default()
}
#[allow(clippy::too_many_arguments)]
async fn process_rooms<'a, Rooms>(
services: &Services,
sender_user: &UserId,
next_batch: u64,
all_invited_rooms: Rooms,
all_knocked_rooms: Rooms,
todo_rooms: &TodoRooms,
response: &mut sync_events::v5::Response,
body: &sync_events::v5::Request,
@@ -444,101 +426,38 @@ async fn process_rooms<'a, Rooms>(
let roomsincecount = PduCount::Normal(*roomsince);
let mut timestamp: Option<_> = None;
let mut invite_state = None;
let (timeline_pdus, limited);
let new_room_id: &RoomId = (*room_id).as_ref();
if all_invited_rooms.clone().any(is_equal_to!(new_room_id)) {
let Ok(invite_count) = services
.rooms
.state_cache
.get_invite_count(room_id, sender_user)
.await
else {
continue;
};
if *roomsince >= invite_count {
continue;
}
// TODO: figure out a timestamp we can use for remote invites
let invite_state = services
invite_state = services
.rooms
.state_cache
.invite_state(sender_user, room_id)
.await
.ok();
rooms.insert(
room_id.clone(),
assign!(sync_events::v5::response::Room::new(), {
initial: Some(roomsince == &0),
invite_state,
limited: true,
}),
);
continue;
}
if all_knocked_rooms.clone().any(is_equal_to!(new_room_id)) {
let Ok(knock_count) = services
.rooms
.state_cache
.get_knock_count(room_id, sender_user)
.await
else {
continue;
};
if *roomsince >= knock_count {
continue;
}
let Ok(knock_state) = services
.rooms
.state_cache
.knock_state(sender_user, room_id)
.await
else {
continue;
};
rooms.insert(
room_id.clone(),
assign!(sync_events::v5::response::Room::new(), {
initial: Some(roomsince == &0),
invite_state: Some(knock_state),
limited: true,
}),
);
continue;
}
if !services
.rooms
.state_cache
.is_joined(sender_user, room_id)
(timeline_pdus, limited) = (VecDeque::new(), true);
} else {
TimelinePdus { pdus: timeline_pdus, limited } = match load_timeline(
services,
sender_user,
room_id,
Some(roomsincecount),
Some(PduCount::from(next_batch)),
*timeline_limit,
)
.await
{
continue;
{
| Ok(value) => value,
| Err(err) => {
warn!("Encountered missing timeline in {}, error {}", room_id, err);
continue;
},
};
}
TimelinePdus { pdus: timeline_pdus, limited } = match load_timeline(
services,
sender_user,
room_id,
Some(roomsincecount),
Some(PduCount::from(next_batch)),
*timeline_limit,
)
.await
{
| Ok(value) => value,
| Err(err) => {
warn!("Encountered missing timeline in {}, error {}", room_id, err);
continue;
},
};
if body.extensions.account_data.enabled == Some(true) {
response.extensions.account_data.rooms.insert(
room_id.to_owned(),
@@ -722,6 +641,7 @@ async fn process_rooms<'a, Rooms>(
},
initial: Some(roomsince == &0),
is_dm: None,
invite_state,
unread_notifications: assign!(UnreadNotificationsCount::new(), {
highlight_count: Some(
services
@@ -849,15 +769,6 @@ async fn collect_typing_events(
let mut typing_response = sync_events::v5::response::Typing::default();
for (room_id, (_, _, roomsince)) in todo_rooms {
if !services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
continue;
}
if services.rooms.typing.last_typing_update(room_id).await? <= *roomsince {
continue;
}
@@ -7,7 +7,7 @@
Err, Event, Result, debug, debug_error, debug_warn, defer, matrix::PartialPdu, trace,
utils::time::jitter,
};
use futures::{FutureExt, StreamExt, future::try_join4};
use futures::{FutureExt, StreamExt, future::try_join3};
use ruma::{CanonicalJsonValue, EventId, RoomId, ServerName, UserId};
use tokio::sync::mpsc;
@@ -69,13 +69,9 @@ pub async fn handle_incoming_pdu<'a>(
.and_then(|s| UserId::parse(s).ok())
.is_some_and(|u| self.services.globals.user_is_local(&u));
let (room_exists, is_disabled, is_resident, ()) = try_join4(
let (room_exists, is_disabled, ()) = try_join3(
self.services.metadata.exists(room_id).map(Ok),
self.services.metadata.is_disabled(room_id).map(Ok),
self.services
.state_cache
.server_in_room(self.services.globals.server_name(), room_id)
.map(Ok),
self.acl_check(origin, room_id),
)
.await
@@ -89,20 +85,12 @@ pub async fn handle_incoming_pdu<'a>(
)));
}
// If the room doesn't exist (we don't have the create event), there's nothing
// we can do.
if !room_exists {
return Err!(Request(NotFound("Room is unknown to this server")));
}
// If the room does exist, but we aren't a resident of it, we might be
// interested in an out-of-band membership (for example, an inviter rescinding
// their invite).
if !is_resident {
if is_interesting_member_event {
// TODO: handle interesting membership events where we aren't in
// the room
}
return Err!(Request(NotFound("This server does not have any members this room")));
return Err!(Request(NotFound("Room is unknown to this server")));
}
// Fetch create event
@@ -104,24 +104,24 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu(
// Determine whether this PDU should be soft-failed.
// If the auth check failed, invariably yes. Otherwise, only if the user isn't
// allowed to redact the target event (if any).
let redaction_permitted =
if let Some(redacted_id) = incoming_pdu.redacts_id(&room_version_rules) {
self.services
let mut should_soft_fail =
match (passes_current_state, incoming_pdu.redacts_id(&room_version_rules)) {
| (false, _) => true,
| (true, None) => false,
| (true, Some(redact_id)) => self
.services
.state_accessor
.user_can_redact(&redacted_id, incoming_pdu.sender(), room_id, true)
.user_can_redact(&redact_id, incoming_pdu.sender(), room_id, true)
.await
.is_ok_and(is_true!())
} else {
true
.is_ok_and(is_true!()),
};
let mut should_soft_fail = !redaction_permitted || !passes_current_state;
if !should_soft_fail {
// Now we can perform check 7, which is ensuring the event passes policy server
// checks.
// We explicitly only do this if we aren't already going to soft-fail the event,
// since the policy server refusing this event also soft-fails it.
debug!("Checking policy server for event");
debug!(event_id = %incoming_pdu.event_id, "Checking policy server for event");
should_soft_fail = !self
.policy_server_check_7(&incoming_pdu, &mut val, &room_version_rules)
.await
@@ -153,12 +153,6 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu(
should_soft_fail = true;
}
}
} else {
debug!(
%redaction_permitted,
%passes_current_state,
"Intending to soft-fail event (skipping further PDU checks)"
);
}
// The PDU has now passed all checks! We can now promote it (or soft-fail it if
+1 -4
View File
@@ -1,4 +1,4 @@
use conduwuit::{Err, Result, matrix::Event, pdu::PartialPdu, trace};
use conduwuit::{Err, Result, matrix::Event, pdu::PartialPdu};
use ruma::{
EventId, RoomId, UserId,
events::{
@@ -46,7 +46,6 @@ pub async fn user_can_redact(
let power_levels = self.get_room_power_levels(room_id).await;
if power_levels.user_can_redact_event_of_other(sender) {
trace!(%sender, "Sender is allowed to redact other users' events");
return Ok(true);
}
@@ -60,12 +59,10 @@ pub async fn user_can_redact(
},
| _ => false,
};
trace!(%is_own_event, "User can redact own event");
return Ok(is_own_event);
}
trace!("User is not permitted to redact their own event nor others' events");
Ok(false)
}