Compare commits

...
69 Commits
Author SHA1 Message Date
Jason Volk a1677f05d3 Bump 1.4.1
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-02 09:43:50 +00:00
Jason Volk 1f77200299 Mark MSC4297 compliance as flake.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-02 06:29:40 +00:00
Jason Volk 888b93df91 Fix /joined_members results. (regression a3666fe672) (fixes #147)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-02 05:34:37 +00:00
Jason Volk a127724eaa Bump 1.4.0
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-01 11:34:08 +00:00
Jason Volk 00102c4276 ci: Skip unnecessary amd64 -v2 and -v3 builds in test-profile mode.
docker: Use env override for git_checkout.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-01 02:33:00 +00:00
dasha_uwu b59af0530c Deprioritize joins through configured servers 2025-08-31 12:30:27 +05:00
dasha_uwu 44af849ed0 Dedup base join servers 2025-08-31 11:30:10 +05:00
Jason Volk 26e7a8e962 Bump 1.4.0 Preview
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 20:27:39 +00:00
Jason Volk aa413ae601 Implement room purge after last local user leaves. (resolves #83)
Consume a state_lock for room delete call.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 11:45:32 +00:00
Jason Volk 7c19d1e1ff Fix unnecessary backfill requests after reaching m.room.create.
Cleanup condition.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 11:45:32 +00:00
Jason Volk 616f20f273 Fix unused lifetims.
Fix unused pub(crate).

Allow implicit hasher.

Fix unused self.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 11:13:14 +00:00
Jason Volk 91efcf8a8e Bump and unpatch tracing-subscriber.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 10:51:55 +00:00
Jason Volk 8708e98478 ci: Tweak docker cache sizes.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 10:16:43 +00:00
Jason Volk 4bdc260015 Update the troubleshooting for easier rocksdb recovery/repair.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 10:16:43 +00:00
Jason Volk 7bc47e58d0 Add state_cache iteration for local pending invitees to room.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 10:04:24 +00:00
Jason Volk 6172b9c8ec Replace legacy federation proto-event formatter.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 10:04:24 +00:00
k0tlin 9dc16eeab7 Adjust generic docs
Match readme instruction with doc
2025-08-30 00:45:41 +03:00
k0tlin 402f7a2157 Update example caddy config 2025-08-30 00:35:38 +03:00
Jason Volk 9c6e0e3350 Stabilize room version 12. (#126)
Bless complement.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 07:49:36 +00:00
Jason Volk 12bdfe3b66 Optimize backfill_if_required conditions.
Optimize backfill_pdu.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
dasha_uwuandJason Volk 54ce221e88 Add active request assert on tls listener shutdown 2025-08-29 06:49:04 +00:00
dasha_uwuandJason Volk 33a16f4831 Make /sync respond early on server shutdown 2025-08-29 06:49:04 +00:00
Jason Volk d8e6d95f4b Fix device verification crutch (regression 628597c318); cleanup. (fixes #134)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jason Volk 9c290bc513 Fix latest key backup determination.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jason Volk a4520424bd Adjust several log levels; cleanup unformatted log call.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jason Volk 280d7733fc Update capability for MSC4133 profile fields.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jason Volk 79e3c898ac Fix /send_join response event format. Optimize.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jason Volk 757d38e891 Optimize expected auth types checking.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jason Volk 4b66911886 Eliminate redundant duplicate auth and matching room check.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jade EllisandJason Volk 9db3e35f41 Fix incorrect command descriptions. [ci skip] 2025-08-29 02:17:46 +00:00
Jason Volk 7977512d5d Fix additional cases for room_id.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 02:17:46 +00:00
Jason Volk e354be6830 Simplify Pdu from_id_val constructions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 02:17:46 +00:00
Jason Volk f58081838e Log error for send_join auth_check failure.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 02:17:46 +00:00
dasha_uwuandJason Volk 526a138021 Force promotion (closes #136) 2025-08-29 02:17:46 +00:00
dasha_uwuandJason Volk 7fa6894334 Remove excessive doc on admin command 2025-08-29 02:17:46 +00:00
dasha_uwuandJason Volk 33d0ad1be2 Remove dead link in readme 2025-08-29 02:17:46 +00:00
Jason Volkanddasha_uwu fbc3892904 ci: Disable lychee for now due to excessive build time issue during install.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-28 23:28:20 +05:00
Jason Volkanddasha_uwu ce6212b161 Add room_version argument and reorg convert_outgoing_federation_event.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-28 23:28:20 +05:00
Jason Volkanddasha_uwu 0aea56f02a Optimize OnceServices; simplify init.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-28 23:28:20 +05:00
dasha_uwu d05d3f710f Room deletion (fixes #43) 2025-08-28 23:28:20 +05:00
Jason Volkanddasha_uwu 7a3496869b Remove associated pushers on device delete. (fixes #120)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-28 23:28:20 +05:00
dasha_uwu 146b72265d Skip serialization for appservice id 2025-08-28 23:28:20 +05:00
dasha_uwu 120a43cceb Fix joining restricted rooms with invite 2025-08-28 23:28:20 +05:00
dasha_uwu c40671bfb0 Remove api crate dependency from admin crate 2025-08-28 23:28:20 +05:00
dasha_uwu 8e9c6661b2 Membership refactor
Introduce deactivate, membership services

Move membership and user deactivation functions from api crate into those services
2025-08-28 23:28:20 +05:00
dasha_uwu 6810604629 banned_room_check: pull out common part 2025-08-28 23:28:20 +05:00
dasha_uwu 3dd984eff9 clippy: add must_use attribute 2025-08-28 23:28:20 +05:00
dasha_uwu bcd4f80149 Move server user presence handling to presence service 2025-08-28 23:28:20 +05:00
dasha_uwu f51a7b12b5 Make Service.interrupt async 2025-08-28 23:28:20 +05:00
dasha_uwu 3a1649bcaf Add config option for creating admin room non-federated (closes #118) 2025-08-28 23:28:20 +05:00
dasha_uwu d4421422b2 Remove explicit unstable thirdparty/protocols route
Already handled by the other ruma route
2025-08-28 23:28:20 +05:00
dasha_uwu 4884c55be7 Split disable_room, ban_room functions 2025-08-28 23:28:13 +05:00
Jason Volkanddasha_uwu a4f589f475 Remove unnecessary recursion_limit.
Reduce type_length_limits.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-28 23:20:44 +05:00
dasha_uwu b5890b9664 Services refactor
Replace structs of Dep<Service> with OnceServices, so each service has a Services reference

Remove service name => Service map

Flatten Services.rooms

Make reqwest Clients lazy initialized (client service)
2025-08-28 23:15:59 +05:00
Jason Volk 26b3a84b88 Fix updated serde type expected by database deserialize_any.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-27 00:41:13 +00:00
Jason Volk 3430d4ac86 Improved solution to current_shortstatehash > next_batch. (59b62b1)
`current_shortstatehash` is now calculated from timeline which is already
upper-bound by the `next_batch` snapshot. Previously `current_shortstatehash`
was truly current and had to be disallowed from exceeding `next_batch` by
ignoring the room during sync until a future pass when these values finally
met that condition.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-27 00:41:12 +00:00
Jason Volk 3e2f2fbffa Bump Ruma post-hydra.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-27 00:41:12 +00:00
Jason Volk 74deb14ca0 Bump Complement post-hydra.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-27 00:41:12 +00:00
Jason Volk acd930b9ca Replace clippy::string_to_string with clippy::implicit_clone
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-26 08:49:26 +00:00
Jason Volk e2db2029c5 Add note directing users to docker deployment docs.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-26 02:40:25 +00:00
Jason Volk 4bb83a1972 Fix version 12 outgoing federation format.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-26 02:40:25 +00:00
Jason Volk 751aa27859 Bump dependencies for yanked slab.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-14 02:25:54 +00:00
Jason Volk 628597c318 State-reset and security mitigations.
Upgrade Ruma to present.

The following are intentionally benign for activation in a later commit:

- Hydra backports not default.
- Room version 12 not default.
- Room version 12 not listed as stable.

Do not enable them manually or you can brick your database.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-11 18:45:28 +00:00
Jason Volk 2c6dd78502 Reduce log level on router::args returned error.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-11 11:32:53 +00:00
Jason Volk cdb0e31b21 Allow clippy::default_trait_access.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-11 11:32:53 +00:00
dasha_uwuandJason Volk 6a6f77a100 Simplify feature condition.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-11 10:24:40 +00:00
Jason Volk a3666fe672 Use member profile rather than global profile for joined_members (fixes #121)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-10 23:52:03 +00:00
Jason Volk 0c8df52d18 Bump dependencies.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-07 04:52:46 +00:00
Jason Volk d7e5c0ccf3 Optimize spaces pageload; pipeline outer loop.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-07 03:16:32 +00:00
258 changed files with 19723 additions and 10173 deletions
+2
View File
@@ -0,0 +1,2 @@
[env]
RUMA_UNSTABLE_EXHAUSTIVE_TYPES = "true"
+2
View File
@@ -106,6 +106,7 @@ jobs:
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
# disabled due to excessive build time issue installing cargo lychee
lychee:
if: >
!failure() && !cancelled()
@@ -113,6 +114,7 @@ jobs:
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
&& false
name: Lychee
uses: ./.github/workflows/bake.yml
+2 -2
View File
@@ -58,8 +58,8 @@ jobs:
- name: Initialize Builder
env:
runner: ${{matrix.runner}}
reserved_space: '{"het": "384GB", "aws": "48GB", "gcp": "160GB"}'
max_used_space: '{"het": "512GB", "aws": "64GB", "gcp": "192GB"}'
reserved_space: '{"het": "128GB", "aws": "48GB", "gcp": "160GB"}'
max_used_space: '{"het": "256GB", "aws": "64GB", "gcp": "192GB"}'
run: |
set +e
docker buildx inspect "${GITHUB_ACTOR}"
+2 -1
View File
@@ -92,7 +92,8 @@ jobs:
{"cargo_profile": "test", "feat_set": "none", "bake_target": "smoke-valgrind"},
{"cargo_profile": "test", "rust_toolchain": "stable"},
{"cargo_profile": "test", "rust_target": "aarch64-unknown-linux-gnu"},
{"cargo_profile": "test", "sys_target": "x86_64-v2-linux-gnu", "bake_target": "smoke-valgrind"},
{"cargo_profile": "test", "sys_target": "x86_64-v2-linux-gnu"},
{"cargo_profile": "test", "sys_target": "x86_64-v3-linux-gnu"},
{"cargo_profile": "release", "rust_toolchain": "nightly"},
{"cargo_profile": "release", "rust_toolchain": "stable", "feat_set": "none"},
{"cargo_profile": "release", "bake_target": "smoke-valgrind"},
Generated
+456 -409
View File
File diff suppressed because it is too large Load Diff
+91 -100
View File
@@ -29,7 +29,7 @@ license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/matrix-construct/tuwunel"
rust-version = "1.88.0"
version = "1.3.0"
version = "1.4.1"
[workspace.metadata.crane]
name = "tuwunel"
@@ -39,7 +39,7 @@ name = "tuwunel"
#
[workspace.dependencies.argon2]
version = "0.5.3"
version = "0.5"
default-features = false
features = [
"alloc",
@@ -47,17 +47,17 @@ features = [
]
[workspace.dependencies.arrayvec]
version = "0.7.6"
version = "0.7"
features = ["serde"]
[workspace.dependencies.async-channel]
version = "2.3.1"
[workspace.dependencies.async-trait]
version = "0.1.88"
version = "0.1"
[workspace.dependencies.axum]
version = "0.8.4"
version = "0.8"
default-features = false
features = [
"form",
@@ -73,7 +73,7 @@ features = [
version = "0.7"
[workspace.dependencies.axum-extra]
version = "0.10.1"
version = "0.10"
default-features = false
features = [
"typed-header",
@@ -81,7 +81,7 @@ features = [
]
[workspace.dependencies.axum-server]
version = "0.7.2"
version = "0.7"
default-features = false
[workspace.dependencies.axum-server-dual-protocol]
@@ -89,11 +89,11 @@ version = "0.7"
default-features = false
[workspace.dependencies.base64]
version = "0.22.1"
version = "0.22"
default-features = false
[workspace.dependencies.blurhash]
version = "0.2.3"
version = "0.2"
default-features = false
features = [
"fast-linear-to-srgb",
@@ -101,13 +101,13 @@ features = [
]
[workspace.dependencies.bytes]
version = "1.10.1"
version = "1.10"
[workspace.dependencies.bytesize]
version = "2.0.1"
version = "2.0"
[workspace.dependencies.cargo_toml]
version = "0.22.1"
version = "0.22"
default-features = false
features = ["features"]
@@ -115,12 +115,12 @@ features = ["features"]
version = "0.1"
[workspace.dependencies.chrono]
version = "0.4.41"
version = "0.4"
features = ["alloc", "std"]
default-features = false
[workspace.dependencies.clap]
version = "4.5.39"
version = "4.5"
default-features = false
features = [
"derive",
@@ -140,26 +140,26 @@ features = [
version = "0.8.3"
[workspace.dependencies.const-str]
version = "0.6.2"
version = "0.6"
[workspace.dependencies.ctor]
version = "0.4.2"
version = "0.4"
[workspace.dependencies.cyborgtime]
version = "2.1.1"
version = "2.1"
[workspace.dependencies.either]
version = "1.15.0"
version = "1.15"
default-features = false
features = ["serde"]
[workspace.dependencies.figment]
version = "0.10.19"
version = "0.10"
default-features = false
features = ["env", "toml"]
[workspace.dependencies.futures]
version = "0.3.31"
version = "0.3"
default-features = false
features = ["std", "async-await"]
@@ -173,7 +173,7 @@ features = [
]
[workspace.dependencies.hickory-resolver]
version = "0.25.2"
version = "0.25"
default-features = false
features = [
"serde",
@@ -182,17 +182,17 @@ features = [
]
[workspace.dependencies.hmac]
version = "0.12.1"
version = "0.12"
default-features = false
[workspace.dependencies.http]
version = "1.3.1"
version = "1.3"
[workspace.dependencies.http-body-util]
version = "0.1.3"
version = "0.1"
[workspace.dependencies.hyper]
version = "1.6.0"
version = "1.6"
default-features = false
features = [
"server",
@@ -210,7 +210,7 @@ features = [
]
[workspace.dependencies.image]
version = "0.25.6"
version = "0.25"
default-features = false
features = [
"jpeg",
@@ -219,14 +219,20 @@ features = [
"webp",
]
[workspace.dependencies.insta]
version = "1.43.1"
features = [
"json",
]
[workspace.dependencies.ipaddress]
version = "0.1.3"
version = "0.1"
[workspace.dependencies.itertools]
version = "0.14.0"
version = "0.14"
[workspace.dependencies.jsonwebtoken]
version = "9.3.1"
version = "9.3"
default-features = false
features = ["use_pem"]
@@ -237,39 +243,39 @@ default-features = false
features = ["sync", "tls-rustls"]
[workspace.dependencies.libc]
version = "0.2.172"
version = "0.2"
[workspace.dependencies.libloading]
version = "0.8.8"
version = "0.8"
[workspace.dependencies.log]
version = "0.4.27"
version = "0.4"
default-features = false
[workspace.dependencies.loole]
version = "0.4.1"
version = "0.4"
[workspace.dependencies.lru-cache]
version = "0.1.2"
version = "0.1"
[workspace.dependencies.maplit]
version = "1.0.2"
version = "1.0"
[workspace.dependencies.minicbor]
version = "1.0.0"
version = "2.0"
features = ["std"]
[workspace.dependencies.minicbor-serde]
version = "0.5.0"
version = "0.6"
features = ["std"]
[workspace.dependencies.nix]
version = "0.30.1"
version = "0.30"
default-features = false
features = ["resource"]
[workspace.dependencies.num-traits]
version = "0.2.19"
version = "0.2"
[workspace.dependencies.opentelemetry]
version = "0.30.0"
@@ -280,23 +286,23 @@ version = "0.30.0"
#features = ["rt-tokio"]
[workspace.dependencies.opentelemetry_sdk]
version = "0.30.0"
version = "0.30"
features = ["rt-tokio"]
[workspace.dependencies.proc-macro2]
version = "1.0.95"
version = "1.0"
[workspace.dependencies.quote]
version = "1.0.40"
version = "1.0"
[workspace.dependencies.rand]
version = "0.8.5"
version = "0.8"
[workspace.dependencies.regex]
version = "1.11.1"
version = "1.11"
[workspace.dependencies.reqwest]
version = "0.12.19"
version = "0.12"
default-features = false
features = [
"rustls-tls-native-roots",
@@ -306,25 +312,24 @@ features = [
]
[workspace.dependencies.ring]
version = "0.17.14"
version = "0.17"
default-features = false
[workspace.dependencies.ruma]
git = "https://github.com/matrix-construct/ruma"
rev = "0155c2b33233bec9dece79d5134a9574b347f4c1"
rev = "8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6"
features = [
"compat",
"rand",
"__compat",
"appservice-api-c",
"client-api",
"client-api-s",
"compat-upload-signatures",
"federation-api",
"identifiers-validation",
"markdown",
"push-gateway-api-c",
"unstable-exhaustive-types",
"rand",
"ring-compat",
"compat-upload-signatures",
"identifiers-validation",
"unstable-unspecified",
"unstable-msc2448",
"unstable-msc2666",
"unstable-msc2867",
@@ -332,24 +337,23 @@ features = [
"unstable-msc3026",
"unstable-msc3061",
"unstable-msc3245",
"unstable-msc3266",
"unstable-msc3381", # polls
"unstable-msc3489", # beacon / live location
"unstable-msc3575",
"unstable-msc3930", # polls push rules
"unstable-msc4075",
"unstable-msc4095",
"unstable-msc4121",
"unstable-msc4125",
"unstable-msc4133",
"unstable-msc4186",
"unstable-msc4203", # sending to-device events to appservices
"unstable-msc4210", # remove legacy mentions
"unstable-msc4311",
"unstable-extensible-events",
"unstable-pdu",
"unstable-hydra",
]
[workspace.dependencies.rustls]
version = "0.23.27"
version = "0.23"
default-features = false
features = ["aws_lc_rs"]
@@ -370,14 +374,14 @@ features = [
]
[workspace.dependencies.sanitize-filename]
version = "0.6.0"
version = "0.6"
[workspace.dependencies.sd-notify]
version = "0.4.5"
version = "0.4"
default-features = false
[workspace.dependencies.sentry]
version = "0.38.1"
version = "0.42"
default-features = false
features = [
"backtrace",
@@ -393,38 +397,41 @@ features = [
]
[workspace.dependencies.sentry-tower]
version = "0.38.1"
version = "0.42"
[workspace.dependencies.sentry-tracing]
version = "0.38.1"
version = "0.42"
[workspace.dependencies.serde]
version = "1.0.219"
version = "1.0"
default-features = false
features = ["rc"]
[workspace.dependencies.serde_html_form]
version = "0.2.6"
version = "0.2"
[workspace.dependencies.serde_json]
version = "1.0.140"
version = "1.0"
default-features = false
features = ["raw_value"]
[workspace.dependencies.serde_regex]
version = "1.1.0"
version = "1.1"
[workspace.dependencies.serde_yaml]
version = "0.9.34"
version = "0.9"
[workspace.dependencies.sha1]
version = "0.10.6"
version = "0.10"
default-features = false
[workspace.dependencies.sha2]
version = "0.10.9"
version = "0.10"
default-features = false
[workspace.dependencies.similar]
version = "2.7.0"
[workspace.dependencies.smallstr]
version = "0.3"
features = [
@@ -434,7 +441,7 @@ features = [
]
[workspace.dependencies.smallvec]
version = "1.15.1"
version = "1.15"
features = [
"const_generics",
"const_new",
@@ -444,7 +451,7 @@ features = [
]
[workspace.dependencies.syn]
version = "2.0.101"
version = "2.0"
default-features = false
features = [
"extra-traits",
@@ -455,11 +462,11 @@ features = [
]
[workspace.dependencies.termimad]
version = "0.33.0"
version = "0.33"
default-features = false
[workspace.dependencies.thiserror]
version = "2.0.12"
version = "2.0"
default-features = false
[workspace.dependencies.tikv-jemallocator]
@@ -487,7 +494,7 @@ features = [
]
[workspace.dependencies.tokio]
version = "1.46.0"
version = "1.47"
default-features = false
features = [
"fs",
@@ -502,20 +509,20 @@ features = [
]
[workspace.dependencies.tokio-metrics]
version = "0.4.2"
version = "0.4"
[workspace.dependencies.toml]
version = "0.8.23"
version = "0.9"
default-features = false
features = ["parse"]
[workspace.dependencies.tower]
version = "0.5.2"
version = "0.5"
default-features = false
features = ["util"]
[workspace.dependencies.tower-http]
version = "0.6.6"
version = "0.6"
default-features = false
features = [
"add-extension",
@@ -543,7 +550,7 @@ version = "0.2.0"
version = "0.31.0"
[workspace.dependencies.tracing-subscriber]
version = "0.3.19"
version = "0.3.20"
default-features = false
features = [
"ansi",
@@ -555,35 +562,18 @@ features = [
]
[workspace.dependencies.url]
version = "2.5.4"
version = "2.5"
default-features = false
features = ["serde"]
[workspace.dependencies.webpage]
version = "2.0.1"
version = "2.0"
default-features = false
#
# Patches
#
# backport of [https://github.com/tokio-rs/tracing/pull/2956] to the 0.1.x
# branch of tracing. we can switch back to upstream if #2956 is merged and
# backported in the upstream repo.
# https://github.com/girlbossceo/tracing/commit/b348dca742af641c47bc390261f60711c2af573c
[patch.crates-io.tracing-subscriber]
git = "https://github.com/matrix-construct/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
[patch.crates-io.tracing]
git = "https://github.com/matrix-construct/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
[patch.crates-io.tracing-core]
git = "https://github.com/matrix-construct/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
[patch.crates-io.tracing-log]
git = "https://github.com/matrix-construct/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
# adds a tab completion callback:
# https://github.com/girlbossceo/rustyline-async/commit/de26100b0db03e419a3d8e1dd26895d170d1fe50
# adds event for CTRL+\:
@@ -915,6 +905,7 @@ pedantic = { level = "warn", priority = -1 }
## some sadness
too_long_first_doc_paragraph = { level = "allow", priority = 1 }
default_trait_access = { level = "allow", priority = 1 }
doc_markdown = { level = "allow", priority = 1 }
enum_glob_use = { level = "allow", priority = 1 }
if_not_else = { level = "allow", priority = 1 }
@@ -972,7 +963,7 @@ semicolon_outside_block = "warn"
str_to_string = "warn"
string_lit_chars_any = "warn"
string_slice = "warn"
string_to_string = "warn"
implicit_clone = "warn"
suspicious_xor_used_as_pow = "warn"
tests_outside_test_module = "warn"
try_err = "warn"
+7 -7
View File
@@ -21,7 +21,7 @@ ## High Performance Matrix Homeserver in Rust!
[bot](https://matrix.org/ecosystem/integrations/). It is written entirely in Rust to be a scalable,
lightweight, low-cost, community-driven alternative covering all but the most niche uses.
This project is the official successor to [conduwuit](https://github.com/girlbossceo/conduwuit), which
This project is the official successor to conduwuit, which
was a featureful and high-performance fork of [Conduit](https://gitlab.com/famedly/conduit), all
community-lead homeservers implementing the compatible
[Matrix Specification](https://spec.matrix.org/latest/).
@@ -57,12 +57,12 @@ ### Getting Started
`/etc/caddy/Caddyfile` configuration with [Element](https://github.com/element-hq/element-web/releases)
unzipped to `/var/www/element`:
```
https://tuwunel.me:8448 {
reverse_proxy http://127.0.0.1:8008
tuwunel.me, tuwunel.me:8448 {
reverse_proxy localhost:8008
}
https://tuwunel.me:443 {
root * /var/www/element/
file_server
web.tuwunel.me {
root * /var/www/element/
file_server
}
```
`caddy reload --config /etc/caddy/Caddyfile`
@@ -75,7 +75,7 @@ ### Getting Started
🤗 Did you find this and other documentation helpful? We would love to hear feedback about setting
up Tuwunel.
### Migrating to Tuwunel
+4 -54
View File
@@ -1,59 +1,9 @@
# Tuwunel 1.3.0
# Tuwunel 1.4.1
August 4, 2025
Preparations for [Project Hydra](https://matrix.org/blog/2025/07/security-predisclosure/) have taken place. Users should be prepared to upgrade to `v1.4.0-rc` next week. Due to the comprehensive low-level changes which have taken place, and the inability to develop them in public with your feedback, the release will undergo an extended `-rc` period. Due to the time difference and scheduling conflicts our release may be published between 12 to 36 hours after the suggested time; though well before the written disclosure of the issues.
Some foundational work for `v1.4.0` was backported into this release after the announcement was made mid-July to further extend Hydra. An unexpected performance improvement drastically reduced CPU usage. As a result, integration tests began to flake, uncovering latent concurrency bugs which were addressed. These fixes primarily impact the legacy and sliding-sync systems, and further fixes improve performance and compliance, though mostly in the legacy system as sliding-sync lacks adequate test coverage.
This release fixes many bugs and improves performance but at the cost of planned features further rescheduled to either `v1.4.1` or `v1.5.0`.
### New Features
- Refresh tokens (MSC2918) have been implemented. Supporting clients can now timeout their access tokens with a soft-logout after a configured `access_token_ttl`. This feature was commissioned and made public by an enterprise sponsor.
- Typing indicators have been added to sliding-sync thanks to @tmayoff. This feature (and the whole of sliding-sync) is still experimental and the indicator may not always appear or disappear as intended, nevertheless the effort will be enhanced by foundational fixes improving sliding-sync requested soon by the project's sponsor.
### Enhancements
- @dasha_uwu maintains their streak as a serial contributor by patching the `!admin query raw` command with a base64 option allowing for low-level debugging of database records.
- @obioma has improved the documentation explaining how to use multiple configuration files with precedence.
- Upon recommendation of @grin a basic request ID has been added to the tracing logs to uniquely distinguish each request while it's interleaved among others.
- Requested by @fruzitent this and future releases are tagged by version as multi-arch docker images to be properly archived in the registry rather than simply overwriting `:latest`.
- Event processing performance has been improved by fetching and processing `prev_events` and `auth_events` concurrently. This reduces the impact of recursing large graphs while the room's mutex is locked.
- An experimental command `!admin debug resync-database` has been added for developers curious about #35
September 2, 2025
Bridge and Application Service users must update from Tuwunel 1.4.0 to this patch. All other users are encouraged to update at their convenience.
### Bug Fixes
- Thanks to @tmayoff room avatars are properly calculated and no longer the same for all spaces (https://github.com/matrix-construct/tuwunel/pull/102).
- Courtesy of @coolGi69 our bump to Rust 1.88 was properly updated for Nix. Apologies to the Nix community for getting this wrong the first time.
- Invite rejections have been fixed, this was due to a misinterpretation of the spec in legacy sync.
- Knock rooms might have been buggy from the ambiguous overuse of the word "count" in the codebase. Database records which expected a summation of the users in a room instead received the sequence number of the server, both are called "count."
- Room knocks failed to wakeup the sync systems; some cases of account_data changes also failed to wakeup the sync systems. These have been addressed.
- The main sequence number fundamental to the entire server's operation (the "count" or counter) has been refactored after having exceeded architectural limitations. It has been replaced by a two-phase counter ensuring read-after-write consistency, and quasi-transactions grouping multiple writes.
- Sequence issues have been addressed in both legacy and sliding sync. These systems operate using a "snapshot" approach which intentionally ignores new data received by the server after the sync request has started; the snapshot approach replaced the complex of mutexes used by Conduit. The server was not originally designed for this approach and some information "from the future" continued to leak into the snapshot's window; these leaks have been sealed.
- Protocol compliance issues in legacy sync have been addressed. Additional compliance tests for device list updates now pass. The `state` and `timeline` on incremental sync provide expected results in more (if not all) cases.
- Errors requiring M_BAD_ALIAS instead of M_UNKNOWN when sending `m.room.canonical_alias` are now conforming.
### Deprecations
- Unauthenticated media fallbacks are no longer requested by default. This can still be enabled with `request_legacy_media` if desired.
- Legacy Sliding-Sync has been removed in favor of Simplified Sliding-Sync. Clients which exclusively using Sliding-Sync have already migrated around the start of this year, so this removal should have no impact now.
- Special thanks to @alaviss for immediately reporting incorrect results from the `/joined_members` endpoint in the v1.4.0 release. This regression primarily affects Application Services and Bridges; the most popular clients have been verified to not make use of this API.
+1 -1
View File
@@ -167,7 +167,7 @@ RUN \
EOF
WORKDIR /usr/src
ADD https://github.com/matrix-construct/complement.git#81f3b61e10ec102a36921890c81b79564ebf6f40 complement
ADD https://github.com/matrix-construct/complement.git#403840348f6bcc9cc8ed1671dc2f638c2b1ce4ac complement
WORKDIR /usr/src/complement
ENV COMPLEMENT_BASE_IMAGE="complement-testee"
+5
View File
@@ -1,5 +1,10 @@
# Docker Builder
> [!NOTE]
> This directory is for building docker images. For [deploying](https://github.com/matrix-construct/tuwunel/tree/main/docs/deploying)
> [docker](https://github.com/matrix-construct/tuwunel/blob/main/docs/deploying/docker.md) visit
> the appropriate documentation instead.
All Docker images for the project are built here. All images are
[Docker Bake](https://docs.docker.com/build/bake/) targets. All targets are leaves and
branches of a unified tree leading to a single root. It is a combinatorial matrix
+1 -1
View File
@@ -1504,7 +1504,7 @@ cargo_installs = [
"cargo-deb",
#"cargo-arch",
"cargo-generate-rpm",
"lychee",
#"lychee",
"mdbook",
"typos-cli",
]
+1 -1
View File
@@ -61,7 +61,7 @@ toolchain_toml="$docker_dir/../rust-toolchain.toml"
rust_msrv=$(grep "channel = " "$toolchain_toml" | cut -d'=' -f2 | sed 's/\s"\|"$//g')
rocksdb_opt_level=3
rocksdb_portable=1
git_checkout="HEAD"
git_checkout="${git_checkout:-HEAD}"
use_chef="true"
set +a
+3
View File
@@ -31,6 +31,9 @@ skip="${skip}|TestRoomCreate/Parallel/POST_/createRoom_makes_a_room_with_a_topic
skip="${skip}|TestLogin/parallel/POST_/"
skip="${skip}|TestUnbanViaInvite"
skip="${skip}|TestRoomState/Parallel/GET_/publicRooms_lists_newly-created_room"
# flakes due to timeout in debug-mode
skip="${skip}|TestMSC4297StateResolutionV2_1_starts_from_empty_set"
skip="${skip}|TestMSC4297StateResolutionV2_1_includes_conflicted_subgraph"
set -a
cargo_profile="${cargo_profile:-$default_cargo_profile}"
+1 -1
View File
@@ -153,7 +153,7 @@ ### Caddy
```caddyfile
your.server.name, your.server.name:8448 {
# TCP reverse_proxy
reverse_proxy 127.0.0.1:6167
reverse_proxy localhost:8008
# UNIX socket
#reverse_proxy unix//run/tuwunel/tuwunel.sock
}
+66 -35
View File
@@ -93,48 +93,79 @@ ## RocksDB / database issues
#### Database corruption
If your database is corrupted *and* is failing to start (e.g. checksum
mismatch), it may be recoverable but careful steps must be taken, and there is
no guarantee it may be recoverable.
There are many causes and varieties of database corruption. There are several
methods for mitigation, each with outcomes ranging from a recovered state down
to a savage state. This guide has been simplified into a set of universal steps
which everyone can follow from the top until they have recovered or reach the
end. The details and implications will be explained within each step.
The first thing that can be done is launching Tuwunel with the
`rocksdb_repair` config option set to true. This will tell RocksDB to attempt to
repair itself at launch. If this does not work, disable the option and continue
reading.
> [!NOTE]
> All command-line `-O` options can be expressed as environment variables or in
> the config file based on your deployment's requirements. Note that
> `--maintenance` is only available on the command-line, but is equivalent to
> configuring `startup_netburst = false` and `listening = false`.
RocksDB has the following recovery modes:
0. Start the server with the following options:
- `TolerateCorruptedTailRecords`
- `AbsoluteConsistency`
- `PointInTime`
- `SkipAnyCorruptedRecord`
`tuwunel --maintenance -O rocksdb_recovery_mode=0`
By default, Tuwunel uses `TolerateCorruptedTailRecords` as generally these may
be due to bad federation and we can re-fetch the correct data over federation.
The RocksDB default is `PointInTime` which will attempt to restore a "snapshot"
of the data when it was last known to be good. This data can be either a few
seconds old, or multiple minutes prior. `PointInTime` may not be suitable for
default usage due to clients and servers possibly not being able to handle
sudden "backwards time travels", and `AbsoluteConsistency` may be too strict.
This is actually a "control" and not a method of recovery. If the server starts
you either do not have corruption or have deep corruption indicated by very
specific errors from rocksdb citing corruption during runtime. If you are
certain there is deep corruption skip to step 4, otherwise you are finished
without any modifications.
`AbsoluteConsistency` will fail to start the database if any sign of corruption
is detected. `SkipAnyCorruptedRecord` will skip all forms of corruption unless
it forbids the database from opening (e.g. too severe). Usage of
`SkipAnyCorruptedRecord` voids any support as this may cause more damage and/or
leave your database in a permanently inconsistent state, but it may do something
if `PointInTime` does not work as a last ditch effort.
1. Start the server in Tolerate-Corrupted-Tail-Records mode:
With this in mind:
`tuwunel --maintenance -O rocksdb_recovery_mode=1`
- First start Tuwunel with the `PointInTime` recovery method. See the [example
config](configuration/examples.md) for how to do this using
`rocksdb_recovery_mode`
- If your database successfully opens, clients are recommended to clear their
client cache to account for the rollback
- Leave your Tuwunel running in `PointInTime` for at least 30-60 minutes so as
much possible corruption is restored
- If all goes will, you should be able to restore back to using
`TolerateCorruptedTailRecords` and you have successfully recovered your database
The most common corruption scenario is from a loss of power to the hardware
(not an application crash, though it is still possible). This is remediated
by dropping the most recently written record. It is highly unlikely there will
be any impact on the application from this loss. In the best-case the same data
is often re-requested over the federation or replaced by a client. In the
worst-case clients may need to clear-cache & reload to guarantee correctness.
If the server starts you are finished.
2. Start the server in Point-In-Time mode:
`tuwunel --maintenance -O rocksdb_recovery_mode=2`
Similar to the corruption scenario above but for more severe cases. The most
recent records are discarded back to the point where there is no corruption.
It is highly unlikely there will be any impact on the application from this
loss, but it is more likely than above that clients may need to clear-cache
& reload to correctly resynchronize with the server.
3. Start the server in Skip-Any-Corrupted-Record mode:
> [!CAUTION]
> Salvage mode potentially impacting the application's ability to function.
> We cannot provide any further support for users who have entered this mode.
`tuwunel --maintenance -O rocksdb_recovery_mode=3`
Similar to the prior corruption scenarios but for the most severe cases.
The database will be inconsistent. It is theoretically possible for the
server to continue functioning without notable issue in the best case, but
it is completely uncertain what the effect of this operation will be. If
the server starts you should immediately export your messages, encryption
keys, etc, in a salvage effort and prepare to reinstall.
4. Start the server in repair mode.
> [!CAUTION]
> Salvage mode potentially impacting the application's ability to function.
> We cannot provide any further support for users who have entered this mode.
`tuwunel --maintenance -O rocksdb_repair=true`
For corruption affecting the bulk database tables not covered by any journal.
This will leave the database in an inconsistent and unpredictable state. It
is theoretically possible to continue operating the server depending on which
records were dropped, such as some historical records which are no longer
essential. Nevertheless the impact of this operation is impossible to assess
and a successful recovery should be used to salvage data prior to reinstall.
## Debugging
-12
View File
@@ -18,56 +18,46 @@ crate-type = [
[features]
brotli_compression = [
"tuwunel-api/brotli_compression",
"tuwunel-core/brotli_compression",
"tuwunel-service/brotli_compression",
]
bzip2_compression = [
"tuwunel-api/bzip2_compression",
"tuwunel-database/bzip2_compression",
"tuwunel-service/bzip2_compression",
]
gzip_compression = [
"tuwunel-api/gzip_compression",
"tuwunel-core/gzip_compression",
"tuwunel-service/gzip_compression",
]
io_uring = [
"tuwunel-api/io_uring",
"tuwunel-database/io_uring",
"tuwunel-service/io_uring",
]
jemalloc = [
"tuwunel-api/jemalloc",
"tuwunel-core/jemalloc",
"tuwunel-database/jemalloc",
"tuwunel-service/jemalloc",
]
jemalloc_conf = [
"tuwunel-api/jemalloc_conf",
"tuwunel-core/jemalloc_conf",
"tuwunel-database/jemalloc_conf",
"tuwunel-service/jemalloc_conf",
]
jemalloc_prof = [
"tuwunel-api/jemalloc_prof",
"tuwunel-core/jemalloc_prof",
"tuwunel-database/jemalloc_prof",
"tuwunel-service/jemalloc_prof",
]
jemalloc_stats = [
"tuwunel-api/jemalloc_stats",
"tuwunel-core/jemalloc_stats",
"tuwunel-database/jemalloc_stats",
"tuwunel-service/jemalloc_stats",
]
lz4_compression = [
"tuwunel-api/lz4_compression",
"tuwunel-database/lz4_compression",
"tuwunel-service/lz4_compression",
]
release_max_log_level = [
"tuwunel-api/release_max_log_level",
"tuwunel-core/release_max_log_level",
"tuwunel-database/release_max_log_level",
"tuwunel-service/release_max_log_level",
@@ -77,7 +67,6 @@ release_max_log_level = [
"log/release_max_level_info",
]
zstd_compression = [
"tuwunel-api/zstd_compression",
"tuwunel-core/zstd_compression",
"tuwunel-database/zstd_compression",
"tuwunel-service/zstd_compression",
@@ -86,7 +75,6 @@ zstd_compression = [
[dependencies]
base64.workspace = true
clap.workspace = true
tuwunel-api.workspace = true
tuwunel-core.workspace = true
tuwunel-database.workspace = true
tuwunel-macros.workspace = true
+24 -43
View File
@@ -45,7 +45,6 @@ pub(super) async fn echo(&self, message: Vec<String>) -> Result {
pub(super) async fn get_auth_chain(&self, event_id: OwnedEventId) -> Result {
let Ok(event) = self
.services
.rooms
.timeline
.get_pdu_json(&event_id)
.await
@@ -64,7 +63,6 @@ pub(super) async fn get_auth_chain(&self, event_id: OwnedEventId) -> Result {
let start = Instant::now();
let count = self
.services
.rooms
.auth_chain
.event_ids_iter(room_id, once(event_id.as_ref()))
.ready_filter_map(Result::ok)
@@ -87,9 +85,12 @@ pub(super) async fn parse_pdu(&self) -> Result {
}
let string = self.body[1..self.body.len().saturating_sub(1)].join("\n");
let rules = RoomVersionId::V6
.rules()
.expect("rules for V6 rooms");
match serde_json::from_str(&string) {
| Err(e) => return Err!("Invalid json in command body: {e}"),
| Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
| Ok(value) => match ruma::signatures::reference_hash(&value, &rules) {
| Err(e) => return Err!("Could not parse PDU JSON: {e:?}"),
| Ok(hash) => {
let event_id = OwnedEventId::parse(format!("${hash}"));
@@ -108,7 +109,6 @@ pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
let mut outlier = false;
let mut pdu_json = self
.services
.rooms
.timeline
.get_non_outlier_pdu_json(&event_id)
.await;
@@ -117,7 +117,6 @@ pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
outlier = true;
pdu_json = self
.services
.rooms
.timeline
.get_pdu_json(&event_id)
.await;
@@ -152,7 +151,6 @@ pub(super) async fn get_short_pdu(
let pdu_json = self
.services
.rooms
.timeline
.get_pdu_json_from_id(&pdu_id)
.await;
@@ -252,7 +250,6 @@ pub(super) async fn get_remote_pdu(
.sending
.send_federation_request(&server, ruma::api::federation::event::get_event::v1::Request {
event_id: event_id.clone(),
include_unredacted_content: None,
})
.await
{
@@ -273,32 +270,28 @@ pub(super) async fn get_remote_pdu(
})?;
trace!("Attempting to parse PDU: {:?}", &response.pdu);
let _parsed_pdu = {
let (room_id, ..) = {
let parsed_result = self
.services
.rooms
.event_handler
.parse_incoming_pdu(&response.pdu)
.boxed()
.await;
let (event_id, value, room_id) = match parsed_result {
match parsed_result {
| Ok(t) => t,
| Err(e) => {
warn!("Failed to parse PDU: {e}");
info!("Full PDU: {:?}", &response.pdu);
return Err!("Failed to parse PDU remote server {server} sent us: {e}");
},
};
vec![(event_id, value, room_id)]
}
};
info!("Attempting to handle event ID {event_id} as backfilled PDU");
self.services
.rooms
.timeline
.backfill_pdu(&server, response.pdu)
.backfill_pdu(&room_id, &server, response.pdu)
.await?;
let text = serde_json::to_string_pretty(&json)?;
@@ -311,10 +304,9 @@ pub(super) async fn get_remote_pdu(
#[admin_command]
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result {
let room_id = self.services.rooms.alias.resolve(&room).await?;
let room_id = self.services.alias.resolve(&room).await?;
let room_state: Vec<Raw<AnyStateEvent>> = self
.services
.rooms
.state_accessor
.room_state_full_pdus(&room_id)
.map_ok(Event::into_format)
@@ -492,7 +484,6 @@ pub(super) async fn verify_pdu(&self, event_id: OwnedEventId) -> Result {
let mut event = self
.services
.rooms
.timeline
.get_pdu_json(&event_id)
.await?;
@@ -517,7 +508,6 @@ pub(super) async fn verify_pdu(&self, event_id: OwnedEventId) -> Result {
pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
if !self
.services
.rooms
.state_cache
.server_in_room(&self.services.server.name, &room_id)
.await
@@ -527,7 +517,6 @@ pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
let first_pdu = self
.services
.rooms
.timeline
.first_pdu_in_room(&room_id)
.await
@@ -542,7 +531,6 @@ pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
if !self
.services
.rooms
.state_cache
.server_in_room(&self.services.server.name, &room_id)
.await
@@ -552,7 +540,6 @@ pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
let latest_pdu = self
.services
.rooms
.timeline
.latest_pdu_in_room(&room_id)
.await
@@ -571,7 +558,6 @@ pub(super) async fn force_set_room_state_from_server(
) -> Result {
if !self
.services
.rooms
.state_cache
.server_in_room(&self.services.server.name, &room_id)
.await
@@ -581,7 +567,6 @@ pub(super) async fn force_set_room_state_from_server(
let first_pdu = self
.services
.rooms
.timeline
.latest_pdu_in_room(&room_id)
.await
@@ -589,7 +574,6 @@ pub(super) async fn force_set_room_state_from_server(
let room_version = self
.services
.rooms
.state
.get_room_version(&room_id)
.await?;
@@ -608,7 +592,6 @@ pub(super) async fn force_set_room_state_from_server(
for pdu in remote_state_response.pdus.clone() {
match self
.services
.rooms
.event_handler
.parse_incoming_pdu(&pdu)
.await
@@ -627,24 +610,33 @@ pub(super) async fn force_set_room_state_from_server(
.server_keys
.validate_and_add_event_id(pdu, &room_version)
}) {
let Ok((event_id, value)) = result.await else {
let Ok((event_id, mut value)) = result.await else {
continue;
};
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
let invalid_pdu_err = |e| {
debug_error!("Invalid PDU in fetching remote room state PDUs response: {value:#?}");
err!(BadServerResponse(debug_error!("Invalid PDU in send_join response: {e:?}")))
})?;
};
let pdu = if value["type"] == "m.room.create" {
PduEvent::from_rid_val(&room_id, &event_id, value.clone()).map_err(invalid_pdu_err)?
} else {
PduEvent::from_id_val(&event_id, value.clone()).map_err(invalid_pdu_err)?
};
if !value.contains_key("room_id") {
let room_id = CanonicalJsonValue::String(room_id.as_str().into());
value.insert("room_id".into(), room_id);
}
self.services
.rooms
.timeline
.add_pdu_outlier(&event_id, &value);
if let Some(state_key) = &pdu.state_key {
let shortstatekey = self
.services
.rooms
.short
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)
.await;
@@ -667,14 +659,12 @@ pub(super) async fn force_set_room_state_from_server(
};
self.services
.rooms
.timeline
.add_pdu_outlier(&event_id, &value);
}
let new_room_state = self
.services
.rooms
.event_handler
.resolve_state(&room_id, &room_version, state)
.await?;
@@ -686,21 +676,13 @@ pub(super) async fn force_set_room_state_from_server(
removed,
} = self
.services
.rooms
.state_compressor
.save_state(room_id.clone().as_ref(), new_room_state)
.await?;
let state_lock = self
.services
.rooms
.state
.mutex
.lock(&*room_id)
.await;
let state_lock = self.services.state.mutex.lock(&*room_id).await;
self.services
.rooms
.state
.force_state(room_id.clone().as_ref(), short_state_hash, added, removed, &state_lock)
.await?;
@@ -710,7 +692,6 @@ pub(super) async fn force_set_room_state_from_server(
the room's m.room.member state"
);
self.services
.rooms
.state_cache
.update_joined_count(&room_id)
.await;
+2 -11
View File
@@ -72,15 +72,6 @@ pub(super) enum DebugCommand {
},
/// - Gets all the room state events for the specified room.
///
/// This is functionally equivalent to `GET
/// /_matrix/client/v3/rooms/{roomid}/state`, except the admin command does
/// *not* check if the sender user is allowed to see state events. This is
/// done because it's implied that server admins here have database access
/// and can see/get room info themselves anyways if they were malicious
/// admins.
///
/// Of course the check is still done on the actual client API.
GetRoomState {
/// Room ID
room_id: OwnedRoomOrAliasId,
@@ -125,13 +116,13 @@ pub(super) enum DebugCommand {
reset: bool,
},
/// - Verify json signatures
/// - Sign JSON blob
///
/// This command needs a JSON blob provided in a Markdown code block below
/// the command.
SignJson,
/// - Verify json signatures
/// - Verify JSON signatures
///
/// This command needs a JSON blob provided in a Markdown code block below
/// the command.
+2 -9
View File
@@ -6,19 +6,13 @@
#[admin_command]
pub(super) async fn disable_room(&self, room_id: OwnedRoomId) -> Result {
self.services
.rooms
.metadata
.disable_room(&room_id, true);
self.services.metadata.disable_room(&room_id);
self.write_str("Room disabled.").await
}
#[admin_command]
pub(super) async fn enable_room(&self, room_id: OwnedRoomId) -> Result {
self.services
.rooms
.metadata
.disable_room(&room_id, false);
self.services.metadata.enable_room(&room_id);
self.write_str("Room enabled.").await
}
@@ -82,7 +76,6 @@ pub(super) async fn remote_user_in_rooms(&self, user_id: OwnedUserId) -> Result
let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
.services
.rooms
.state_cache
.rooms_joined(&user_id)
.then(|room_id| get_room_info(self.services, room_id))
-1
View File
@@ -37,7 +37,6 @@ pub(super) async fn delete(
// parsing the PDU for any MXC URLs begins here
match self
.services
.rooms
.timeline
.get_pdu_json(&event_id)
.await
-1
View File
@@ -1,4 +1,3 @@
#![recursion_limit = "192"]
#![allow(clippy::wildcard_imports)]
#![allow(clippy::enum_glob_use)]
#![allow(clippy::too_many_arguments)]
+1 -7
View File
@@ -30,11 +30,7 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Context<'_>)
match subcommand {
| RoomAliasCommand::ResolveLocalAlias { alias } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
.alias
.resolve_local_alias(&alias)
.await;
let results = services.alias.resolve_local_alias(&alias).await;
let query_time = timer.elapsed();
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
@@ -42,7 +38,6 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Context<'_>)
| RoomAliasCommand::LocalAliasesForRoom { room_id } => {
let timer = tokio::time::Instant::now();
let aliases: Vec<_> = services
.rooms
.alias
.local_aliases_for_room(&room_id)
.map(ToOwned::to_owned)
@@ -55,7 +50,6 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Context<'_>)
| RoomAliasCommand::AllLocalAliases => {
let timer = tokio::time::Instant::now();
let aliases = services
.rooms
.alias
.all_local_aliases()
.map(|(room_id, alias)| (room_id.to_owned(), alias.to_owned()))
-16
View File
@@ -83,7 +83,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::ServerInRoom { server, room_id } => {
let timer = tokio::time::Instant::now();
let result = services
.rooms
.state_cache
.server_in_room(&server, &room_id)
.await;
@@ -98,7 +97,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomServers { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.room_servers(&room_id)
.map(ToOwned::to_owned)
@@ -115,7 +113,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::ServerRooms { server } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.server_rooms(&server)
.map(ToOwned::to_owned)
@@ -132,7 +129,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomMembers { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.room_members(&room_id)
.map(ToOwned::to_owned)
@@ -149,7 +145,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::LocalUsersInRoom { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.local_users_in_room(&room_id)
.map(ToOwned::to_owned)
@@ -166,7 +161,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::ActiveLocalUsersInRoom { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.active_local_users_in_room(&room_id)
.map(ToOwned::to_owned)
@@ -183,7 +177,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomJoinedCount { room_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
.state_cache
.room_joined_count(&room_id)
.await;
@@ -198,7 +191,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomInvitedCount { room_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
.state_cache
.room_invited_count(&room_id)
.await;
@@ -213,7 +205,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomUserOnceJoined { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.room_useroncejoined(&room_id)
.map(ToOwned::to_owned)
@@ -230,7 +221,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomMembersInvited { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.room_members_invited(&room_id)
.map(ToOwned::to_owned)
@@ -247,7 +237,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::GetInviteCount { room_id, user_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
.state_cache
.get_invite_count(&room_id, &user_id)
.await;
@@ -262,7 +251,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::GetLeftCount { room_id, user_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
.state_cache
.get_left_count(&room_id, &user_id)
.await;
@@ -277,7 +265,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomsJoined { user_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.rooms_joined(&user_id)
.map(ToOwned::to_owned)
@@ -294,7 +281,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomsInvited { user_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.rooms_invited(&user_id)
.collect()
@@ -310,7 +296,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomsLeft { user_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.rooms_left(&user_id)
.collect()
@@ -326,7 +311,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::InviteState { user_id, room_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
.state_cache
.invite_state(&user_id, &room_id)
.await;
+3 -15
View File
@@ -25,18 +25,12 @@ pub(crate) enum RoomTimelineCommand {
#[admin_command]
pub(super) async fn last(&self, room_id: OwnedRoomOrAliasId) -> Result {
let room_id = self
.services
.rooms
.alias
.resolve(&room_id)
.await?;
let room_id = self.services.alias.resolve(&room_id).await?;
let result = self
.services
.rooms
.timeline
.last_timeline_count(None, &room_id)
.last_timeline_count(None, &room_id, None)
.await?;
self.write_str(&format!("{result:#?}")).await
@@ -49,18 +43,12 @@ pub(super) async fn pdus(
from: Option<String>,
limit: Option<usize>,
) -> Result {
let room_id = self
.services
.rooms
.alias
.resolve(&room_id)
.await?;
let room_id = self.services.alias.resolve(&room_id).await?;
let from: Option<PduCount> = from.as_deref().map(str::parse).transpose()?;
let result: Vec<_> = self
.services
.rooms
.timeline
.pdus_rev(None, &room_id, from)
.try_take(limit.unwrap_or(3))
+1 -8
View File
@@ -21,7 +21,6 @@ pub(crate) enum ShortCommand {
pub(super) async fn short_event_id(&self, event_id: OwnedEventId) -> Result {
let shortid = self
.services
.rooms
.short
.get_shorteventid(&event_id)
.await?;
@@ -31,16 +30,10 @@ pub(super) async fn short_event_id(&self, event_id: OwnedEventId) -> Result {
#[admin_command]
pub(super) async fn short_room_id(&self, room_id: OwnedRoomOrAliasId) -> Result {
let room_id = self
.services
.rooms
.alias
.resolve(&room_id)
.await?;
let room_id = self.services.alias.resolve(&room_id).await?;
let shortid = self
.services
.rooms
.short
.get_shortroomid(&room_id)
.await?;
-1
View File
@@ -134,7 +134,6 @@ async fn get_shared_rooms(&self, user_a: OwnedUserId, user_b: OwnedUserId) -> Re
let timer = tokio::time::Instant::now();
let result: Vec<_> = self
.services
.rooms
.state_cache
.get_shared_rooms(&user_a, &user_b)
.map(ToOwned::to_owned)
+8 -16
View File
@@ -63,17 +63,15 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
match (
force,
services
.rooms
.alias
.resolve_local_alias(&room_alias)
.await,
) {
| (true, Ok(id)) => {
match services.rooms.alias.set_alias(
&room_alias,
&room_id,
server_user,
) {
match services
.alias
.set_alias(&room_alias, &room_id, server_user)
{
| Err(err) => Err!("Failed to remove alias: {err}"),
| Ok(()) =>
context
@@ -88,11 +86,10 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
overwrite"
),
| (_, Err(_)) => {
match services.rooms.alias.set_alias(
&room_alias,
&room_id,
server_user,
) {
match services
.alias
.set_alias(&room_alias, &room_id, server_user)
{
| Err(err) => Err!("Failed to remove alias: {err}"),
| Ok(()) => context.write_str("Successfully set alias").await,
}
@@ -101,14 +98,12 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
},
| RoomAliasCommand::Remove { .. } => {
match services
.rooms
.alias
.resolve_local_alias(&room_alias)
.await
{
| Err(_) => Err!("Alias isn't in use."),
| Ok(id) => match services
.rooms
.alias
.remove_alias(&room_alias, server_user)
.await
@@ -123,7 +118,6 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
},
| RoomAliasCommand::Which { .. } => {
match services
.rooms
.alias
.resolve_local_alias(&room_alias)
.await
@@ -141,7 +135,6 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
| RoomAliasCommand::List { room_id } =>
if let Some(room_id) = room_id {
let aliases: Vec<OwnedRoomAliasId> = services
.rooms
.alias
.local_aliases_for_room(&room_id)
.map(Into::into)
@@ -160,7 +153,6 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
context.write_str(&plain).await
} else {
let aliases = services
.rooms
.alias
.all_local_aliases()
.map(|(room_id, localpart)| (room_id.into(), localpart.into()))
+22 -21
View File
@@ -16,27 +16,14 @@ pub(super) async fn list_rooms(
let page = page.unwrap_or(1);
let mut rooms = self
.services
.rooms
.metadata
.iter_ids()
.filter_map(async |room_id| {
(!exclude_disabled
|| !self
.services
.rooms
.metadata
.is_disabled(room_id)
.await)
(!exclude_disabled || !self.services.metadata.is_disabled(room_id).await)
.then_some(room_id)
})
.filter_map(async |room_id| {
(!exclude_banned
|| !self
.services
.rooms
.metadata
.is_banned(room_id)
.await)
(!exclude_banned || !self.services.metadata.is_banned(room_id).await)
.then_some(room_id)
})
.then(|room_id| get_room_info(self.services, room_id))
@@ -74,12 +61,26 @@ pub(super) async fn list_rooms(
#[admin_command]
pub(super) async fn exists(&self, room_id: OwnedRoomId) -> Result {
let result = self
.services
.rooms
.metadata
.exists(&room_id)
.await;
let result = self.services.metadata.exists(&room_id).await;
self.write_str(&format!("{result}")).await
}
#[admin_command]
pub(super) async fn delete_room(&self, room_id: OwnedRoomId) -> Result {
if self.services.admin.is_admin_room(&room_id).await {
return Err!("Cannot delete admin room");
}
let state_lock = self.services.state.mutex.lock(&room_id).await;
self.services
.delete
.delete_room(&room_id, state_lock)
.await?;
self.write_str("Successfully deleted the room from our database.")
.await?;
Ok(())
}
+2 -3
View File
@@ -29,18 +29,17 @@ pub(super) async fn process(command: RoomDirectoryCommand, context: &Context<'_>
let services = context.services;
match command {
| RoomDirectoryCommand::Publish { room_id } => {
services.rooms.directory.set_public(&room_id);
services.directory.set_public(&room_id);
context.write_str("Room published").await
},
| RoomDirectoryCommand::Unpublish { room_id } => {
services.rooms.directory.set_not_public(&room_id);
services.directory.set_not_public(&room_id);
context.write_str("Room unpublished").await
},
| RoomDirectoryCommand::List { page } => {
// TODO: i know there's a way to do this with clap, but i can't seem to find it
let page = page.unwrap_or(1);
let mut rooms: Vec<_> = services
.rooms
.directory
.public_rooms()
.then(|room_id| get_room_info(services, room_id))
-3
View File
@@ -30,7 +30,6 @@ pub(crate) enum RoomInfoCommand {
async fn list_joined_members(&self, room_id: OwnedRoomId, local_only: bool) -> Result {
let room_name = self
.services
.rooms
.state_accessor
.get_name(&room_id)
.await
@@ -38,7 +37,6 @@ async fn list_joined_members(&self, room_id: OwnedRoomId, local_only: bool) -> R
let member_info: Vec<_> = self
.services
.rooms
.state_cache
.room_members(&room_id)
.ready_filter(|user_id| {
@@ -75,7 +73,6 @@ async fn list_joined_members(&self, room_id: OwnedRoomId, local_only: bool) -> R
async fn view_room_topic(&self, room_id: OwnedRoomId) -> Result {
let Ok(room_topic) = self
.services
.rooms
.state_accessor
.get_room_topic(&room_id)
.await
+5
View File
@@ -56,4 +56,9 @@ pub(super) enum RoomCommand {
Exists {
room_id: OwnedRoomId,
},
/// - Delete room
DeleteRoom {
room_id: OwnedRoomId,
},
}
+26 -58
View File
@@ -1,7 +1,6 @@
use clap::Subcommand;
use futures::{FutureExt, StreamExt};
use ruma::{OwnedRoomId, OwnedRoomOrAliasId, RoomAliasId, RoomId, RoomOrAliasId};
use tuwunel_api::client::leave_room;
use tuwunel_core::{
Err, Result, debug,
utils::{IterStream, ReadyExt},
@@ -70,10 +69,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
};
debug!("Room specified is a room ID, banning room ID");
self.services
.rooms
.metadata
.ban_room(room_id, true);
self.services.metadata.ban_room(room_id);
room_id.to_owned()
} else if room.is_room_alias_id() {
@@ -95,7 +91,6 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
let room_id = match self
.services
.rooms
.alias
.resolve_local_alias(room_alias)
.await
@@ -109,7 +104,6 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.await
@@ -131,10 +125,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
},
};
self.services
.rooms
.metadata
.ban_room(&room_id, true);
self.services.metadata.ban_room(&room_id);
room_id
} else {
@@ -148,7 +139,6 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
debug!("Making all users leave the room {room_id} and forgetting it");
let mut users = self
.services
.rooms
.state_cache
.room_members(&room_id)
.map(ToOwned::to_owned)
@@ -161,27 +151,31 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
evicting admins too)",
);
if let Err(e) = leave_room(self.services, user_id, &room_id, None)
let state_lock = self.services.state.mutex.lock(&room_id).await;
if let Err(e) = self
.services
.membership
.leave(user_id, &room_id, None, &state_lock)
.boxed()
.await
{
warn!("Failed to leave room: {e}");
}
drop(state_lock);
self.services
.rooms
.state_cache
.forget(&room_id, user_id);
}
self.services
.rooms
.alias
.local_aliases_for_room(&room_id)
.map(ToOwned::to_owned)
.for_each(async |local_alias| {
self.services
.rooms
.alias
.remove_alias(&local_alias, &self.services.globals.server_user)
.await
@@ -190,15 +184,9 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
.await;
// unpublish from room directory
self.services
.rooms
.directory
.set_not_public(&room_id);
self.services.directory.set_not_public(&room_id);
self.services
.rooms
.metadata
.disable_room(&room_id, true);
self.services.metadata.disable_room(&room_id);
self.write_str(
"Room banned, removed all our local users, and disabled incoming federation with room.",
@@ -258,7 +246,6 @@ async fn ban_list_of_rooms(&self) -> Result {
| Ok(room_alias) => {
let room_id = match self
.services
.rooms
.alias
.resolve_local_alias(room_alias)
.await
@@ -272,7 +259,6 @@ async fn ban_list_of_rooms(&self) -> Result {
match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.await
@@ -320,10 +306,7 @@ async fn ban_list_of_rooms(&self) -> Result {
}
for room_id in room_ids {
self.services
.rooms
.metadata
.ban_room(&room_id, true);
self.services.metadata.ban_room(&room_id);
debug!("Banned {room_id} successfully");
room_ban_count = room_ban_count.saturating_add(1);
@@ -331,7 +314,6 @@ async fn ban_list_of_rooms(&self) -> Result {
debug!("Making all users leave the room {room_id} and forgetting it");
let mut users = self
.services
.rooms
.state_cache
.room_members(&room_id)
.map(ToOwned::to_owned)
@@ -344,28 +326,32 @@ async fn ban_list_of_rooms(&self) -> Result {
evicting admins too)",
);
if let Err(e) = leave_room(self.services, user_id, &room_id, None)
let state_lock = self.services.state.mutex.lock(&room_id).await;
if let Err(e) = self
.services
.membership
.leave(user_id, &room_id, None, &state_lock)
.boxed()
.await
{
warn!("Failed to leave room: {e}");
}
drop(state_lock);
self.services
.rooms
.state_cache
.forget(&room_id, user_id);
}
// remove any local aliases, ignore errors
self.services
.rooms
.alias
.local_aliases_for_room(&room_id)
.map(ToOwned::to_owned)
.for_each(async |local_alias| {
self.services
.rooms
.alias
.remove_alias(&local_alias, &self.services.globals.server_user)
.await
@@ -374,15 +360,9 @@ async fn ban_list_of_rooms(&self) -> Result {
.await;
// unpublish from room directory, ignore errors
self.services
.rooms
.directory
.set_not_public(&room_id);
self.services.directory.set_not_public(&room_id);
self.services
.rooms
.metadata
.disable_room(&room_id, true);
self.services.metadata.disable_room(&room_id);
}
self.write_str(&format!(
@@ -407,10 +387,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
};
debug!("Room specified is a room ID, unbanning room ID");
self.services
.rooms
.metadata
.ban_room(room_id, false);
self.services.metadata.unban_room(room_id);
room_id.to_owned()
} else if room.is_room_alias_id() {
@@ -432,7 +409,6 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
let room_id = match self
.services
.rooms
.alias
.resolve_local_alias(room_alias)
.await
@@ -446,7 +422,6 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.await
@@ -466,10 +441,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
},
};
self.services
.rooms
.metadata
.ban_room(&room_id, false);
self.services.metadata.unban_room(&room_id);
room_id
} else {
@@ -480,10 +452,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
);
};
self.services
.rooms
.metadata
.disable_room(&room_id, false);
self.services.metadata.enable_room(&room_id);
self.write_str("Room unbanned and federation re-enabled.")
.await
}
@@ -492,7 +461,6 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
async fn list_banned_rooms(&self, no_details: bool) -> Result {
let room_ids: Vec<OwnedRoomId> = self
.services
.rooms
.metadata
.list_banned_rooms()
.map(Into::into)
+194 -152
View File
@@ -2,30 +2,27 @@
use futures::{FutureExt, StreamExt};
use ruma::{
OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId,
Int, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId,
events::{
RoomAccountDataEventType, StateEventType,
room::{
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent, UserPowerLevel},
redaction::RoomRedactionEventContent,
},
tag::{TagEvent, TagEventContent, TagInfo},
},
};
use tuwunel_api::client::{
full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, update_avatar_url,
update_displayname,
};
use tuwunel_core::{
Err, Result, debug, debug_warn, error, info, is_equal_to,
matrix::{Event, pdu::PduBuilder},
utils::{self, ReadyExt},
warn,
};
use tuwunel_service::Services;
use crate::{
admin_command, get_room_info,
utils::{parse_active_local_user_id, parse_local_user_id},
utils::{parse_active_local_user_id, parse_local_user_id, parse_user_id},
};
const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
@@ -122,7 +119,7 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
.is_empty()
{
for room in &self.services.server.config.auto_join_rooms {
let Ok(room_id) = self.services.rooms.alias.resolve(room).await else {
let Ok(room_id) = self.services.alias.resolve(room).await else {
error!(
%user_id,
"Failed to resolve room alias to room ID when attempting to auto join {room}, skipping"
@@ -132,7 +129,6 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
if !self
.services
.rooms
.state_cache
.server_in_room(self.services.globals.server_name(), &room_id)
.await
@@ -143,20 +139,24 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
continue;
}
let state_lock = self.services.state.mutex.lock(&room_id).await;
if let Some(room_server_name) = room.server_name() {
match join_room_by_id_helper(
self.services,
&user_id,
&room_id,
Some("Automatically joining this room upon registration".to_owned()),
&[
self.services.globals.server_name().to_owned(),
room_server_name.to_owned(),
],
None,
&None,
)
.await
match self
.services
.membership
.join(
&user_id,
&room_id,
Some("Automatically joining this room upon registration".to_owned()),
&[
self.services.globals.server_name().to_owned(),
room_server_name.to_owned(),
],
&None,
&state_lock,
)
.await
{
| Ok(_response) => {
info!("Automatically joined room {room} for user {user_id}");
@@ -175,6 +175,8 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
.await;
},
}
drop(state_lock);
}
}
}
@@ -186,7 +188,6 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
if let Ok(admin_room) = self.services.admin.get_admin_room().await {
if self
.services
.rooms
.state_cache
.room_joined_count(&admin_room)
.await
@@ -195,6 +196,7 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
self.services
.admin
.make_user_admin(&user_id)
.boxed()
.await?;
warn!("Granting {user_id} admin privileges as the first user");
}
@@ -216,34 +218,7 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
return Err!("Not allowed to deactivate the server service account.",);
}
self.services
.users
.deactivate_account(&user_id)
.await?;
if !no_leave_rooms {
self.services
.admin
.send_text(&format!("Making {user_id} leave all rooms after deactivation..."))
.await;
let all_joined_rooms: Vec<OwnedRoomId> = self
.services
.rooms
.state_cache
.rooms_joined(&user_id)
.map(Into::into)
.collect()
.await;
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
.boxed()
.await?;
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms).await;
leave_all_rooms(self.services, &user_id).await;
}
deactivate_user(self.services, &user_id, no_leave_rooms).await?;
self.write_str(&format!("User {user_id} has been deactivated"))
.await
@@ -336,41 +311,16 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
let mut deactivation_count: usize = 0;
for user_id in user_ids {
match self
.services
.users
.deactivate_account(&user_id)
.await
{
match deactivate_user(self.services, &user_id, no_leave_rooms).await {
| Ok(()) => {
deactivation_count = deactivation_count.saturating_add(1);
},
| Err(e) => {
self.services
.admin
.send_text(&format!("Failed deactivating user: {e}"))
.await;
},
| Ok(()) => {
deactivation_count = deactivation_count.saturating_add(1);
if !no_leave_rooms {
info!("Forcing user {user_id} to leave all rooms apart of deactivate-all");
let all_joined_rooms: Vec<OwnedRoomId> = self
.services
.rooms
.state_cache
.rooms_joined(&user_id)
.map(Into::into)
.collect()
.await;
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
.boxed()
.await?;
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms)
.await;
leave_all_rooms(self.services, &user_id).await;
}
},
}
}
@@ -387,6 +337,20 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
.await
}
async fn deactivate_user(services: &Services, user_id: &UserId, no_leave_rooms: bool) -> Result {
if !no_leave_rooms {
services
.deactivate
.full_deactivate(user_id)
.boxed()
.await?;
} else {
services.users.deactivate_account(user_id).await?;
}
Ok(())
}
#[admin_command]
pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result {
// Validate user id
@@ -394,7 +358,6 @@ pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result {
let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
.services
.rooms
.state_cache
.rooms_joined(&user_id)
.then(|room_id| get_room_info(self.services, room_id))
@@ -444,14 +407,12 @@ pub(super) async fn force_join_list_of_local_users(
let (room_id, servers) = self
.services
.rooms
.alias
.resolve_with_servers(&room_id, None)
.await?;
if !self
.services
.rooms
.state_cache
.server_in_room(self.services.globals.server_name(), &room_id)
.await
@@ -461,7 +422,6 @@ pub(super) async fn force_join_list_of_local_users(
let server_admins: Vec<_> = self
.services
.rooms
.state_cache
.active_local_users_in_room(&admin_room)
.map(ToOwned::to_owned)
@@ -470,7 +430,6 @@ pub(super) async fn force_join_list_of_local_users(
if !self
.services
.rooms
.state_cache
.room_members(&room_id)
.ready_any(|user_id| server_admins.contains(&user_id.to_owned()))
@@ -518,17 +477,21 @@ pub(super) async fn force_join_list_of_local_users(
let mut failed_joins: usize = 0;
let mut successful_joins: usize = 0;
let state_lock = self.services.state.mutex.lock(&room_id).await;
for user_id in user_ids {
match join_room_by_id_helper(
self.services,
&user_id,
&room_id,
Some(String::from(BULK_JOIN_REASON)),
&servers,
None,
&None,
)
.await
match self
.services
.membership
.join(
&user_id,
&room_id,
Some(String::from(BULK_JOIN_REASON)),
&servers,
&None,
&state_lock,
)
.await
{
| Ok(_res) => {
successful_joins = successful_joins.saturating_add(1);
@@ -540,6 +503,8 @@ pub(super) async fn force_join_list_of_local_users(
}
}
drop(state_lock);
self.write_str(&format!(
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins \
failed.",
@@ -566,14 +531,12 @@ pub(super) async fn force_join_all_local_users(
let (room_id, servers) = self
.services
.rooms
.alias
.resolve_with_servers(&room_id, None)
.await?;
if !self
.services
.rooms
.state_cache
.server_in_room(self.services.globals.server_name(), &room_id)
.await
@@ -583,7 +546,6 @@ pub(super) async fn force_join_all_local_users(
let server_admins: Vec<_> = self
.services
.rooms
.state_cache
.active_local_users_in_room(&admin_room)
.map(ToOwned::to_owned)
@@ -592,7 +554,6 @@ pub(super) async fn force_join_all_local_users(
if !self
.services
.rooms
.state_cache
.room_members(&room_id)
.ready_any(|user_id| server_admins.contains(&user_id.to_owned()))
@@ -604,6 +565,8 @@ pub(super) async fn force_join_all_local_users(
let mut failed_joins: usize = 0;
let mut successful_joins: usize = 0;
let state_lock = self.services.state.mutex.lock(&room_id).await;
for user_id in &self
.services
.users
@@ -612,16 +575,18 @@ pub(super) async fn force_join_all_local_users(
.collect::<Vec<_>>()
.await
{
match join_room_by_id_helper(
self.services,
user_id,
&room_id,
Some(String::from(BULK_JOIN_REASON)),
&servers,
None,
&None,
)
.await
match self
.services
.membership
.join(
user_id,
&room_id,
Some(String::from(BULK_JOIN_REASON)),
&servers,
&None,
&state_lock,
)
.await
{
| Ok(_res) => {
successful_joins = successful_joins.saturating_add(1);
@@ -633,6 +598,8 @@ pub(super) async fn force_join_all_local_users(
}
}
drop(state_lock);
self.write_str(&format!(
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins \
failed.",
@@ -649,7 +616,6 @@ pub(super) async fn force_join_room(
let user_id = parse_local_user_id(self.services, &user_id)?;
let (room_id, servers) = self
.services
.rooms
.alias
.resolve_with_servers(&room_id, None)
.await?;
@@ -658,9 +624,16 @@ pub(super) async fn force_join_room(
self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user"
);
join_room_by_id_helper(self.services, &user_id, &room_id, None, &servers, None, &None)
let state_lock = self.services.state.mutex.lock(&room_id).await;
self.services
.membership
.join(&user_id, &room_id, None, &servers, &None, &state_lock)
.await?;
drop(state_lock);
self.write_str(&format!("{user_id} has been joined to {room_id}.",))
.await
}
@@ -672,12 +645,7 @@ pub(super) async fn force_leave_room(
room_id: OwnedRoomOrAliasId,
) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
let room_id = self
.services
.rooms
.alias
.resolve(&room_id)
.await?;
let room_id = self.services.alias.resolve(&room_id).await?;
assert!(
self.services.globals.user_is_local(&user_id),
@@ -686,7 +654,6 @@ pub(super) async fn force_leave_room(
if !self
.services
.rooms
.state_cache
.is_joined(&user_id, &room_id)
.await
@@ -694,10 +661,16 @@ pub(super) async fn force_leave_room(
return Err!("{user_id} is not joined in the room");
}
leave_room(self.services, &user_id, &room_id, None)
let state_lock = self.services.state.mutex.lock(&room_id).await;
self.services
.membership
.leave(&user_id, &room_id, None, &state_lock)
.boxed()
.await?;
drop(state_lock);
self.write_str(&format!("{user_id} has left {room_id}.",))
.await
}
@@ -705,57 +678,49 @@ pub(super) async fn force_leave_room(
#[admin_command]
pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAliasId) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
let room_id = self
.services
.rooms
.alias
.resolve(&room_id)
.await?;
let room_id = self.services.alias.resolve(&room_id).await?;
assert!(
self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user"
);
let state_lock = self
.services
.rooms
.state
.mutex
.lock(&room_id)
.await;
let state_lock = self.services.state.mutex.lock(&room_id).await;
let room_power_levels: Option<RoomPowerLevelsEventContent> = self
let room_power_levels: Option<RoomPowerLevels> = self
.services
.rooms
.state_accessor
.room_state_get_content(&room_id, &StateEventType::RoomPowerLevels, "")
.get_power_levels(&room_id)
.await
.ok();
let user_can_demote_self = room_power_levels
let user_can_change_self = room_power_levels
.as_ref()
.is_some_and(|power_levels_content| {
RoomPowerLevels::from(power_levels_content.clone())
.user_can_change_user_power_level(&user_id, &user_id)
}) || self
.services
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "")
.await
.is_ok_and(|event| event.sender() == user_id);
.is_some_and(|power_levels| {
power_levels.user_can_change_user_power_level(&user_id, &user_id)
});
let user_can_demote_self = user_can_change_self
|| self
.services
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "")
.await
.is_ok_and(|event| event.sender() == user_id);
if !user_can_demote_self {
return Err!("User is not allowed to modify their own power levels in the room.",);
return Err!("User is not allowed to modify their own power levels in the room.");
}
let mut power_levels_content = room_power_levels.unwrap_or_default();
let mut power_levels_content: RoomPowerLevelsEventContent = room_power_levels
.map(TryInto::try_into)
.transpose()?
.unwrap_or_default();
power_levels_content.users.remove(&user_id);
let event_id = self
.services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &power_levels_content),
@@ -772,6 +737,85 @@ pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAli
.await
}
#[admin_command]
pub(super) async fn force_promote(
&self,
target_id: String,
room_id: OwnedRoomOrAliasId,
) -> Result {
let target_id = parse_user_id(self.services, &target_id)?;
let room_id = self.services.alias.resolve(&room_id).await?;
let state_lock = self.services.state.mutex.lock(&room_id).await;
let room_power_levels = self
.services
.state_accessor
.get_power_levels(&room_id)
.await?;
let privileged_member = self
.services
.state_cache
.room_members(&room_id)
.ready_filter(|member_id| {
self.services.globals.user_is_local(member_id)
&& room_power_levels.user_can_change_user_power_level(member_id, &target_id)
})
.map(ToOwned::to_owned)
.ready_fold_default(|selected_user, member_id| match selected_user {
| None => Some(member_id),
| Some(selected_user) => Some(
if room_power_levels.for_user(&selected_user)
> room_power_levels.for_user(&member_id)
{
selected_user
} else {
member_id
},
),
})
.await;
let Some(privileged_member) = privileged_member else {
return Err!("No privileged user exists in room, cannot promote.");
};
info!("Selected privileged member {privileged_member}");
let power_level: Int = match room_power_levels.for_user(&privileged_member) {
| UserPowerLevel::Infinite => Int::MAX,
| UserPowerLevel::Int(x) => x,
};
let mut power_levels_content: RoomPowerLevelsEventContent = room_power_levels.try_into()?;
power_levels_content
.users
.insert(target_id.clone(), power_level);
let event_id = self
.services
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &power_levels_content),
&privileged_member,
&room_id,
&state_lock,
)
.await?;
drop(state_lock);
self.write_str(&format!(
"User {privileged_member} promoted {target_id} to {power_level} power level in \
{room_id} - {event_id}"
))
.await?;
Ok(())
}
#[admin_command]
pub(super) async fn make_user_admin(&self, user_id: String) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
@@ -783,6 +827,7 @@ pub(super) async fn make_user_admin(&self, user_id: String) -> Result {
self.services
.admin
.make_user_admin(&user_id)
.boxed()
.await?;
self.write_str(&format!("{user_id} has been granted admin privileges.",))
@@ -889,7 +934,6 @@ pub(super) async fn get_room_tags(&self, user_id: String, room_id: OwnedRoomId)
pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
let Ok(event) = self
.services
.rooms
.timeline
.get_non_outlier_pdu(&event_id)
.await
@@ -917,14 +961,12 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
let redaction_event_id = {
let state_lock = self
.services
.rooms
.state
.mutex
.lock(event.room_id())
.await;
self.services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
+6
View File
@@ -88,6 +88,12 @@ pub(super) enum UserCommand {
room_id: OwnedRoomOrAliasId,
},
/// - Force promote
ForcePromote {
user_id: String,
room_id: OwnedRoomOrAliasId,
},
/// - Grant server-admin privileges to a user.
MakeUserAdmin {
user_id: String,
-2
View File
@@ -17,13 +17,11 @@ pub(crate) async fn get_room_info(
(
room_id.into(),
services
.rooms
.state_cache
.room_joined_count(room_id)
.await
.unwrap_or(0),
services
.rooms
.state_accessor
.get_name(room_id)
.await
+12 -156
View File
@@ -1,29 +1,15 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use futures::{FutureExt, StreamExt};
use ruma::{
OwnedRoomId, UserId,
api::client::{
account::{
ThirdPartyIdRemovalStatus, change_password, deactivate, get_3pids,
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
whoami,
},
uiaa::{AuthFlow, AuthType, UiaaInfo},
},
events::{
StateEventType,
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
use ruma::api::client::{
account::{
ThirdPartyIdRemovalStatus, change_password, deactivate, get_3pids,
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
whoami,
},
uiaa::{AuthFlow, AuthType, UiaaInfo},
};
use tuwunel_core::{
Err, Error, Result, err, info,
matrix::{Event, pdu::PduBuilder},
utils,
utils::{ReadyExt, stream::BroadbandExt},
warn,
};
use tuwunel_service::Services;
use tuwunel_core::{Err, Error, Result, err, info, utils, utils::ReadyExt};
use super::SESSION_ID_LENGTH;
use crate::Ruma;
@@ -60,10 +46,7 @@ pub(crate) async fn change_password_route(
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
..Default::default()
};
match &body.auth {
@@ -107,29 +90,6 @@ pub(crate) async fn change_password_route(
.ready_filter(|id| *id != body.sender_device())
.for_each(|id| services.users.remove_device(sender_user, id))
.await;
// Remove all pushers except the ones associated with this session
services
.pusher
.get_pushkeys(sender_user)
.map(ToOwned::to_owned)
.broad_filter_map(async |pushkey| {
services
.pusher
.get_pusher_device(&pushkey)
.await
.ok()
.filter(|pusher_device| pusher_device != body.sender_device())
.is_some()
.then_some(pushkey)
})
.for_each(async |pushkey| {
services
.pusher
.delete_pusher(sender_user, &pushkey)
.await;
})
.await;
}
info!("User {sender_user} changed their password.");
@@ -189,10 +149,7 @@ pub(crate) async fn deactivate_route(
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
..Default::default()
};
match &body.auth {
@@ -222,19 +179,9 @@ pub(crate) async fn deactivate_route(
},
}
// Remove profile pictures and display name
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(sender_user)
.map(Into::into)
.collect()
.await;
super::update_displayname(&services, sender_user, None, &all_joined_rooms).await;
super::update_avatar_url(&services, sender_user, None, None, &all_joined_rooms).await;
full_user_deactivate(&services, sender_user, &all_joined_rooms)
services
.deactivate
.full_deactivate(sender_user)
.boxed()
.await?;
@@ -293,94 +240,3 @@ pub(crate) async fn request_3pid_management_token_via_msisdn_route(
) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
Err!(Request(ThreepidDenied("Third party identifiers are not implemented")))
}
/// Runs through all the deactivation steps:
///
/// - Mark as deactivated
/// - Removing display name
/// - Removing avatar URL and blurhash
/// - Removing all profile data
/// - Leaving all rooms (and forgets all of them)
pub async fn full_user_deactivate(
services: &Services,
user_id: &UserId,
all_joined_rooms: &[OwnedRoomId],
) -> Result {
services
.users
.deactivate_account(user_id)
.await
.ok();
super::update_displayname(services, user_id, None, all_joined_rooms).await;
super::update_avatar_url(services, user_id, None, None, all_joined_rooms).await;
services
.users
.all_profile_keys(user_id)
.ready_for_each(|(profile_key, _)| {
services
.users
.set_profile_key(user_id, &profile_key, None);
})
.await;
for room_id in all_joined_rooms {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let room_power_levels = services
.rooms
.state_accessor
.room_state_get_content::<RoomPowerLevelsEventContent>(
room_id,
&StateEventType::RoomPowerLevels,
"",
)
.await
.ok();
let user_can_demote_self =
room_power_levels
.as_ref()
.is_some_and(|power_levels_content| {
RoomPowerLevels::from(power_levels_content.clone())
.user_can_change_user_power_level(user_id, user_id)
}) || services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await
.is_ok_and(|event| event.sender() == user_id);
if user_can_demote_self {
let mut power_levels_content = room_power_levels.unwrap_or_default();
power_levels_content.users.remove(user_id);
// ignore errors so deactivation doesn't fail
match services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &power_levels_content),
user_id,
room_id,
&state_lock,
)
.await
{
| Err(e) => {
warn!(%room_id, %user_id, "Failed to demote user's own power level: {e}");
},
| _ => {
info!("Demoted {user_id} in {room_id} as part of account deactivation");
},
}
}
}
super::leave_all_rooms(services, user_id)
.boxed()
.await;
Ok(())
}
-7
View File
@@ -19,7 +19,6 @@ pub(crate) async fn create_alias_route(
) -> Result<create_alias::v3::Response> {
let sender_user = body.sender_user();
services
.rooms
.alias
.appservice_checks(&body.room_alias, &body.appservice_info)
.await?;
@@ -35,7 +34,6 @@ pub(crate) async fn create_alias_route(
}
if services
.rooms
.alias
.resolve_local_alias(&body.room_alias)
.await
@@ -45,7 +43,6 @@ pub(crate) async fn create_alias_route(
}
services
.rooms
.alias
.set_alias(&body.room_alias, &body.room_id, sender_user)?;
@@ -63,13 +60,11 @@ pub(crate) async fn delete_alias_route(
) -> Result<delete_alias::v3::Response> {
let sender_user = body.sender_user();
services
.rooms
.alias
.appservice_checks(&body.room_alias, &body.appservice_info)
.await?;
services
.rooms
.alias
.remove_alias(&body.room_alias, sender_user)
.await?;
@@ -89,7 +84,6 @@ pub(crate) async fn get_alias_route(
let room_alias = body.body.room_alias;
let Ok((room_id, servers)) = services
.rooms
.alias
.resolve_alias(&room_alias, None)
.await
@@ -111,7 +105,6 @@ async fn room_available_servers(
) -> Vec<OwnedServerName> {
// find active servers in room state cache to suggest
let mut servers: Vec<OwnedServerName> = services
.rooms
.state_cache
.room_servers(room_id)
.map(ToOwned::to_owned)
+7 -5
View File
@@ -3,9 +3,12 @@
use axum::extract::State;
use ruma::{
RoomVersionId,
api::client::discovery::get_capabilities::{
self, Capabilities, GetLoginTokenCapability, RoomVersionStability,
RoomVersionsCapability, ThirdPartyIdChangesCapability,
api::client::discovery::{
get_capabilities,
get_capabilities::v3::{
Capabilities, GetLoginTokenCapability, ProfileFieldsCapability, RoomVersionStability,
RoomVersionsCapability, ThirdPartyIdChangesCapability,
},
},
};
use serde_json::json;
@@ -41,8 +44,7 @@ pub(crate) async fn get_capabilities_route(
enabled: services.server.config.login_via_existing_session,
};
// MSC4133 capability
capabilities.set("uk.tcpip.msc4133.profile_fields", json!({"enabled": true}))?;
capabilities.profile_fields = ProfileFieldsCapability::new(true).into();
capabilities.set(
"org.matrix.msc4267.forget_forced_upon_leave",
+8 -21
View File
@@ -38,7 +38,7 @@ pub(crate) async fn get_context_route(
let event_id = &body.event_id;
let filter = &body.filter;
if !services.rooms.metadata.exists(room_id).await {
if !services.metadata.exists(room_id).await {
return Err!(Request(Forbidden("Room does not exist to this server")));
}
@@ -50,19 +50,16 @@ pub(crate) async fn get_context_route(
.min(LIMIT_MAX);
let base_id = services
.rooms
.timeline
.get_pdu_id(event_id)
.map_err(|_| err!(Request(NotFound("Event not found."))));
let base_pdu = services
.rooms
.timeline
.get_pdu(event_id)
.map_err(|_| err!(Request(NotFound("Base event not found."))));
let visible = services
.rooms
.state_accessor
.user_can_see_event(sender_user, room_id, event_id)
.map(Ok);
@@ -74,7 +71,11 @@ pub(crate) async fn get_context_route(
}
if !visible {
debug_warn!(req_evt = ?event_id, ?base_id, ?room_id, "Event requested by {sender_user} but is not allowed to see it, returning 404");
debug_warn!(
req_evt = ?event_id, ?base_id, ?room_id,
"Event requested by {sender_user} but is not allowed to see it."
);
return Err!(Request(NotFound("Event not found.")));
}
@@ -83,7 +84,6 @@ pub(crate) async fn get_context_route(
let base_event = ignored_filter(&services, (base_count, base_pdu), sender_user);
let events_before = services
.rooms
.timeline
.pdus_rev(Some(sender_user), room_id, Some(base_count))
.ignore_err()
@@ -94,7 +94,6 @@ pub(crate) async fn get_context_route(
.collect();
let events_after = services
.rooms
.timeline
.pdus(Some(sender_user), room_id, Some(base_count))
.ignore_err()
@@ -135,18 +134,11 @@ pub(crate) async fn get_context_route(
.map_or_else(|| body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
let state_ids = services
.rooms
.state_accessor
.pdu_shortstatehash(state_at)
.or_else(|_| {
services
.rooms
.state
.get_room_shortstatehash(room_id)
})
.or_else(|_| services.state.get_room_shortstatehash(room_id))
.map_ok(|shortstatehash| {
services
.rooms
.state_accessor
.state_full_ids(shortstatehash)
.map(Ok)
@@ -163,7 +155,6 @@ pub(crate) async fn get_context_route(
let shorteventids = state_ids.iter().map(ref_at!(1)).stream();
let lazy_loading_witnessed = lazy_loading_witnessed.unwrap_or_default();
let state: Vec<_> = services
.rooms
.short
.multi_get_statekey_from_short(shortstatekeys)
.zip(shorteventids)
@@ -182,11 +173,7 @@ pub(crate) async fn get_context_route(
Some(event_id)
})
.broad_filter_map(|event_id: &OwnedEventId| {
services
.rooms
.timeline
.get_pdu(event_id.as_ref())
.ok()
services.timeline.get_pdu(event_id.as_ref()).ok()
})
.map(Event::into_format)
.collect()
+2 -8
View File
@@ -145,10 +145,7 @@ pub(crate) async fn delete_device_route(
// UIAA
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
..Default::default()
};
match &body.auth {
@@ -224,10 +221,7 @@ pub(crate) async fn delete_devices_route(
// UIAA
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
..Default::default()
};
match &body.auth {
+33 -71
View File
@@ -16,18 +16,15 @@
},
federation,
},
directory::{Filter, PublicRoomJoinRule, PublicRoomsChunk, RoomNetwork, RoomTypeFilter},
directory::{Filter, PublicRoomsChunk, RoomNetwork, RoomTypeFilter},
events::{
StateEventType,
room::{
join_rules::{JoinRule, RoomJoinRulesEventContent},
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
room::join_rules::{JoinRule, RoomJoinRulesEventContent},
},
uint,
};
use tuwunel_core::{
Err, Result, err, info,
Err, Result, err, info, is_true,
matrix::Event,
utils::{
TryFutureExtExt,
@@ -51,7 +48,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_public_rooms_filtered::v3::Request>,
) -> Result<get_public_rooms_filtered::v3::Response> {
check_banned(&services, body.server.as_deref())?;
check_server_banned(&services, body.server.as_deref())?;
let response = get_public_rooms_filtered_helper(
&services,
@@ -80,7 +77,7 @@ pub(crate) async fn get_public_rooms_route(
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_public_rooms::v3::Request>,
) -> Result<get_public_rooms::v3::Response> {
check_banned(&services, body.server.as_deref())?;
check_server_banned(&services, body.server.as_deref())?;
let response = get_public_rooms_filtered_helper(
&services,
@@ -114,12 +111,7 @@ pub(crate) async fn set_room_visibility_route(
) -> Result<set_room_visibility::v3::Response> {
let sender_user = body.sender_user();
if !services
.rooms
.metadata
.exists(&body.room_id)
.await
{
if !services.metadata.exists(&body.room_id).await {
// Return 404 if the room doesn't exist
return Err!(Request(NotFound("Room not found")));
}
@@ -169,7 +161,7 @@ pub(crate) async fn set_room_visibility_route(
)));
}
services.rooms.directory.set_public(&body.room_id);
services.directory.set_public(&body.room_id);
if services.server.config.admin_room_notices {
services
@@ -182,10 +174,7 @@ pub(crate) async fn set_room_visibility_route(
}
info!("{sender_user} made {0} public to the room directory", body.room_id);
},
| room::Visibility::Private => services
.rooms
.directory
.set_not_public(&body.room_id),
| room::Visibility::Private => services.directory.set_not_public(&body.room_id),
| _ => {
return Err!(Request(InvalidParam("Room visibility type is not supported.",)));
},
@@ -201,19 +190,13 @@ pub(crate) async fn get_room_visibility_route(
State(services): State<crate::State>,
body: Ruma<get_room_visibility::v3::Request>,
) -> Result<get_room_visibility::v3::Response> {
if !services
.rooms
.metadata
.exists(&body.room_id)
.await
{
if !services.metadata.exists(&body.room_id).await {
// Return 404 if the room doesn't exist
return Err!(Request(NotFound("Room not found")));
}
Ok(get_room_visibility::v3::Response {
visibility: if services
.rooms
.directory
.is_public_room(&body.room_id)
.await
@@ -298,13 +281,12 @@ pub(crate) async fn get_public_rooms_filtered_helper(
let meta_public_rooms = search_room_id
.filter(|_| services.config.allow_unlisted_room_search_by_id)
.map(|prefix| services.rooms.metadata.public_ids_prefix(prefix))
.map(|prefix| services.metadata.public_ids_prefix(prefix))
.into_iter()
.stream()
.flatten();
let mut all_rooms: Vec<PublicRoomsChunk> = services
.rooms
.directory
.public_rooms()
.map(ToOwned::to_owned)
@@ -391,20 +373,14 @@ async fn user_can_publish_room(
room_id: &RoomId,
) -> Result<bool> {
match services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")
.get_power_levels(room_id)
.await
{
| Ok(event) => serde_json::from_str(event.content().get())
.map_err(|_| err!(Database("Invalid event content for m.room.power_levels")))
.map(|content: RoomPowerLevelsEventContent| {
RoomPowerLevels::from(content)
.user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)
}),
| Ok(power_levels) =>
Ok(power_levels.user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)),
| _ => {
match services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await
@@ -417,57 +393,42 @@ async fn user_can_publish_room(
}
async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> PublicRoomsChunk {
let name = services
.rooms
.state_accessor
.get_name(&room_id)
.ok();
let name = services.state_accessor.get_name(&room_id).ok();
let room_type = services
.rooms
.state_accessor
.get_room_type(&room_id)
.ok();
let canonical_alias = services
.rooms
.state_accessor
.get_canonical_alias(&room_id)
.ok();
let avatar_url = services.rooms.state_accessor.get_avatar(&room_id);
let avatar_url = services.state_accessor.get_avatar(&room_id);
let topic = services
.rooms
.state_accessor
.get_room_topic(&room_id)
.ok();
let world_readable = services
.rooms
.state_accessor
.is_world_readable(&room_id);
let join_rule = services
.rooms
.state_accessor
.room_state_get_content(&room_id, &StateEventType::RoomJoinRules, "")
.map_ok(|c: RoomJoinRulesEventContent| match c.join_rule {
| JoinRule::Public => PublicRoomJoinRule::Public,
| JoinRule::Public => "public".into(),
| JoinRule::Knock => "knock".into(),
| JoinRule::KnockRestricted(_) => "knock_restricted".into(),
| _ => "invite".into(),
});
let guest_can_join = services
.rooms
.state_accessor
.guest_can_join(&room_id);
let guest_can_join = services.state_accessor.guest_can_join(&room_id);
let num_joined_members = services
.rooms
.state_cache
.room_joined_count(&room_id);
let num_joined_members = services.state_cache.room_joined_count(&room_id);
let (
(avatar_url, canonical_alias, guest_can_join, join_rule, name),
@@ -497,24 +458,25 @@ async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> Public
}
}
fn check_banned(services: &Services, server: Option<&ServerName>) -> Result {
fn check_server_banned(services: &Services, server: Option<&ServerName>) -> Result {
let Some(server) = server else {
return Ok(());
};
let forbidden_remote_directory = services
.config
.forbidden_remote_room_directory_server_names
.is_match(server.host());
let conditions = [
services
.config
.forbidden_remote_room_directory_server_names
.is_match(server.host()),
services
.config
.forbidden_remote_server_names
.is_match(server.host()),
];
let forbidden_remote_server = services
.config
.forbidden_remote_server_names
.is_match(server.host());
if forbidden_remote_directory || forbidden_remote_server {
Err!(Request(Forbidden("Server is banned on this homeserver.")))
} else {
Ok(())
if conditions.iter().any(is_true!()) {
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
}
Ok(())
}
+14 -18
View File
@@ -3,7 +3,8 @@
use axum::extract::State;
use futures::{StreamExt, stream::FuturesUnordered};
use ruma::{
OneTimeKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
CanonicalJsonObject, CanonicalJsonValue, OneTimeKeyAlgorithm, OwnedDeviceId, OwnedUserId,
UserId,
api::{
client::{
error::ErrorKind,
@@ -162,10 +163,7 @@ pub(crate) async fn upload_signing_keys_route(
// UIAA
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
..Default::default()
};
match check_for_new_keys(
@@ -396,7 +394,6 @@ pub(crate) async fn get_key_changes_route(
);
let mut rooms_joined = services
.rooms
.state_cache
.rooms_joined(sender_user)
.boxed();
@@ -599,20 +596,19 @@ fn add_unsigned_device_display_name(
include_display_names: bool,
) -> Result {
if let Some(display_name) = metadata.display_name {
let mut object = keys.deserialize_as::<serde_json::Map<String, serde_json::Value>>()?;
let mut object = keys.deserialize_as_unchecked::<CanonicalJsonObject>()?;
let unsigned = object
.entry("unsigned")
.or_insert_with(|| json!({}));
if let serde_json::Value::Object(unsigned_object) = unsigned {
if include_display_names {
unsigned_object.insert("device_display_name".to_owned(), display_name.into());
if let CanonicalJsonValue::Object(unsigned) = object
.entry("unsigned".into())
.or_insert_with(|| CanonicalJsonObject::default().into())
{
let display_name = if include_display_names {
CanonicalJsonValue::String(display_name)
} else {
unsigned_object.insert(
"device_display_name".to_owned(),
Some(metadata.device_id.as_str().to_owned()).into(),
);
}
CanonicalJsonValue::String(metadata.device_id.into())
};
unsigned.insert("device_display_name".into(), display_name);
}
*keys = Raw::from_json(serde_json::value::to_raw_value(&object)?);
+7 -35
View File
@@ -1,9 +1,7 @@
use axum::extract::State;
use ruma::{
api::client::membership::ban_user,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use tuwunel_core::{Err, Result, matrix::pdu::PduBuilder};
use futures::FutureExt;
use ruma::api::client::membership::ban_user;
use tuwunel_core::{Err, Result};
use crate::Ruma;
@@ -20,38 +18,12 @@ pub(crate) async fn ban_user_route(
return Err!(Request(Forbidden("You cannot ban yourself.")));
}
let state_lock = services
.rooms
.state
.mutex
.lock(&body.room_id)
.await;
let current_member_content = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
.unwrap_or_else(|_| RoomMemberEventContent::new(MembershipState::Ban));
let state_lock = services.state.mutex.lock(&body.room_id).await;
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Ban,
reason: body.reason.clone(),
displayname: None, // display name may be offensive
avatar_url: None, // avatar may be offensive
is_direct: None,
join_authorized_via_users_server: None,
third_party_invite: None,
..current_member_content
}),
sender_user,
&body.room_id,
&state_lock,
)
.membership
.ban(&body.room_id, &body.user_id, body.reason.as_ref(), sender_user, &state_lock)
.boxed()
.await?;
drop(state_lock);
+4 -18
View File
@@ -21,18 +21,9 @@ pub(crate) async fn forget_room_route(
let user_id = body.sender_user();
let room_id = &body.room_id;
let joined = services
.rooms
.state_cache
.is_joined(user_id, room_id);
let knocked = services
.rooms
.state_cache
.is_knocked(user_id, room_id);
let invited = services
.rooms
.state_cache
.is_invited(user_id, room_id);
let joined = services.state_cache.is_joined(user_id, room_id);
let knocked = services.state_cache.is_knocked(user_id, room_id);
let invited = services.state_cache.is_invited(user_id, room_id);
pin_mut!(joined, knocked, invited);
if joined.or(knocked).or(invited).await {
@@ -40,7 +31,6 @@ pub(crate) async fn forget_room_route(
}
let membership = services
.rooms
.state_accessor
.get_member(room_id, user_id)
.await;
@@ -55,15 +45,11 @@ pub(crate) async fn forget_room_route(
if non_membership
|| services
.rooms
.state_cache
.is_left(user_id, room_id)
.await
{
services
.rooms
.state_cache
.forget(room_id, user_id);
services.state_cache.forget(room_id, user_id);
}
Ok(forget_room::v3::Response::new())
+50 -222
View File
@@ -1,19 +1,11 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use futures::{FutureExt, join};
use ruma::{
OwnedServerName, RoomId, UserId,
api::{client::membership::invite_user, federation::membership::create_invite},
events::room::member::{MembershipState, RoomMemberEventContent},
};
use tuwunel_core::{
Err, Result, debug_error, err, info,
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
};
use tuwunel_service::Services;
use ruma::{api::client::membership::invite_user, events::room::member::MembershipState};
use tuwunel_core::{Err, Result};
use super::banned_room_check;
use crate::Ruma;
use crate::{Ruma, client::utils::invite_check};
/// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
///
@@ -26,220 +18,56 @@ pub(crate) async fn invite_user_route(
) -> Result<invite_user::v3::Response> {
let sender_user = body.sender_user();
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
debug_error!(
"User {sender_user} is not an admin and attempted to send an invite to room {}",
&body.room_id
);
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
}
let room_id = &body.room_id;
banned_room_check(
&services,
sender_user,
Some(&body.room_id),
body.room_id.server_name(),
client,
)
.await?;
invite_check(&services, sender_user, room_id).await?;
match &body.recipient {
| invite_user::v3::InvitationRecipient::UserId { user_id } => {
let sender_ignored_recipient = services
.users
.user_is_ignored(sender_user, user_id);
let recipient_ignored_by_sender = services
.users
.user_is_ignored(user_id, sender_user);
let (sender_ignored_recipient, recipient_ignored_by_sender) =
join!(sender_ignored_recipient, recipient_ignored_by_sender);
if sender_ignored_recipient {
return Ok(invite_user::v3::Response {});
}
if let Ok(target_user_membership) = services
.rooms
.state_accessor
.get_member(&body.room_id, user_id)
.await
{
if target_user_membership.membership == MembershipState::Ban {
return Err!(Request(Forbidden("User is banned from this room.")));
}
}
if recipient_ignored_by_sender {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
return Ok(invite_user::v3::Response {});
}
invite_helper(
&services,
sender_user,
user_id,
&body.room_id,
body.reason.clone(),
false,
)
.boxed()
.await?;
Ok(invite_user::v3::Response {})
},
| _ => {
Err!(Request(NotFound("User not found.")))
},
}
}
pub(crate) async fn invite_helper(
services: &Services,
sender_user: &UserId,
user_id: &UserId,
room_id: &RoomId,
reason: Option<String>,
is_direct: bool,
) -> Result {
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
info!(
"User {sender_user} is not an admin and attempted to send an invite to room \
{room_id}"
);
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
}
if !services.globals.user_is_local(user_id) {
let (pdu, pdu_json, invite_room_state) = {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
avatar_url: services.users.avatar_url(user_id).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
};
let (pdu, pdu_json) = services
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(user_id.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
.await?;
let invite_room_state = services.rooms.state.summary_stripped(&pdu).await;
drop(state_lock);
(pdu, pdu_json, invite_room_state)
};
let room_version_id = services
.rooms
.state
.get_room_version(room_id)
.await?;
let response = services
.sending
.send_federation_request(user_id.server_name(), create_invite::v2::Request {
room_id: room_id.to_owned(),
event_id: (*pdu.event_id).to_owned(),
room_version: room_version_id.clone(),
event: services
.sending
.convert_to_outgoing_federation_event(pdu_json.clone())
.await,
invite_room_state,
via: services
.rooms
.state_cache
.servers_route_via(room_id)
.await
.ok(),
})
.await?;
// We do not add the event_id field to the pdu here because of signature and
// hashes checks
let (event_id, value) = gen_event_id_canonical_json(&response.event, &room_version_id)
.map_err(|e| {
err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
})?;
if pdu.event_id != event_id {
return Err!(Request(BadJson(warn!(
%pdu.event_id, %event_id,
"Server {} sent event with wrong event ID",
user_id.server_name()
))));
}
let origin: OwnedServerName = serde_json::from_value(serde_json::to_value(
value
.get("origin")
.ok_or_else(|| err!(Request(BadJson("Event missing origin field."))))?,
)?)
.map_err(|e| {
err!(Request(BadJson(warn!("Origin field in event is not a valid server name: {e}"))))
})?;
let pdu_id = services
.rooms
.event_handler
.handle_incoming_pdu(&origin, room_id, &event_id, value, true)
.boxed()
.await?
.ok_or_else(|| {
err!(Request(InvalidParam("Could not accept incoming PDU as timeline event.")))
})?;
return services
.sending
.send_pdu_room(room_id, &pdu_id)
.await;
}
if !services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
return Err!(Request(Forbidden(
"You must be joined in the room you are trying to invite from."
)));
}
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
displayname: services.users.displayname(user_id).await.ok(),
avatar_url: services.users.avatar_url(user_id).await.ok(),
blurhash: services.users.blurhash(user_id).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
};
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(user_id.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
banned_room_check(&services, sender_user, Some(room_id), room_id.server_name(), client)
.await?;
drop(state_lock);
let invite_user::v3::InvitationRecipient::UserId { user_id } = &body.recipient else {
return Err!(Request(ThreepidDenied("Third party identifiers are not implemented")));
};
Ok(())
let sender_ignored_recipient = services
.users
.user_is_ignored(sender_user, user_id);
let recipient_ignored_by_sender = services
.users
.user_is_ignored(user_id, sender_user);
let (sender_ignored_recipient, recipient_ignored_by_sender) =
join!(sender_ignored_recipient, recipient_ignored_by_sender);
if sender_ignored_recipient {
return Ok(invite_user::v3::Response {});
}
// TODO: this should be in the service, but moving it from here would
// trigger the recipient_ignored_by_sender check before the banned check,
// revealing the ignore state to the sending user if the recipient is banned
if let Ok(target_user_membership) = services
.state_accessor
.get_member(room_id, user_id)
.await
{
if target_user_membership.membership == MembershipState::Ban {
return Err!(Request(Forbidden("User is banned from this room.")));
}
}
if recipient_ignored_by_sender {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
return Ok(invite_user::v3::Response {});
}
services
.membership
.invite(sender_user, user_id, room_id, body.reason.as_ref(), false)
.boxed()
.await?;
Ok(invite_user::v3::Response {})
}
File diff suppressed because it is too large Load Diff
+11 -45
View File
@@ -1,9 +1,7 @@
use axum::extract::State;
use ruma::{
api::client::membership::kick_user,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use tuwunel_core::{Err, Result, matrix::pdu::PduBuilder};
use futures::FutureExt;
use ruma::api::client::membership::kick_user;
use tuwunel_core::{Err, Result};
use crate::Ruma;
@@ -14,50 +12,18 @@ pub(crate) async fn kick_user_route(
State(services): State<crate::State>,
body: Ruma<kick_user::v3::Request>,
) -> Result<kick_user::v3::Response> {
let state_lock = services
.rooms
.state
.mutex
.lock(&body.room_id)
.await;
let sender_user = body.sender_user();
let Ok(event) = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
else {
// copy synapse's behaviour of returning 200 without any change to the state
// instead of erroring on left users
return Ok(kick_user::v3::Response::new());
};
if !matches!(
event.membership,
MembershipState::Invite | MembershipState::Knock | MembershipState::Join,
) {
return Err!(Request(Forbidden(
"Cannot kick a user who is not apart of the room (current membership: {})",
event.membership
)));
if sender_user == body.user_id {
return Err!(Request(Forbidden("You cannot kick yourself.")));
}
let state_lock = services.state.mutex.lock(&body.room_id).await;
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Leave,
reason: body.reason.clone(),
is_direct: None,
join_authorized_via_users_server: None,
third_party_invite: None,
..event
}),
body.sender_user(),
&body.room_id,
&state_lock,
)
.membership
.kick(&body.room_id, &body.user_id, body.reason.as_ref(), sender_user, &state_lock)
.boxed()
.await?;
drop(state_lock);
+45 -136
View File
@@ -4,11 +4,14 @@
use axum_client_ip::InsecureClientIp;
use futures::{FutureExt, StreamExt};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, RoomId,
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedServerName, RoomId,
RoomVersionId, UserId,
api::{
client::knock::knock_room,
federation::{self},
federation::{
membership::RawStrippedState,
{self},
},
},
canonical_json::to_canonical_value,
events::{
@@ -17,14 +20,13 @@
},
};
use tuwunel_core::{
Err, Result, debug, debug_info, debug_warn, err, info,
Err, Result, debug, debug_info, debug_warn, err, extract_variant, info,
matrix::{
event::{Event, gen_event_id},
pdu::{PduBuilder, PduEvent},
},
result::FlatOk,
trace,
utils::{self, shuffle, stream::IterStream},
utils::{self},
warn,
};
use tuwunel_service::{
@@ -36,7 +38,7 @@
};
use super::banned_room_check;
use crate::Ruma;
use crate::{Ruma, client::membership::get_join_params};
/// # `POST /_matrix/client/*/knock/{roomIdOrAlias}`
///
@@ -50,102 +52,13 @@ pub(crate) async fn knock_room_route(
let sender_user = body.sender_user();
let body = &body.body;
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias.clone()) {
| Ok(room_id) => {
banned_room_check(
&services,
sender_user,
Some(&room_id),
room_id.server_name(),
client,
)
.await?;
let (room_id, servers) =
get_join_params(&services, sender_user, &body.room_id_or_alias, &body.via).await?;
let mut servers = body.via.clone();
servers.extend(
services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
banned_room_check(&services, sender_user, Some(&room_id), room_id.server_name(), client)
.await?;
servers.extend(
services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default()
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
if let Some(server) = room_id.server_name() {
servers.push(server.to_owned());
}
servers.sort_unstable();
servers.dedup();
shuffle(&mut servers);
(servers, room_id)
},
| Err(room_alias) => {
let (room_id, mut servers) = services
.rooms
.alias
.resolve_alias(&room_alias, Some(body.via.clone()))
.await?;
banned_room_check(
&services,
sender_user,
Some(&room_id),
Some(room_alias.server_name()),
client,
)
.await?;
let addl_via_servers = services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned);
let addl_state_servers = services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default();
let mut addl_servers: Vec<_> = addl_state_servers
.iter()
.map(|event| event.get_field("sender"))
.filter_map(FlatOk::flat_ok)
.map(|user: &UserId| user.server_name().to_owned())
.stream()
.chain(addl_via_servers)
.collect()
.await;
addl_servers.sort_unstable();
addl_servers.dedup();
shuffle(&mut addl_servers);
servers.append(&mut addl_servers);
(servers, room_id)
},
};
knock_room_by_id_helper(&services, sender_user, &room_id, body.reason.clone(), &servers)
.boxed()
.await
knock_room_by_id_helper(&services, sender_user, &room_id, body.reason.clone(), &servers).await
}
async fn knock_room_by_id_helper(
@@ -155,10 +68,9 @@ async fn knock_room_by_id_helper(
reason: Option<String>,
servers: &[OwnedServerName],
) -> Result<knock_room::v3::Response> {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let state_lock = services.state.mutex.lock(room_id).await;
if services
.rooms
.state_cache
.is_invited(sender_user, room_id)
.await
@@ -170,7 +82,6 @@ async fn knock_room_by_id_helper(
}
if services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
@@ -180,7 +91,6 @@ async fn knock_room_by_id_helper(
}
if services
.rooms
.state_cache
.is_knocked(sender_user, room_id)
.await
@@ -190,7 +100,6 @@ async fn knock_room_by_id_helper(
}
if let Ok(membership) = services
.rooms
.state_accessor
.get_member(room_id, sender_user)
.await
@@ -202,7 +111,6 @@ async fn knock_room_by_id_helper(
}
let server_in_room = services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.await;
@@ -234,11 +142,7 @@ async fn knock_room_helper_local(
) -> Result {
debug_info!("We can knock locally");
let room_version_id = services
.rooms
.state
.get_room_version(room_id)
.await?;
let room_version_id = services.state.get_room_version(room_id).await?;
if matches!(
room_version_id,
@@ -262,7 +166,6 @@ async fn knock_room_helper_local(
// Try normal knock first
let Err(error) = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
@@ -346,12 +249,12 @@ async fn knock_room_helper_local(
let knock_event = knock_event_stub;
info!("Asking {remote_server} for send_knock in room {room_id}");
let send_knock_request = federation::knock::send_knock::v1::Request {
let send_knock_request = federation::membership::create_knock_event::v1::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
pdu: services
.sending
.convert_to_outgoing_federation_event(knock_event.clone())
.federation
.format_pdu_into(knock_event.clone(), Some(&room_version_id))
.await,
};
@@ -363,7 +266,6 @@ async fn knock_room_helper_local(
info!("send_knock finished");
services
.rooms
.short
.get_or_create_shortroomid(room_id)
.await;
@@ -375,7 +277,6 @@ async fn knock_room_helper_local(
info!("Updating membership locally to knock state with provided stripped state events");
services
.rooms
.state_cache
.update_membership(
room_id,
@@ -384,7 +285,13 @@ async fn knock_room_helper_local(
.get_content::<RoomMemberEventContent>()
.expect("we just created this"),
sender_user,
Some(send_knock_response.knock_room_state),
Some(
send_knock_response
.knock_room_state
.into_iter()
.filter_map(|s| extract_variant!(s, RawStrippedState::Stripped))
.collect(),
),
None,
false,
)
@@ -392,7 +299,6 @@ async fn knock_room_helper_local(
info!("Appending room knock event locally");
services
.rooms
.timeline
.append_pdu(
&parsed_knock_pdu,
@@ -477,12 +383,12 @@ async fn knock_room_helper_remote(
let knock_event = knock_event_stub;
info!("Asking {remote_server} for send_knock in room {room_id}");
let send_knock_request = federation::knock::send_knock::v1::Request {
let send_knock_request = federation::membership::create_knock_event::v1::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
pdu: services
.sending
.convert_to_outgoing_federation_event(knock_event.clone())
.federation
.format_pdu_into(knock_event.clone(), Some(&room_version_id))
.await,
};
@@ -494,7 +400,6 @@ async fn knock_room_helper_remote(
info!("send_knock finished");
services
.rooms
.short
.get_or_create_shortroomid(room_id)
.await;
@@ -507,7 +412,14 @@ async fn knock_room_helper_remote(
let state = send_knock_response
.knock_room_state
.iter()
.map(|event| serde_json::from_str::<CanonicalJsonObject>(event.clone().into_json().get()))
.map(|event| {
serde_json::from_str::<CanonicalJsonObject>(
extract_variant!(event.clone(), RawStrippedState::Stripped)
.expect("Raw<AnyStrippedStateEvent>")
.json()
.get(),
)
})
.filter_map(Result::ok);
let mut state_map: HashMap<u64, OwnedEventId> = HashMap::new();
@@ -534,13 +446,11 @@ async fn knock_room_helper_remote(
let event_id = gen_event_id(&event, &room_version_id)?;
let shortstatekey = services
.rooms
.short
.get_or_create_shortstatekey(&event_type, &state_key)
.await;
services
.rooms
.timeline
.add_pdu_outlier(&event_id, &event);
@@ -549,7 +459,6 @@ async fn knock_room_helper_remote(
info!("Compressing state from send_knock");
let compressed: CompressedState = services
.rooms
.state_compressor
.compress_state_events(
state_map
@@ -565,27 +474,23 @@ async fn knock_room_helper_remote(
added,
removed,
} = services
.rooms
.state_compressor
.save_state(room_id, Arc::new(compressed))
.await?;
debug!("Forcing state for new room");
services
.rooms
.state
.force_state(room_id, statehash_before_knock, added, removed, &state_lock)
.await?;
let statehash_after_knock = services
.rooms
.state
.append_to_state(&parsed_knock_pdu)
.await?;
info!("Updating membership locally to knock state with provided stripped state events");
services
.rooms
.state_cache
.update_membership(
room_id,
@@ -594,7 +499,13 @@ async fn knock_room_helper_remote(
.get_content::<RoomMemberEventContent>()
.expect("we just created this"),
sender_user,
Some(send_knock_response.knock_room_state),
Some(
send_knock_response
.knock_room_state
.into_iter()
.filter_map(|s| extract_variant!(s, RawStrippedState::Stripped))
.collect(),
),
None,
false,
)
@@ -602,7 +513,6 @@ async fn knock_room_helper_remote(
info!("Appending room knock event locally");
services
.rooms
.timeline
.append_pdu(
&parsed_knock_pdu,
@@ -616,7 +526,6 @@ async fn knock_room_helper_remote(
// We set the room state after inserting the pdu, so that we never have a moment
// in time where events in the current room state do not exist
services
.rooms
.state
.set_room_state(room_id, statehash_after_knock, &state_lock);
@@ -628,7 +537,7 @@ async fn make_knock_request(
sender_user: &UserId,
room_id: &RoomId,
servers: &[OwnedServerName],
) -> Result<(federation::knock::create_knock_event_template::v1::Response, OwnedServerName)> {
) -> Result<(federation::membership::prepare_knock_event::v1::Response, OwnedServerName)> {
let mut make_knock_response_and_server =
Err!(BadServerResponse("No server available to assist in knocking."));
@@ -645,7 +554,7 @@ async fn make_knock_request(
.sending
.send_federation_request(
remote_server,
federation::knock::create_knock_event_template::v1::Request {
federation::membership::prepare_knock_event::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: services
+17 -376
View File
@@ -1,25 +1,7 @@
use std::collections::HashSet;
use axum::extract::State;
use futures::{FutureExt, StreamExt, TryFutureExt, pin_mut};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, RoomVersionId, UserId,
api::{
client::membership::leave_room,
federation::{self},
},
events::{
StateEventType,
room::member::{MembershipState, RoomMemberEventContent},
},
};
use tuwunel_core::{
Err, Result, debug_info, debug_warn, err,
matrix::{event::gen_event_id, pdu::PduBuilder},
utils::{self, FutureBoolExt, future::ReadyEqExt},
warn,
};
use tuwunel_service::Services;
use futures::FutureExt;
use ruma::api::client::membership::leave_room;
use tuwunel_core::Result;
use crate::Ruma;
@@ -32,362 +14,21 @@ pub(crate) async fn leave_room_route(
State(services): State<crate::State>,
body: Ruma<leave_room::v3::Request>,
) -> Result<leave_room::v3::Response> {
leave_room(&services, body.sender_user(), &body.room_id, body.reason.clone())
let state_lock = services.state.mutex.lock(&body.room_id).await;
services
.membership
.leave(body.sender_user(), &body.room_id, body.reason.clone(), &state_lock)
.boxed()
.await
.map(|()| leave_room::v3::Response::new())
}
// Make a user leave all their joined rooms, rescinds knocks, forgets all rooms,
// and ignores errors
pub async fn leave_all_rooms(services: &Services, user_id: &UserId) {
let rooms_joined = services
.rooms
.state_cache
.rooms_joined(user_id)
.map(ToOwned::to_owned);
let rooms_invited = services
.rooms
.state_cache
.rooms_invited(user_id)
.map(|(r, _)| r);
let rooms_knocked = services
.rooms
.state_cache
.rooms_knocked(user_id)
.map(|(r, _)| r);
let all_rooms: Vec<_> = rooms_joined
.chain(rooms_invited)
.chain(rooms_knocked)
.collect()
.await;
for room_id in all_rooms {
// ignore errors
if let Err(e) = leave_room(services, user_id, &room_id, None)
.boxed()
.await
{
warn!(%user_id, "Failed to leave {room_id} remotely: {e}");
}
services
.rooms
.state_cache
.forget(&room_id, user_id);
}
}
pub async fn leave_room(
services: &Services,
user_id: &UserId,
room_id: &RoomId,
reason: Option<String>,
) -> Result {
let default_member_content = RoomMemberEventContent {
membership: MembershipState::Leave,
reason: reason.clone(),
join_authorized_via_users_server: None,
is_direct: None,
avatar_url: None,
displayname: None,
third_party_invite: None,
blurhash: None,
};
let is_banned = services.rooms.metadata.is_banned(room_id);
let is_disabled = services.rooms.metadata.is_disabled(room_id);
pin_mut!(is_banned, is_disabled);
if is_banned.or(is_disabled).await {
// the room is banned/disabled, the room must be rejected locally since we
// cant/dont want to federate with this server
services
.rooms
.state_cache
.update_membership(
room_id,
user_id,
default_member_content,
user_id,
None,
None,
true,
)
.await?;
return Ok(());
}
let dont_have_room = services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.eq(&false);
let not_knocked = services
.rooms
.state_cache
.is_knocked(user_id, room_id)
.eq(&false);
// Ask a remote server if we don't have this room and are not knocking on it
if dont_have_room.and(not_knocked).await {
if let Err(e) = remote_leave_room(services, user_id, room_id)
.boxed()
.await
{
warn!(%user_id, "Failed to leave room {room_id} remotely: {e}");
// Don't tell the client about this error
}
let last_state = services
.rooms
.state_cache
.invite_state(user_id, room_id)
.or_else(|_| {
services
.rooms
.state_cache
.knock_state(user_id, room_id)
})
.or_else(|_| {
services
.rooms
.state_cache
.left_state(user_id, room_id)
})
.await
.ok();
// We always drop the invite, we can't rely on other servers
services
.rooms
.state_cache
.update_membership(
room_id,
user_id,
default_member_content,
user_id,
last_state,
None,
true,
)
.await?;
} else {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let Ok(event) = services
.rooms
.state_accessor
.room_state_get_content::<RoomMemberEventContent>(
room_id,
&StateEventType::RoomMember,
user_id.as_str(),
)
.await
else {
debug_warn!(
"Trying to leave a room you are not a member of, marking room as left locally."
);
return services
.rooms
.state_cache
.update_membership(
room_id,
user_id,
default_member_content,
user_id,
None,
None,
true,
)
.await;
};
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Leave,
reason,
join_authorized_via_users_server: None,
is_direct: None,
..event
}),
user_id,
room_id,
&state_lock,
)
.await?;
}
Ok(())
}
async fn remote_leave_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result {
let mut make_leave_response_and_server =
Err!(BadServerResponse("No remote server available to assist in leaving {room_id}."));
let mut servers: HashSet<OwnedServerName> = services
.rooms
.state_cache
.servers_invite_via(room_id)
.map(ToOwned::to_owned)
.collect()
.await;
match services
.rooms
.state_cache
.invite_state(user_id, room_id)
.await
{
| Ok(invite_state) => {
servers.extend(
invite_state
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
},
| _ => {
match services
.rooms
.state_cache
.knock_state(user_id, room_id)
.await
{
| Ok(knock_state) => {
servers.extend(
knock_state
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.filter_map(|sender| {
if !services.globals.user_is_local(sender) {
Some(sender.server_name().to_owned())
} else {
None
}
}),
);
},
| _ => {},
}
},
}
if let Some(room_id_server_name) = room_id.server_name() {
servers.insert(room_id_server_name.to_owned());
}
debug_info!("servers in remote_leave_room: {servers:?}");
for remote_server in servers {
let make_leave_response = services
.sending
.send_federation_request(
&remote_server,
federation::membership::prepare_leave_event::v1::Request {
room_id: room_id.to_owned(),
user_id: user_id.to_owned(),
},
)
.await;
make_leave_response_and_server = make_leave_response.map(|r| (r, remote_server));
if make_leave_response_and_server.is_ok() {
break;
}
}
let (make_leave_response, remote_server) = make_leave_response_and_server?;
let Some(room_version_id) = make_leave_response.room_version else {
return Err!(BadServerResponse(warn!(
"No room version was returned by {remote_server} for {room_id}, room version is \
likely not supported by tuwunel"
)));
};
if !services
.server
.supported_room_version(&room_version_id)
{
return Err!(BadServerResponse(warn!(
"Remote room version {room_version_id} for {room_id} is not supported by conduwuit",
)));
}
let mut leave_event_stub = serde_json::from_str::<CanonicalJsonObject>(
make_leave_response.event.get(),
)
.map_err(|e| {
err!(BadServerResponse(warn!(
"Invalid make_leave event json received from {remote_server} for {room_id}: {e:?}"
)))
})?;
// TODO: Is origin needed?
leave_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
leave_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
// room v3 and above removed the "event_id" field from remote PDU format
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
leave_event_stub.remove("event_id");
},
}
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut leave_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&leave_event_stub, &room_version_id)?;
// Add event_id back
leave_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let leave_event = leave_event_stub;
services
.sending
.send_federation_request(
&remote_server,
federation::membership::create_leave_event::v2::Request {
room_id: room_id.to_owned(),
event_id,
pdu: services
.sending
.convert_to_outgoing_federation_event(leave_event.clone())
.await,
},
)
.await?;
Ok(())
if services.config.delete_rooms_after_leave {
services
.delete
.delete_if_empty_local(&body.room_id, state_lock)
.boxed()
.await;
}
Ok(leave_room::v3::Response {})
}
+22 -28
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use futures::{FutureExt, StreamExt, future::join};
use futures::{FutureExt, StreamExt};
use ruma::{
api::client::membership::{
get_member_events::{self, v3::MembershipEventFilter},
@@ -10,14 +10,7 @@
room::member::{MembershipState, RoomMemberEventContent},
},
};
use tuwunel_core::{
Err, Result, at,
matrix::Event,
utils::{
future::TryExtExt,
stream::{BroadbandExt, ReadyExt},
},
};
use tuwunel_core::{Err, Result, at, matrix::Event, utils::stream::ReadyExt};
use crate::Ruma;
@@ -31,22 +24,18 @@ pub(crate) async fn get_member_events_route(
State(services): State<crate::State>,
body: Ruma<get_member_events::v3::Request>,
) -> Result<get_member_events::v3::Response> {
let sender_user = body.sender_user();
let membership = body.membership.as_ref();
let not_membership = body.not_membership.as_ref();
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)
.user_can_see_state_events(body.sender_user(), &body.room_id)
.await
{
return Err!(Request(Forbidden("You don't have permission to view this room.")));
}
let membership = body.membership.as_ref();
let not_membership = body.not_membership.as_ref();
Ok(get_member_events::v3::Response {
chunk: services
.rooms
.state_accessor
.room_state_full(&body.room_id)
.ready_filter_map(Result::ok)
@@ -71,7 +60,6 @@ pub(crate) async fn joined_members_route(
body: Ruma<joined_members::v3::Request>,
) -> Result<joined_members::v3::Response> {
if !services
.rooms
.state_accessor
.user_can_see_state_events(body.sender_user(), &body.room_id)
.await
@@ -81,20 +69,26 @@ pub(crate) async fn joined_members_route(
Ok(joined_members::v3::Response {
joined: services
.rooms
.state_cache
.room_members(&body.room_id)
.map(ToOwned::to_owned)
.broad_then(async |user_id| {
let (display_name, avatar_url) = join(
services.users.displayname(&user_id).ok(),
services.users.avatar_url(&user_id).ok(),
)
.await;
.state_accessor
.room_state_full(&body.room_id)
.ready_filter_map(Result::ok)
.ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember)
.map(at!(1))
.ready_filter_map(|pdu| {
membership_filter(pdu, Some(&MembershipEventFilter::Join), None)
})
.ready_filter_map(|pdu| {
let content = pdu.get_content::<RoomMemberEventContent>().ok()?;
let sender = pdu.sender().to_owned();
let member = RoomMember {
display_name: content.displayname,
avatar_url: content.avatar_url,
};
(user_id, RoomMember { display_name, avatar_url })
Some((sender, member))
})
.collect()
.boxed()
.await,
})
}
+144 -82
View File
@@ -8,18 +8,21 @@
mod members;
mod unban;
use std::net::IpAddr;
use std::{cmp::Ordering, net::IpAddr};
use axum::extract::State;
use futures::{FutureExt, StreamExt};
use ruma::{OwnedRoomId, RoomId, ServerName, UserId, api::client::membership::joined_rooms};
use tuwunel_core::{Err, Result, warn};
use ruma::{
OwnedRoomId, OwnedServerName, RoomId, RoomOrAliasId, ServerName, UserId,
api::client::membership::joined_rooms,
};
use tuwunel_core::{Err, Result, result::LogErr, utils::shuffle, warn};
use tuwunel_service::Services;
pub(crate) use self::{
ban::ban_user_route,
forget::forget_room_route,
invite::{invite_helper, invite_user_route},
invite::invite_user_route,
join::{join_room_by_id_or_alias_route, join_room_by_id_route},
kick::kick_user_route,
knock::knock_room_route,
@@ -27,11 +30,7 @@
members::{get_member_events_route, joined_members_route},
unban::unban_user_route,
};
pub use self::{
join::join_room_by_id_helper,
leave::{leave_all_rooms, leave_room},
};
use crate::{Ruma, client::full_user_deactivate};
use crate::Ruma;
/// # `POST /_matrix/client/r0/joined_rooms`
///
@@ -42,7 +41,6 @@ pub(crate) async fn joined_rooms_route(
) -> Result<joined_rooms::v3::Response> {
Ok(joined_rooms::v3::Response {
joined_rooms: services
.rooms
.state_cache
.rooms_joined(body.sender_user())
.map(ToOwned::to_owned)
@@ -68,53 +66,28 @@ pub(crate) async fn banned_room_check(
return Ok(());
}
// TODO: weird condition
if let Some(room_id) = room_id {
if services.rooms.metadata.is_banned(room_id).await
|| services
.config
.forbidden_remote_server_names
.is_match(
room_id
.server_name()
.expect("legacy room mxid")
.host(),
) {
if services.metadata.is_banned(room_id).await
|| (room_id.server_name().is_some()
&& services
.config
.forbidden_remote_server_names
.is_match(
room_id
.server_name()
.expect("legacy room mxid")
.host(),
)) {
warn!(
"User {user_id} who is not an admin attempted to send an invite for or \
attempted to join a banned room or banned room server name: {room_id}"
);
if services
.server
.config
.auto_deactivate_banned_room_attempts
{
warn!(
"Automatically deactivating user {user_id} due to attempted banned room join"
);
if services.server.config.admin_room_notices {
services
.admin
.send_text(&format!(
"Automatically deactivating user {user_id} due to attempted banned \
room join from IP {client_ip}"
))
.await;
}
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(user_id)
.map(Into::into)
.collect()
.await;
full_user_deactivate(services, user_id, &all_joined_rooms)
.boxed()
.await?;
}
maybe_deactivate(services, user_id, client_ip)
.await
.log_err()
.ok();
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
}
@@ -129,37 +102,10 @@ pub(crate) async fn banned_room_check(
name {server_name} that is globally forbidden. Rejecting.",
);
if services
.server
.config
.auto_deactivate_banned_room_attempts
{
warn!(
"Automatically deactivating user {user_id} due to attempted banned room join"
);
if services.server.config.admin_room_notices {
services
.admin
.send_text(&format!(
"Automatically deactivating user {user_id} due to attempted banned \
room join from IP {client_ip}"
))
.await;
}
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(user_id)
.map(Into::into)
.collect()
.await;
full_user_deactivate(services, user_id, &all_joined_rooms)
.boxed()
.await?;
}
maybe_deactivate(services, user_id, client_ip)
.await
.log_err()
.ok();
return Err!(Request(Forbidden("This remote server is banned on this homeserver.")));
}
@@ -167,3 +113,119 @@ pub(crate) async fn banned_room_check(
Ok(())
}
async fn maybe_deactivate(services: &Services, user_id: &UserId, client_ip: IpAddr) -> Result {
if services
.server
.config
.auto_deactivate_banned_room_attempts
{
warn!("Automatically deactivating user {user_id} due to attempted banned room join");
if services.server.config.admin_room_notices {
services
.admin
.send_text(&format!(
"Automatically deactivating user {user_id} due to attempted banned room \
join from IP {client_ip}"
))
.await;
}
services
.deactivate
.full_deactivate(user_id)
.boxed()
.await?;
}
Ok(())
}
// TODO: should this be in services? banned check would have to resolve again if
// room_id is not available at callsite
async fn get_join_params(
services: &Services,
user_id: &UserId,
room_id_or_alias: &RoomOrAliasId,
via: &[OwnedServerName],
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
// servers tried first, additional_servers shuffled then tried after
let (room_id, mut servers, mut additional_servers) =
match OwnedRoomId::try_from(room_id_or_alias.to_owned()) {
// if room id, shuffle via + room_id server_name ...
| Ok(room_id) => {
let mut additional_servers = via.to_vec();
if let Some(server) = room_id.server_name() {
additional_servers.push(server.to_owned());
}
(room_id, Vec::new(), additional_servers)
},
// ... if room alias, resolve and don't shuffle ...
| Err(room_alias) => {
let (room_id, servers) = services
.alias
.resolve_alias(&room_alias, Some(via.to_vec()))
.await?;
(room_id, servers, Vec::new())
},
};
// either way, add invited vias
additional_servers.extend(
services
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
// either way, add invite senders' servers
additional_servers.extend(
services
.state_cache
.invite_state(user_id, &room_id)
.await
.unwrap_or_default()
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
// shuffle additionals, append to base servers
additional_servers.sort_unstable();
additional_servers.dedup();
shuffle(&mut additional_servers);
servers.sort_unstable();
servers.dedup();
servers.append(&mut additional_servers);
// sort deprioritized servers last
servers.sort_by(|a, b| {
let a_matches = services
.server
.config
.deprioritize_joins_through_servers
.is_match(a.host());
let b_matches = services
.server
.config
.deprioritize_joins_through_servers
.is_match(b.host());
if a_matches && !b_matches {
Ordering::Greater
} else if !a_matches && b_matches {
Ordering::Less
} else {
Ordering::Equal
}
});
Ok((room_id, servers))
}
+10 -37
View File
@@ -1,9 +1,7 @@
use axum::extract::State;
use ruma::{
api::client::membership::unban_user,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use tuwunel_core::{Err, Result, matrix::pdu::PduBuilder};
use futures::FutureExt;
use ruma::api::client::membership::unban_user;
use tuwunel_core::Result;
use crate::Ruma;
@@ -14,43 +12,18 @@ pub(crate) async fn unban_user_route(
State(services): State<crate::State>,
body: Ruma<unban_user::v3::Request>,
) -> Result<unban_user::v3::Response> {
let state_lock = services
.rooms
.state
.mutex
.lock(&body.room_id)
.await;
let current_member_content = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
.unwrap_or_else(|_| RoomMemberEventContent::new(MembershipState::Leave));
if current_member_content.membership != MembershipState::Ban {
return Err!(Request(Forbidden(
"Cannot unban a user who is not banned (current membership: {})",
current_member_content.membership
)));
}
let state_lock = services.state.mutex.lock(&body.room_id).await;
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Leave,
reason: body.reason.clone(),
join_authorized_via_users_server: None,
third_party_invite: None,
is_direct: None,
..current_member_content
}),
body.sender_user(),
.membership
.unban(
&body.room_id,
&body.user_id,
body.reason.as_ref(),
body.sender_user(),
&state_lock,
)
.boxed()
.await?;
drop(state_lock);
+2 -8
View File
@@ -73,7 +73,7 @@ pub(crate) async fn get_message_events_route(
let room_id = &body.room_id;
let filter = &body.filter;
if !services.rooms.metadata.exists(room_id).await {
if !services.metadata.exists(room_id).await {
return Err!(Request(Forbidden("Room does not exist to this server")));
}
@@ -97,7 +97,6 @@ pub(crate) async fn get_message_events_route(
if matches!(body.dir, Direction::Backward) {
services
.rooms
.timeline
.backfill_if_required(room_id, from)
.await
@@ -107,14 +106,12 @@ pub(crate) async fn get_message_events_route(
let it = match body.dir {
| Direction::Forward => services
.rooms
.timeline
.pdus(Some(sender_user), room_id, Some(from))
.ignore_err()
.boxed(),
| Direction::Backward => services
.rooms
.timeline
.pdus_rev(Some(sender_user), room_id, Some(from))
.ignore_err()
@@ -192,7 +189,7 @@ pub(crate) async fn lazy_loading_witness<'a, I>(
.max()
.unwrap_or_else(PduCount::max);
let receipts = services.rooms.read_receipt.readreceipts_since(
let receipts = services.read_receipt.readreceipts_since(
lazy_loading_context.room_id,
oldest.into_unsigned(),
Some(newest.into_unsigned()),
@@ -213,7 +210,6 @@ pub(crate) async fn lazy_loading_witness<'a, I>(
.await;
services
.rooms
.lazy_loading
.witness_retain(witness, lazy_loading_context)
.await
@@ -225,7 +221,6 @@ async fn get_member_event(
user_id: &UserId,
) -> Option<Raw<AnyStateEvent>> {
services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())
.map_ok(Event::into_format)
@@ -293,7 +288,6 @@ pub(crate) async fn visibility_filter(
let (_, pdu) = &item;
services
.rooms
.state_accessor
.user_can_see_event(user_id, pdu.room_id(), pdu.event_id())
.await
+2 -3
View File
@@ -40,7 +40,8 @@
pub(super) mod voip;
pub(super) mod well_known;
pub use account::full_user_deactivate;
mod utils;
pub(super) use account::*;
pub(super) use account_data::*;
pub(super) use alias::*;
@@ -55,12 +56,10 @@
pub(super) use media::*;
pub(super) use media_legacy::*;
pub(super) use membership::*;
pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room};
pub(super) use message::*;
pub(super) use openid::*;
pub(super) use presence::*;
pub(super) use profile::*;
pub use profile::{update_all_rooms, update_avatar_url, update_displayname};
pub(super) use push::*;
pub(super) use read_marker::*;
pub(super) use redact::*;
-1
View File
@@ -44,7 +44,6 @@ pub(crate) async fn get_presence_route(
let mut presence_event = None;
let has_shared_rooms = services
.rooms
.state_cache
.user_sees_user(body.sender_user(), &body.user_id)
.await;
+44 -156
View File
@@ -2,27 +2,20 @@
use axum::extract::State;
use futures::{
StreamExt, TryStreamExt,
future::{join, join3, join4},
StreamExt,
future::{join, join4},
};
use ruma::{
OwnedMxcUri, OwnedRoomId, UserId,
OwnedRoomId,
api::{
client::profile::{
get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name,
},
federation,
},
events::room::member::{MembershipState, RoomMemberEventContent},
presence::PresenceState,
};
use tuwunel_core::{
Err, Result,
matrix::pdu::PduBuilder,
utils::{IterStream, future::TryExtExt, stream::TryIgnore},
warn,
};
use tuwunel_service::Services;
use tuwunel_core::{Err, Result, utils::future::TryExtExt};
use crate::Ruma;
@@ -42,14 +35,15 @@ pub(crate) async fn set_displayname_route(
}
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(&body.user_id)
.map(ToOwned::to_owned)
.collect()
.await;
update_displayname(&services, &body.user_id, body.displayname.clone(), &all_joined_rooms)
services
.users
.update_displayname(&body.user_id, body.displayname.clone(), &all_joined_rooms)
.await;
if services.config.allow_local_presence {
@@ -138,21 +132,21 @@ pub(crate) async fn set_avatar_url_route(
}
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(&body.user_id)
.map(ToOwned::to_owned)
.collect()
.await;
update_avatar_url(
&services,
&body.user_id,
body.avatar_url.clone(),
body.blurhash.clone(),
&all_joined_rooms,
)
.await;
services
.users
.update_avatar_url(
&body.user_id,
body.avatar_url.clone(),
body.blurhash.clone(),
&all_joined_rooms,
)
.await;
if services.config.allow_local_presence {
// Presence update
@@ -279,13 +273,20 @@ pub(crate) async fn get_profile_route(
);
}
return Ok(get_profile::v3::Response {
displayname: response.displayname,
avatar_url: response.avatar_url,
blurhash: response.blurhash,
tz: response.tz,
custom_profile_fields: response.custom_profile_fields,
});
let canonical_fields = [
("avatar_url", response.avatar_url.map(Into::into)),
("blurhash", response.blurhash),
("displayname", response.displayname),
("tz", response.tz),
];
let response = canonical_fields
.into_iter()
.filter_map(|(key, val)| val.map(|val| (key, val)))
.map(|(key, val)| (key.to_owned(), val.into()))
.chain(response.custom_profile_fields.into_iter());
return Ok(response.collect::<get_profile::v3::Response>());
}
}
@@ -313,131 +314,18 @@ pub(crate) async fn get_profile_route(
)
.await;
Ok(get_profile::v3::Response {
avatar_url,
blurhash,
displayname,
tz,
custom_profile_fields,
})
}
pub async fn update_displayname(
services: &Services,
user_id: &UserId,
displayname: Option<String>,
all_joined_rooms: &[OwnedRoomId],
) {
let (current_avatar_url, current_blurhash, current_displayname) = join3(
services.users.avatar_url(user_id).ok(),
services.users.blurhash(user_id).ok(),
services.users.displayname(user_id).ok(),
)
.await;
if displayname == current_displayname {
return;
}
services
.users
.set_displayname(user_id, displayname.clone());
// Send a new join membership event into all joined rooms
let avatar_url = &current_avatar_url;
let blurhash = &current_blurhash;
let displayname = &displayname;
let all_joined_rooms: Vec<_> = all_joined_rooms
.iter()
.try_stream()
.and_then(async |room_id: &OwnedRoomId| {
let pdu = PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
displayname: displayname.clone(),
membership: MembershipState::Join,
avatar_url: avatar_url.clone(),
blurhash: blurhash.clone(),
join_authorized_via_users_server: None,
reason: None,
is_direct: None,
third_party_invite: None,
});
Ok((pdu, room_id))
})
.ignore_err()
.collect()
.await;
update_all_rooms(services, all_joined_rooms, user_id).await;
}
pub async fn update_avatar_url(
services: &Services,
user_id: &UserId,
avatar_url: Option<OwnedMxcUri>,
blurhash: Option<String>,
all_joined_rooms: &[OwnedRoomId],
) {
let (current_avatar_url, current_blurhash, current_displayname) = join3(
services.users.avatar_url(user_id).ok(),
services.users.blurhash(user_id).ok(),
services.users.displayname(user_id).ok(),
)
.await;
if current_avatar_url == avatar_url && current_blurhash == blurhash {
return;
}
services
.users
.set_avatar_url(user_id, avatar_url.clone());
services
.users
.set_blurhash(user_id, blurhash.clone());
// Send a new join membership event into all joined rooms
let avatar_url = &avatar_url;
let blurhash = &blurhash;
let displayname = &current_displayname;
let all_joined_rooms: Vec<_> = all_joined_rooms
.iter()
.try_stream()
.and_then(async |room_id: &OwnedRoomId| {
let pdu = PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
avatar_url: avatar_url.clone(),
blurhash: blurhash.clone(),
membership: MembershipState::Join,
displayname: displayname.clone(),
join_authorized_via_users_server: None,
reason: None,
is_direct: None,
third_party_invite: None,
});
Ok((pdu, room_id))
})
.ignore_err()
.collect()
.await;
update_all_rooms(services, all_joined_rooms, user_id).await;
}
pub async fn update_all_rooms(
services: &Services,
all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>,
user_id: &UserId,
) {
for (pdu_builder, room_id) in all_joined_rooms {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
if let Err(e) = services
.rooms
.timeline
.build_and_append_pdu(pdu_builder, user_id, room_id, &state_lock)
.await
{
warn!(%user_id, %room_id, "Failed to update/send new profile join membership update in room: {e}");
}
}
let canonical_fields = [
("avatar_url", avatar_url.map(Into::into)),
("blurhash", blurhash),
("displayname", displayname),
("tz", tz),
];
let response = canonical_fields
.into_iter()
.filter_map(|(key, val)| val.map(|val| (key, val)))
.map(|(key, val)| (key.to_owned(), val.into()))
.chain(custom_profile_fields.into_iter());
Ok(response.collect::<get_profile::v3::Response>())
}
-8
View File
@@ -44,7 +44,6 @@ pub(crate) async fn set_read_marker_route(
if body.private_read_receipt.is_some() || body.read_receipt.is_some() {
services
.rooms
.user
.reset_notification_counts(sender_user, &body.room_id);
}
@@ -70,7 +69,6 @@ pub(crate) async fn set_read_marker_route(
)]);
services
.rooms
.read_receipt
.readreceipt_update(
sender_user,
@@ -85,7 +83,6 @@ pub(crate) async fn set_read_marker_route(
if let Some(event) = &body.private_read_receipt {
let count = services
.rooms
.timeline
.get_pdu_count(event)
.await
@@ -98,7 +95,6 @@ pub(crate) async fn set_read_marker_route(
};
services
.rooms
.read_receipt
.private_read_set(&body.room_id, sender_user, count);
}
@@ -120,7 +116,6 @@ pub(crate) async fn create_receipt_route(
create_receipt::v3::ReceiptType::Read | create_receipt::v3::ReceiptType::ReadPrivate
) {
services
.rooms
.user
.reset_notification_counts(sender_user, &body.room_id);
}
@@ -166,7 +161,6 @@ pub(crate) async fn create_receipt_route(
)]);
services
.rooms
.read_receipt
.readreceipt_update(
sender_user,
@@ -180,7 +174,6 @@ pub(crate) async fn create_receipt_route(
},
| create_receipt::v3::ReceiptType::ReadPrivate => {
let count = services
.rooms
.timeline
.get_pdu_count(&body.event_id)
.await
@@ -193,7 +186,6 @@ pub(crate) async fn create_receipt_route(
};
services
.rooms
.read_receipt
.private_read_set(&body.room_id, sender_user, count);
},
+1 -7
View File
@@ -18,15 +18,9 @@ pub(crate) async fn redact_event_route(
let sender_user = body.sender_user();
let body = &body.body;
let state_lock = services
.rooms
.state
.mutex
.lock(&body.room_id)
.await;
let state_lock = services.state.mutex.lock(&body.room_id).await;
let event_id = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
+25 -18
View File
@@ -19,7 +19,7 @@
use tuwunel_core::{Err, Error, Result, debug_info, error, info, is_equal_to, utils, warn};
use tuwunel_service::users::device::generate_refresh_token;
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, join_room_by_id_helper};
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH};
use crate::Ruma;
const RANDOM_USER_ID_LENGTH: usize = 10;
@@ -316,7 +316,7 @@ pub(crate) async fn register_route(
stages: vec![AuthType::RegistrationToken],
}],
completed: Vec::new(),
params: Box::default(),
params: Default::default(),
session: None,
auth_error: None,
};
@@ -326,7 +326,7 @@ pub(crate) async fn register_route(
uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
completed: Vec::new(),
params: Box::default(),
params: Default::default(),
session: None,
auth_error: None,
};
@@ -517,13 +517,16 @@ pub(crate) async fn register_route(
&& services.config.grant_admin_to_first_user
&& let Ok(admin_room) = services.admin.get_admin_room().await
&& services
.rooms
.state_cache
.room_joined_count(&admin_room)
.await
.is_ok_and(is_equal_to!(1))
{
services.admin.make_user_admin(&user_id).await?;
services
.admin
.make_user_admin(&user_id)
.boxed()
.await?;
warn!("Granting {user_id} admin privileges as the first user");
}
@@ -532,7 +535,7 @@ pub(crate) async fn register_route(
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
{
for room in &services.server.config.auto_join_rooms {
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
let Ok(room_id) = services.alias.resolve(room).await else {
error!(
"Failed to resolve room alias to room ID when attempting to auto join \
{room}, skipping"
@@ -541,7 +544,6 @@ pub(crate) async fn register_route(
};
if !services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), &room_id)
.await
@@ -553,17 +555,20 @@ pub(crate) async fn register_route(
}
if let Some(room_server_name) = room.server_name() {
match join_room_by_id_helper(
&services,
&user_id,
&room_id,
Some("Automatically joining this room upon registration".to_owned()),
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
None,
&body.appservice_info,
)
.boxed()
.await
let state_lock = services.state.mutex.lock(&room_id).await;
match services
.membership
.join(
&user_id,
&room_id,
Some("Automatically joining this room upon registration".to_owned()),
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
&body.appservice_info,
&state_lock,
)
.boxed()
.await
{
| Err(e) => {
// don't return this error so we don't fail registrations
@@ -575,6 +580,8 @@ pub(crate) async fn register_route(
info!("Automatically joined room {room} for user {user_id}");
},
}
drop(state_lock);
}
}
}
-2
View File
@@ -133,7 +133,6 @@ async fn paginate_relations_with_filter(
let depth: u8 = if recurse { 3 } else { 1 };
let events: Vec<_> = services
.rooms
.pdu_metadata
.get_relations(sender_user, room_id, target, start, limit, depth, dir)
.await
@@ -183,7 +182,6 @@ async fn visibility_filter<Pdu: Event>(
let (_, pdu) = &item;
services
.rooms
.state_accessor
.user_can_see_event(sender_user, pdu.room_id(), pdu.event_id())
.await
+9 -19
View File
@@ -15,6 +15,8 @@
use crate::Ruma;
const REASON_MAX_LEN: usize = 750;
/// # `POST /_matrix/client/v3/rooms/{roomId}/report`
///
/// Reports an abusive room to homeserver admins
@@ -29,24 +31,18 @@ pub(crate) async fn report_room_route(
info!(
"Received room report by user {sender_user} for room {} with reason: \"{}\"",
body.room_id,
body.reason.as_deref().unwrap_or("")
body.room_id, body.reason,
);
if body
.reason
.as_ref()
.is_some_and(|s| s.len() > 750)
{
return Err!(Request(
InvalidParam("Reason too long, should be 750 characters or fewer",)
));
if body.reason.len().gt(&REASON_MAX_LEN) {
return Err!(Request(InvalidParam(
"Reason too long, should be {REASON_MAX_LEN} characters or fewer"
)));
}
delay_response().await;
if !services
.rooms
.state_cache
.server_in_room(&services.server.name, &body.room_id)
.await
@@ -64,7 +60,7 @@ pub(crate) async fn report_room_route(
"@room Room report received from {} -\n\nRoom ID: {}\n\nReport Reason: {}",
sender_user.to_owned(),
body.room_id,
body.reason.as_deref().unwrap_or("")
body.reason,
)))
.await
.ok();
@@ -95,12 +91,7 @@ pub(crate) async fn report_event_route(
delay_response().await;
// check if we know about the reported event ID or if it's invalid
let Ok(pdu) = services
.rooms
.timeline
.get_pdu(&body.event_id)
.await
else {
let Ok(pdu) = services.timeline.get_pdu(&body.event_id).await else {
return Err!(Request(NotFound("Event ID is not known to us or Event ID is invalid")));
};
@@ -170,7 +161,6 @@ async fn is_event_report_valid(
}
if !services
.rooms
.state_cache
.room_members(room_id)
.ready_any(|user_id| user_id == sender_user)
-2
View File
@@ -18,7 +18,6 @@ pub(crate) async fn get_room_aliases_route(
let sender_user = body.sender_user();
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)
.await
@@ -28,7 +27,6 @@ pub(crate) async fn get_room_aliases_route(
Ok(aliases::v3::Response {
aliases: services
.rooms
.alias
.local_aliases_for_room(&body.room_id)
.map(ToOwned::to_owned)
+363 -204
View File
@@ -1,10 +1,13 @@
use std::collections::BTreeMap;
use axum::extract::State;
use futures::FutureExt;
use futures::{FutureExt, future::OptionFuture};
use ruma::{
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId,
api::client::room::{self, create_room},
api::client::room::{
self, create_room,
create_room::v3::{CreationContent, RoomPreset},
},
events::{
TimelineEventType,
room::{
@@ -16,21 +19,23 @@
member::{MembershipState, RoomMemberEventContent},
name::RoomNameEventContent,
power_levels::RoomPowerLevelsEventContent,
topic::RoomTopicEventContent,
topic::{RoomTopicEventContent, TopicContentBlock},
},
},
int,
room_version_rules::{RoomIdFormatVersion, RoomVersionRules},
serde::{JsonObject, Raw},
};
use serde_json::{json, value::to_raw_value};
use tuwunel_core::{
Err, Result, debug_info, debug_warn, err, info,
matrix::{StateKey, pdu::PduBuilder},
matrix::{StateKey, pdu::PduBuilder, room_version},
utils::BoolExt,
warn,
};
use tuwunel_service::{Services, appservice::RegistrationInfo};
use tuwunel_service::{Services, appservice::RegistrationInfo, rooms::state::RoomMutexGuard};
use crate::{Ruma, client::invite_helper};
use crate::{Ruma, client::utils::invite_check};
/// # `POST /_matrix/client/v3/createRoom`
///
@@ -53,166 +58,62 @@ pub(crate) async fn create_room_route(
State(services): State<crate::State>,
body: Ruma<create_room::v3::Request>,
) -> Result<create_room::v3::Response> {
use create_room::v3::RoomPreset;
can_create_room_check(&services, &body).await?;
can_publish_directory_check(&services, &body).await?;
let sender_user = body.sender_user();
// Figure out preset. We need it for preset specific events
let preset = body
.preset
.clone()
.unwrap_or(match &body.visibility {
| room::Visibility::Public => RoomPreset::PublicChat,
| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
});
if !services.globals.allow_room_creation()
&& body.appservice_info.is_none()
&& !services.users.is_admin(sender_user).await
{
return Err!(Request(Forbidden("Room creation has been disabled.",)));
}
let alias: OptionFuture<_> = body
.room_alias_name
.as_ref()
.map(|alias| room_alias_check(&services, alias, body.appservice_info.as_ref()))
.into();
let room_id: OwnedRoomId = match &body.room_id {
| Some(custom_room_id) => custom_room_id_check(&services, custom_room_id)?,
| _ => RoomId::new(&services.server.name),
};
// check if room ID doesn't already exist instead of erroring on auth check
if services
.rooms
.short
.get_shortroomid(&room_id)
.await
.is_ok()
{
return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
}
if body.visibility == room::Visibility::Public
&& services
.server
.config
.lockdown_public_room_directory
&& !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none()
{
warn!(
"Non-admin user {sender_user} tried to publish {room_id} to the room directory \
while \"lockdown_public_room_directory\" is enabled"
);
if services.server.config.admin_room_notices {
// Determine room version
let (room_version, version_rules) = body
.room_version
.as_ref()
.map_or(Ok(&services.server.config.default_room_version), |version| {
services
.admin
.notice(&format!(
"Non-admin user {sender_user} tried to publish {room_id} to the room \
directory while \"lockdown_public_room_directory\" is enabled"
))
.await;
}
return Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")));
}
let _short_id = services
.rooms
.short
.get_or_create_shortroomid(&room_id)
.await;
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
let alias: Option<OwnedRoomAliasId> = match body.room_alias_name.as_ref() {
| Some(alias) =>
Some(room_alias_check(&services, alias, body.appservice_info.as_ref()).await?),
| _ => None,
};
let room_version = match body.room_version.clone() {
| Some(room_version) =>
if services
.server
.supported_room_version(&room_version)
{
room_version
} else {
return Err!(Request(UnsupportedRoomVersion(
"This server does not support that room version."
)));
},
| None => services
.server
.config
.default_room_version
.clone(),
};
.supported_room_version(version)
.then_ok_or_else(version, || {
err!(Request(UnsupportedRoomVersion(
"This server does not support room version {version:?}"
)))
})
})
.and_then(|version| Ok((version, room_version::rules(version)?)))?;
let create_content = match &body.creation_content {
| Some(content) => {
use RoomVersionId::*;
let mut content = content
.deserialize_as::<CanonicalJsonObject>()
.map_err(|e| {
err!(Request(BadJson(error!(
"Failed to deserialise content as canonical JSON: {e}"
))))
})?;
match room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
content.insert(
"creator".into(),
json!(&sender_user).try_into().map_err(|e| {
err!(Request(BadJson(debug_error!("Invalid creation content: {e}"))))
})?,
);
},
| _ => {
// V11+ removed the "creator" key
},
}
content.insert(
"room_version".into(),
json!(room_version.as_str())
.try_into()
.map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
);
content
},
| None => {
use RoomVersionId::*;
let content = match room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
RoomCreateEventContent::new_v1(sender_user.to_owned()),
| _ => RoomCreateEventContent::new_v11(),
};
let mut content =
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())
.unwrap();
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
content
},
};
// Error on existing alias before committing to creation.
let alias = alias.await.transpose()?;
// Increment and hold the counter; the room will sync atomically to clients
// which is preferable.
let next_count = services.globals.next_count();
// 1. The room create event
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_content)?,
state_key: Some(StateKey::new()),
..Default::default()
},
sender_user,
&room_id,
&state_lock,
)
.boxed()
.await?;
// 1. Create the create event.
let (room_id, state_lock) = match version_rules.room_id_format {
| RoomIdFormatVersion::V1 =>
create_create_event_legacy(&services, &body, room_version, &version_rules).await?,
| RoomIdFormatVersion::V2 =>
create_create_event(&services, &body, &preset, room_version, &version_rules)
.await
.map_err(|e| {
err!(Request(InvalidParam("Error while creating m.room.create event: {e}")))
})?,
};
// 2. Let the room creator join
let sender_user = body.sender_user();
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &RoomMemberEventContent {
@@ -230,17 +131,14 @@ pub(crate) async fn create_room_route(
.await?;
// 3. Power levels
// Figure out preset. We need it for preset specific events
let preset = body
.preset
.clone()
.unwrap_or(match &body.visibility {
| room::Visibility::Public => RoomPreset::PublicChat,
| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
});
let mut users = BTreeMap::from_iter([(sender_user.to_owned(), int!(100))]);
let mut users = if !version_rules
.authorization
.explicitly_privilege_room_creators
{
BTreeMap::from_iter([(sender_user.to_owned(), int!(100))])
} else {
BTreeMap::new()
};
if preset == RoomPreset::TrustedPrivateChat {
for invite in &body.invite {
@@ -260,18 +158,23 @@ pub(crate) async fn create_room_route(
continue;
}
users.insert(invite.clone(), int!(100));
if !version_rules
.authorization
.additional_room_creators
{
users.insert(invite.clone(), int!(100));
}
}
}
let power_levels_content = default_power_levels_content(
&version_rules,
body.power_level_content_override.as_ref(),
&body.visibility,
users,
)?;
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
@@ -290,7 +193,6 @@ pub(crate) async fn create_room_route(
// 4. Canonical room alias
if let Some(room_alias_id) = &alias {
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomCanonicalAliasEventContent {
@@ -309,7 +211,6 @@ pub(crate) async fn create_room_route(
// 5.1 Join Rules
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
@@ -329,7 +230,6 @@ pub(crate) async fn create_room_route(
// 5.2 History Visibility
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
@@ -345,7 +245,6 @@ pub(crate) async fn create_room_route(
// 5.3 Guest Access
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
@@ -365,7 +264,7 @@ pub(crate) async fn create_room_route(
// 6. Events listed in initial_state
for event in &body.initial_state {
let mut pdu_builder = event
.deserialize_as::<PduBuilder>()
.deserialize_as_unchecked::<PduBuilder>()
.map_err(|e| {
err!(Request(InvalidParam(warn!("Invalid initial state event: {e:?}"))))
})?;
@@ -394,7 +293,6 @@ pub(crate) async fn create_room_route(
}
services
.rooms
.timeline
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
.boxed()
@@ -404,7 +302,6 @@ pub(crate) async fn create_room_route(
// 7. Events implied by name and topic
if let Some(name) = &body.name {
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomNameEventContent::new(name.clone())),
@@ -418,10 +315,12 @@ pub(crate) async fn create_room_route(
if let Some(topic) = &body.topic {
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomTopicEventContent { topic: topic.clone() }),
PduBuilder::state(String::new(), &RoomTopicEventContent {
topic: topic.clone(),
topic_block: TopicContentBlock::default(),
}),
sender_user,
&room_id,
&state_lock,
@@ -433,43 +332,50 @@ pub(crate) async fn create_room_route(
drop(next_count);
drop(state_lock);
// 8. Events implied by invite (and TODO: invite_3pid)
for user_id in &body.invite {
if services
.users
.user_is_ignored(sender_user, user_id)
// if inviting anyone with room creation and invite check passes
if (!body.invite.is_empty() || !body.invite_3pid.is_empty())
&& invite_check(&services, sender_user, &room_id)
.await
{
continue;
} else if services
.users
.user_is_ignored(user_id, sender_user)
.await
{
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
continue;
}
.is_ok()
{
// 8. Events implied by invite (and TODO: invite_3pid)
for user_id in &body.invite {
if services
.users
.user_is_ignored(sender_user, user_id)
.await
{
continue;
} else if services
.users
.user_is_ignored(user_id, sender_user)
.await
{
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
continue;
}
if let Err(e) =
invite_helper(&services, sender_user, user_id, &room_id, None, body.is_direct)
if let Err(e) = services
.membership
.invite(sender_user, user_id, &room_id, None, body.is_direct)
.boxed()
.await
{
warn!(%e, "Failed to send invite");
{
warn!(%e, "Failed to send invite");
}
}
}
// Homeserver specific stuff
if let Some(alias) = alias {
services
.rooms
.alias
.set_alias(&alias, &room_id, sender_user)?;
}
if body.visibility == room::Visibility::Public {
services.rooms.directory.set_public(&room_id);
services.directory.set_public(&room_id);
if services.server.config.admin_room_notices {
services
@@ -488,25 +394,223 @@ pub(crate) async fn create_room_route(
Ok(create_room::v3::Response::new(room_id))
}
async fn create_create_event(
services: &Services,
body: &Ruma<create_room::v3::Request>,
preset: &RoomPreset,
room_version: &RoomVersionId,
version_rules: &RoomVersionRules,
) -> Result<(OwnedRoomId, RoomMutexGuard)> {
let _sender_user = body.sender_user();
let mut create_content = match &body.creation_content {
| Some(content) => {
let mut content = content
.deserialize_as_unchecked::<CanonicalJsonObject>()
.map_err(|e| {
err!(Request(BadJson(error!(
"Failed to deserialise content as canonical JSON: {e}"
))))
})?;
content.insert(
"room_version".into(),
json!(room_version.as_str())
.try_into()
.map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
);
content
},
| None => {
let content = RoomCreateEventContent::new_v11();
let mut content =
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
content
},
};
if version_rules
.authorization
.additional_room_creators
{
let mut additional_creators = body
.creation_content
.as_ref()
.and_then(|c| {
c.deserialize_as_unchecked::<CreationContent>()
.ok()
})
.unwrap_or_default()
.additional_creators;
if *preset == RoomPreset::TrustedPrivateChat {
additional_creators.extend(body.invite.clone());
}
additional_creators.sort();
additional_creators.dedup();
if !additional_creators.is_empty() {
create_content
.insert("additional_creators".into(), json!(additional_creators).try_into()?);
}
}
// 1. The room create event, using a placeholder room_id
let room_id = ruma::room_id!("!thiswillbereplaced").to_owned();
let state_lock = services.state.mutex.lock(&room_id).await;
let create_event_id = services
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_content)?,
state_key: Some(StateKey::new()),
..Default::default()
},
body.sender_user(),
&room_id,
&state_lock,
)
.boxed()
.await?;
drop(state_lock);
// The real room_id is now the event_id.
let room_id = OwnedRoomId::from_parts('!', create_event_id.localpart(), None)?;
let state_lock = services.state.mutex.lock(&room_id).await;
Ok((room_id, state_lock))
}
async fn create_create_event_legacy(
services: &Services,
body: &Ruma<create_room::v3::Request>,
room_version: &RoomVersionId,
_version_rules: &RoomVersionRules,
) -> Result<(OwnedRoomId, RoomMutexGuard)> {
let room_id: OwnedRoomId = match &body.room_id {
| None => RoomId::new_v1(&services.server.name),
| Some(custom_id) => custom_room_id_check(services, custom_id).await?,
};
let state_lock = services.state.mutex.lock(&room_id).await;
let _short_id = services
.short
.get_or_create_shortroomid(&room_id)
.await;
let create_content = match &body.creation_content {
| Some(content) => {
use RoomVersionId::*;
let mut content = content
.deserialize_as_unchecked::<CanonicalJsonObject>()
.map_err(|e| {
err!(Request(BadJson(error!(
"Failed to deserialise content as canonical JSON: {e}"
))))
})?;
match room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
content.insert(
"creator".into(),
json!(body.sender_user())
.try_into()
.map_err(|e| {
err!(Request(BadJson(debug_error!(
"Invalid creation content: {e}"
))))
})?,
);
},
| _ => {
// V11+ removed the "creator" key
},
}
content.insert(
"room_version".into(),
json!(room_version.as_str())
.try_into()
.map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
);
content
},
| None => {
use RoomVersionId::*;
let content = match room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
RoomCreateEventContent::new_v1(body.sender_user().to_owned()),
| _ => RoomCreateEventContent::new_v11(),
};
let mut content =
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
content
},
};
// 1. The room create event
services
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_content)?,
state_key: Some(StateKey::new()),
..Default::default()
},
body.sender_user(),
&room_id,
&state_lock,
)
.boxed()
.await?;
Ok((room_id, state_lock))
}
/// creates the power_levels_content for the PDU builder
fn default_power_levels_content(
version_rules: &RoomVersionRules,
power_level_content_override: Option<&Raw<RoomPowerLevelsEventContent>>,
visibility: &room::Visibility,
users: BTreeMap<OwnedUserId, Int>,
) -> Result<serde_json::Value> {
use serde_json::to_value;
let mut power_levels_content =
to_value(RoomPowerLevelsEventContent { users, ..Default::default() })?;
let mut power_levels_content = RoomPowerLevelsEventContent::new(&version_rules.authorization);
power_levels_content.users = users;
let mut power_levels_content = to_value(power_levels_content)?;
// secure proper defaults of sensitive/dangerous permissions that moderators
// (power level 50) should not have easy access to
power_levels_content["events"]["m.room.power_levels"] = to_value(100)?;
power_levels_content["events"]["m.room.server_acl"] = to_value(100)?;
power_levels_content["events"]["m.room.tombstone"] = to_value(100)?;
power_levels_content["events"]["m.room.encryption"] = to_value(100)?;
power_levels_content["events"]["m.room.history_visibility"] = to_value(100)?;
if version_rules
.authorization
.explicitly_privilege_room_creators
{
power_levels_content["events"]["m.room.tombstone"] = to_value(150)?;
} else {
power_levels_content["events"]["m.room.tombstone"] = to_value(100)?;
}
// always allow users to respond (not post new) to polls. this is primarily
// useful in read-only announcement rooms that post a public poll.
power_levels_content["events"]["org.matrix.msc3381.poll.response"] = to_value(0)?;
@@ -572,7 +676,6 @@ async fn room_alias_check(
})?;
if services
.rooms
.alias
.resolve_local_alias(&full_room_alias)
.await
@@ -599,7 +702,7 @@ async fn room_alias_check(
}
/// if a room is being created with a custom room ID, run our checks against it
fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
async fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
// apply forbidden room alias checks to custom room IDs too
if services
.globals
@@ -623,8 +726,64 @@ fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<Own
let server_name = services.globals.server_name();
let full_room_id = format!("!{custom_room_id}:{server_name}");
OwnedRoomId::parse(full_room_id)
.map_err(Into::into)
let room_id = OwnedRoomId::parse(full_room_id)
.inspect(|full_room_id| debug_info!(?full_room_id, "Full custom room ID"))
.inspect_err(|e| warn!(?e, ?custom_room_id, "Failed to create room with custom room ID",))
.inspect_err(|e| {
warn!(?e, ?custom_room_id, "Failed to create room with custom room ID");
})?;
// check if room ID doesn't already exist instead of erroring on auth check
if services
.short
.get_shortroomid(&room_id)
.await
.is_ok()
{
return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
}
Ok(room_id)
}
async fn can_publish_directory_check(
services: &Services,
body: &Ruma<create_room::v3::Request>,
) -> Result {
if !services
.server
.config
.lockdown_public_room_directory
|| body.appservice_info.is_some()
|| body.visibility != room::Visibility::Public
|| services.users.is_admin(body.sender_user()).await
{
return Ok(());
}
let msg = format!(
"Non-admin user {} tried to publish new to the directory while \
lockdown_public_room_directory is enabled",
body.sender_user(),
);
warn!("{msg}");
if services.server.config.admin_room_notices {
services.admin.notice(&msg).await;
}
Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")))
}
async fn can_create_room_check(
services: &Services,
body: &Ruma<create_room::v3::Request>,
) -> Result {
if !services.globals.allow_room_creation()
&& body.appservice_info.is_none()
&& !services.users.is_admin(body.sender_user()).await
{
return Err!(Request(Forbidden("Room creation has been disabled.",)));
}
Ok(())
}
-2
View File
@@ -16,13 +16,11 @@ pub(crate) async fn get_room_event_route(
let room_id = &body.room_id;
let event = services
.rooms
.timeline
.get_pdu(event_id)
.map_err(|_| err!(Request(NotFound("Event {} not found.", event_id))));
let visible = services
.rooms
.state_accessor
.user_can_see_event(body.sender_user(), room_id, event_id)
.map(Ok);
+1 -9
View File
@@ -17,7 +17,6 @@ pub(crate) async fn room_initial_sync_route(
let room_id = &body.room_id;
if !services
.rooms
.state_accessor
.user_can_see_state_events(body.sender_user(), room_id)
.await
@@ -26,19 +25,13 @@ pub(crate) async fn room_initial_sync_route(
}
let membership = services
.rooms
.state_cache
.user_membership(body.sender_user(), room_id)
.map(Ok);
let visibility = services
.rooms
.directory
.visibility(room_id)
.map(Ok);
let visibility = services.directory.visibility(room_id).map(Ok);
let state = services
.rooms
.state_accessor
.room_state_full_pdus(room_id)
.map_ok(Event::into_format)
@@ -46,7 +39,6 @@ pub(crate) async fn room_initial_sync_route(
let limit = LIMIT_MAX;
let events = services
.rooms
.timeline
.pdus_rev(None, room_id, None)
.try_take(limit)
+53 -89
View File
@@ -12,7 +12,7 @@
federation::space::{SpaceHierarchyParentSummary, get_hierarchy},
},
events::room::member::MembershipState,
space::SpaceRoomJoinRule::{self, *},
room::{JoinRuleSummary, RoomSummary},
};
use tuwunel_core::{
Err, Result, debug_warn, trace,
@@ -34,8 +34,8 @@
pub(crate) async fn get_room_summary_legacy(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_summary::msc3266::Request>,
) -> Result<RumaResponse<get_summary::msc3266::Response>> {
body: Ruma<get_summary::v1::Request>,
) -> Result<RumaResponse<get_summary::v1::Response>> {
get_room_summary(State(services), InsecureClientIp(client), body)
.boxed()
.await
@@ -51,15 +51,14 @@ pub(crate) async fn get_room_summary_legacy(
pub(crate) async fn get_room_summary(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_summary::msc3266::Request>,
) -> Result<get_summary::msc3266::Response> {
body: Ruma<get_summary::v1::Request>,
) -> Result<get_summary::v1::Response> {
let (room_id, servers) = services
.rooms
.alias
.resolve_with_servers(&body.room_id_or_alias, Some(body.via.clone()))
.await?;
if services.rooms.metadata.is_banned(&room_id).await {
if services.metadata.is_banned(&room_id).await {
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
}
@@ -73,9 +72,8 @@ async fn room_summary_response(
room_id: &RoomId,
servers: &[OwnedServerName],
sender_user: Option<&UserId>,
) -> Result<get_summary::msc3266::Response> {
) -> Result<get_summary::v1::Response> {
if services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.await
@@ -85,23 +83,12 @@ async fn room_summary_response(
.await;
}
let room =
remote_room_summary_hierarchy_response(services, room_id, servers, sender_user).await?;
let summary = remote_room_summary_hierarchy_response(services, room_id, servers, sender_user)
.await?
.summary;
Ok(get_summary::msc3266::Response {
room_id: room_id.to_owned(),
canonical_alias: room.canonical_alias,
avatar_url: room.avatar_url,
guest_can_join: room.guest_can_join,
name: room.name,
num_joined_members: room.num_joined_members,
topic: room.topic,
world_readable: room.world_readable,
join_rule: room.join_rule,
room_type: room.room_type,
room_version: room.room_version,
encryption: room.encryption,
allowed_room_ids: room.allowed_room_ids,
Ok(get_summary::v1::Response {
summary,
membership: sender_user
.is_some()
.then_some(MembershipState::Leave),
@@ -112,22 +99,13 @@ async fn local_room_summary_response(
services: &Services,
room_id: &RoomId,
sender_user: Option<&UserId>,
) -> Result<get_summary::msc3266::Response> {
) -> Result<get_summary::v1::Response> {
trace!(?sender_user, "Sending local room summary response for {room_id:?}");
let join_rule = services
.rooms
.state_accessor
.get_join_rules(room_id);
let join_rule = services.state_accessor.get_join_rules(room_id);
let world_readable = services
.rooms
.state_accessor
.is_world_readable(room_id);
let world_readable = services.state_accessor.is_world_readable(room_id);
let guest_can_join = services
.rooms
.state_accessor
.guest_can_join(room_id);
let guest_can_join = services.state_accessor.guest_can_join(room_id);
let (join_rule, world_readable, guest_can_join) =
join3(join_rule, world_readable, guest_can_join).await;
@@ -139,55 +117,41 @@ async fn local_room_summary_response(
&join_rule.clone().into(),
guest_can_join,
world_readable,
join_rule.allowed_rooms(),
join_rule.allowed_room_ids(),
sender_user,
)
.await?;
let canonical_alias = services
.rooms
.state_accessor
.get_canonical_alias(room_id)
.ok();
let name = services
.rooms
.state_accessor
.get_name(room_id)
.ok();
let name = services.state_accessor.get_name(room_id).ok();
let topic = services
.rooms
.state_accessor
.get_room_topic(room_id)
.ok();
let room_type = services
.rooms
.state_accessor
.get_room_type(room_id)
.ok();
let avatar_url = services
.rooms
.state_accessor
.get_avatar(room_id)
.map(|res| res.into_option().unwrap_or_default().url);
let room_version = services
.rooms
.state
.get_room_version(room_id)
.ok();
let room_version = services.state.get_room_version(room_id).ok();
let encryption = services
.rooms
.state_accessor
.get_room_encryption(room_id)
.ok();
let num_joined_members = services
.rooms
.state_cache
.room_joined_count(room_id)
.unwrap_or(0);
@@ -195,7 +159,6 @@ async fn local_room_summary_response(
let membership: OptionFuture<_> = sender_user
.map(|sender_user| {
services
.rooms
.state_accessor
.get_member(room_id, sender_user)
.map_ok_or(MembershipState::Leave, |content| content.membership)
@@ -224,24 +187,22 @@ async fn local_room_summary_response(
membership,
);
Ok(get_summary::msc3266::Response {
room_id: room_id.to_owned(),
canonical_alias,
avatar_url,
guest_can_join,
name,
num_joined_members: num_joined_members.try_into().unwrap_or_default(),
topic,
world_readable,
room_type,
room_version,
encryption,
Ok(get_summary::v1::Response {
summary: RoomSummary {
room_id: room_id.to_owned(),
canonical_alias,
avatar_url,
guest_can_join,
name,
num_joined_members: num_joined_members.try_into().unwrap_or_default(),
topic,
world_readable,
room_type,
room_version,
encryption,
join_rule: join_rule.into(),
},
membership,
allowed_room_ids: join_rule
.allowed_rooms()
.map(Into::into)
.collect(),
join_rule: join_rule.into(),
})
}
@@ -257,7 +218,7 @@ async fn remote_room_summary_hierarchy_response(
return Err!(Request(Forbidden("Federation is disabled.")));
}
if services.rooms.metadata.is_disabled(room_id).await {
if services.metadata.is_disabled(room_id).await {
return Err!(Request(Forbidden(
"Federaton of room {room_id} is currently disabled on this server."
)));
@@ -277,10 +238,11 @@ async fn remote_room_summary_hierarchy_response(
while let Some(Ok(response)) = requests.next().await {
trace!("{response:?}");
let room = response.room.clone();
if room.room_id != room_id {
let summary = &room.summary;
if summary.room_id != room_id {
debug_warn!(
"Room ID {} returned does not belong to the requested room ID {}",
room.room_id,
summary.room_id,
room_id
);
continue;
@@ -289,10 +251,10 @@ async fn remote_room_summary_hierarchy_response(
return user_can_see_summary(
services,
room_id,
&room.join_rule,
room.guest_can_join,
room.world_readable,
room.allowed_room_ids.iter().map(AsRef::as_ref),
&summary.join_rule,
summary.guest_can_join,
summary.world_readable,
summary.join_rule.allowed_room_ids(),
sender_user,
)
.await
@@ -308,7 +270,7 @@ async fn remote_room_summary_hierarchy_response(
async fn user_can_see_summary<'a, I>(
services: &Services,
room_id: &RoomId,
join_rule: &SpaceRoomJoinRule,
join_rule: &JoinRuleSummary,
guest_can_join: bool,
world_readable: bool,
allowed_room_ids: I,
@@ -317,23 +279,25 @@ async fn user_can_see_summary<'a, I>(
where
I: Iterator<Item = &'a RoomId> + Send,
{
let is_public_room = matches!(join_rule, Public | Knock | KnockRestricted);
let is_public_room = matches!(
join_rule,
JoinRuleSummary::Public | JoinRuleSummary::Knock | JoinRuleSummary::KnockRestricted(_)
);
match sender_user {
| Some(sender_user) => {
let user_can_see_state_events = services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, room_id);
let is_guest = services
.users
.is_deactivated(sender_user)
.unwrap_or(false);
let user_in_allowed_restricted_room = allowed_room_ids.stream().any(|room| {
services
.rooms
.state_cache
.is_joined(sender_user, room)
});
let user_in_allowed_restricted_room = allowed_room_ids
.stream()
.any(|room| services.state_cache.is_joined(sender_user, room));
let (user_can_see_state_events, is_guest, user_in_allowed_restricted_room) =
join3(user_can_see_state_events, is_guest, user_in_allowed_restricted_room)
+19 -31
View File
@@ -4,7 +4,7 @@
use futures::StreamExt;
use ruma::{
CanonicalJsonObject, RoomId, RoomVersionId,
api::client::{error::ErrorKind, room::upgrade_room},
api::client::room::upgrade_room,
events::{
StateEventType, TimelineEventType,
room::{
@@ -14,11 +14,12 @@
},
},
int,
room_version_rules::RoomIdFormatVersion,
};
use serde_json::{json, value::to_raw_value};
use tuwunel_core::{
Err, Error, Result, err,
matrix::{Event, StateKey, pdu::PduBuilder},
Err, Result, err,
matrix::{Event, StateKey, pdu::PduBuilder, room_version},
};
use crate::Ruma;
@@ -61,33 +62,35 @@ pub(crate) async fn upgrade_room_route(
.server
.supported_room_version(&body.new_version)
{
return Err(Error::BadRequest(
ErrorKind::UnsupportedRoomVersion,
return Err!(Request(UnsupportedRoomVersion(
"This server does not support that room version.",
));
)));
}
if matches!(body.new_version, RoomVersionId::V12) {
return Err!(Request(UnsupportedRoomVersion(
"Upgrading to version 12 is still under development.",
)));
}
let room_version_rules = room_version::rules(&body.new_version)?;
let room_id_format = &room_version_rules.room_id_format;
assert!(*room_id_format == RoomIdFormatVersion::V1, "TODO");
// Create a replacement room
let replacement_room = RoomId::new(services.globals.server_name());
let replacement_room = RoomId::new_v1(services.globals.server_name());
let _short_id = services
.rooms
.short
.get_or_create_shortroomid(&replacement_room)
.await;
let state_lock = services
.rooms
.state
.mutex
.lock(&body.room_id)
.await;
let state_lock = services.state.mutex.lock(&body.room_id).await;
// Send a m.room.tombstone event to the old room to indicate that it is not
// intended to be used any further Fail if the sender does not have the required
// permissions
let tombstone_event_id = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
@@ -102,16 +105,10 @@ pub(crate) async fn upgrade_room_route(
// Change lock to replacement room
drop(state_lock);
let state_lock = services
.rooms
.state
.mutex
.lock(&replacement_room)
.await;
let state_lock = services.state.mutex.lock(&replacement_room).await;
// Get the old room creation event
let mut create_event_content: CanonicalJsonObject = services
.rooms
.state_accessor
.room_state_get_content(&body.room_id, &StateEventType::RoomCreate, "")
.await
@@ -164,7 +161,6 @@ pub(crate) async fn upgrade_room_route(
}
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
@@ -183,7 +179,6 @@ pub(crate) async fn upgrade_room_route(
// Join the new room
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
@@ -212,7 +207,6 @@ pub(crate) async fn upgrade_room_route(
// Replicate transferable state events to the new room
for event_type in TRANSFERABLE_STATE_EVENTS {
let event_content = match services
.rooms
.state_accessor
.room_state_get(&body.room_id, event_type, "")
.await
@@ -222,7 +216,6 @@ pub(crate) async fn upgrade_room_route(
};
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
@@ -240,27 +233,23 @@ pub(crate) async fn upgrade_room_route(
// Moves any local aliases to the new room
let mut local_aliases = services
.rooms
.alias
.local_aliases_for_room(&body.room_id)
.boxed();
while let Some(alias) = local_aliases.next().await {
services
.rooms
.alias
.remove_alias(alias, sender_user)
.await?;
services
.rooms
.alias
.set_alias(alias, &replacement_room, sender_user)?;
}
// Get the old room power levels
let power_levels_event_content: RoomPowerLevelsEventContent = services
.rooms
.state_accessor
.room_state_get_content(&body.room_id, &StateEventType::RoomPowerLevels, "")
.await
@@ -280,7 +269,6 @@ pub(crate) async fn upgrade_room_route(
// Modify the power levels in the old room to prevent sending of events and
// inviting new users
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomPowerLevelsEventContent {
+1 -10
View File
@@ -87,7 +87,6 @@ async fn category_room_events(
.map(StreamExt::boxed)
.unwrap_or_else(|| {
services
.rooms
.state_cache
.rooms_joined(sender_user)
.map(ToOwned::to_owned)
@@ -110,12 +109,7 @@ async fn category_room_events(
limit,
};
let (count, results) = services
.rooms
.search
.search_pdus(&query)
.await
.ok()?;
let (count, results) = services.search.search_pdus(&query).await.ok()?;
results
.collect::<Vec<_>>()
@@ -187,7 +181,6 @@ async fn category_room_events(
async fn procure_room_state(services: &Services, room_id: &RoomId) -> Result<RoomState> {
let state = services
.rooms
.state_accessor
.room_state_full_pdus(room_id)
.map_ok(Event::into_format)
@@ -208,14 +201,12 @@ async fn check_room_visible(
let is_joined = !check_visible
|| services
.rooms
.state_cache
.is_joined(user_id, room_id)
.await;
let state_visible = !check_state
|| services
.rooms
.state_accessor
.user_can_see_state_events(user_id, room_id)
.await;
+1 -8
View File
@@ -30,16 +30,10 @@ pub(crate) async fn send_message_event_route(
return Err!(Request(Forbidden("Encryption has been disabled")));
}
let state_lock = services
.rooms
.state
.mutex
.lock(&body.room_id)
.await;
let state_lock = services.state.mutex.lock(&body.room_id).await;
if body.event_type == MessageLikeEventType::CallInvite
&& services
.rooms
.directory
.is_public_room(&body.room_id)
.await
@@ -75,7 +69,6 @@ pub(crate) async fn send_message_event_route(
.map_err(|e| err!(Request(BadJson("Invalid JSON body: {e}"))))?;
let event_id = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
+2
View File
@@ -1,3 +1,4 @@
use futures::FutureExt;
use ruma::{OwnedUserId, UserId};
use tuwunel_core::{Err, Result, debug};
use tuwunel_service::Services;
@@ -63,6 +64,7 @@ pub(super) async fn ldap_login(
services
.admin
.make_user_admin(lowercased_user_id)
.boxed()
.await?;
} else if !is_ldap_admin && is_tuwunel_admin {
services
+1 -4
View File
@@ -56,10 +56,7 @@ pub(crate) async fn login_token_route(
let mut uiaainfo = uiaa::UiaaInfo {
flows: vec![password_flow],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
..Default::default()
};
match &body.auth {
+52 -28
View File
@@ -1,21 +1,21 @@
use std::{
collections::{BTreeSet, VecDeque},
str::FromStr,
};
use std::{collections::BTreeSet, iter::once, str::FromStr};
use axum::extract::State;
use futures::{StreamExt, TryFutureExt, future::OptionFuture};
use futures::{FutureExt, StreamExt, TryFutureExt, future::OptionFuture, stream::FuturesOrdered};
use ruma::{
OwnedRoomId, OwnedServerName, RoomId, UInt, UserId, api::client::space::get_hierarchy,
};
use tuwunel_core::{
Err, Result,
Err, Result, debug_error,
utils::{future::TryExtExt, stream::IterStream},
};
use tuwunel_service::{
Services,
rooms::spaces::{
PaginationToken, SummaryAccessibility, get_parent_children_via, summary_to_chunk,
rooms::{
short::ShortRoomId,
spaces::{
PaginationToken, SummaryAccessibility, get_parent_children_via, summary_to_chunk,
},
},
};
@@ -77,30 +77,38 @@ async fn get_client_hierarchy<'a, ShortRoomIds>(
short_room_ids: ShortRoomIds,
) -> Result<get_hierarchy::v1::Response>
where
ShortRoomIds: Iterator<Item = &'a u64> + Clone + Send + Sync + 'a,
ShortRoomIds: Iterator<Item = &'a ShortRoomId> + Clone + Send + Sync + 'a,
{
type Via = Vec<OwnedServerName>;
type Entry = (OwnedRoomId, Via);
type Rooms = VecDeque<Entry>;
type Via = Vec<OwnedServerName>;
let mut queue: Rooms = [(
room_id.to_owned(),
room_id
let initial = async move {
let via = room_id
.server_name()
.map(ToOwned::to_owned)
.into_iter()
.collect(),
)]
.into();
.collect::<Vec<_>>();
let mut rooms = Vec::with_capacity(limit);
let mut parents = BTreeSet::new();
while let Some((current_room, via)) = queue.pop_front() {
let summary = services
.rooms
.spaces
.get_summary_and_children_client(&current_room, suggested_only, sender_user, &via)
.await?;
.get_summary_and_children_client(room_id, suggested_only, sender_user, &via)
.await;
(room_id.to_owned(), via, summary)
};
let mut parents = BTreeSet::new();
let mut rooms = Vec::with_capacity(limit);
let mut queue: FuturesOrdered<_> = once(initial.boxed()).collect();
while let Some((current_room, via, summary)) = queue.next().await {
let summary = match summary {
| Ok(summary) => summary,
| Err(e) => {
debug_error!(?current_room, ?via, ?e, "error getting summary");
continue;
},
};
match (summary, current_room == room_id) {
| (None | Some(SummaryAccessibility::Inaccessible), false) => {
@@ -130,7 +138,6 @@ async fn get_client_hierarchy<'a, ShortRoomIds>(
.stream()
.skip_while(|(room, _)| {
services
.rooms
.short
.get_shortroomid(room)
.map_ok(|short| {
@@ -159,20 +166,37 @@ async fn get_client_hierarchy<'a, ShortRoomIds>(
continue;
}
queue.extend(children);
children
.into_iter()
.map(|(room_id, via)| async move {
let summary = services
.spaces
.get_summary_and_children_client(
&room_id,
suggested_only,
sender_user,
&via,
)
.await;
(room_id, via, summary)
})
.map(FutureExt::boxed)
.for_each(|entry| queue.push_back(entry));
},
}
}
let next_batch: OptionFuture<_> = queue
.pop_front()
.map(async |(room, _)| {
.next()
.await
.map(async |(room, ..)| {
parents.insert(room);
let next_short_room_ids: Vec<_> = parents
.iter()
.stream()
.filter_map(|room_id| services.rooms.short.get_shortroomid(room_id).ok())
.filter_map(|room_id| services.short.get_shortroomid(room_id).ok())
.collect()
.await;
+52 -58
View File
@@ -2,7 +2,7 @@
use futures::{FutureExt, TryFutureExt, TryStreamExt};
use ruma::{
OwnedEventId, RoomId, UserId,
api::client::state::{get_state_events, get_state_events_for_key, send_state_event},
api::client::state::{get_state_event_for_key, get_state_events, send_state_event},
events::{
AnyStateEventContent, StateEventType,
room::{
@@ -78,7 +78,6 @@ pub(crate) async fn get_state_events_route(
let sender_user = body.sender_user();
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)
.await
@@ -88,7 +87,6 @@ pub(crate) async fn get_state_events_route(
Ok(get_state_events::v3::Response {
room_state: services
.rooms
.state_accessor
.room_state_full_pdus(&body.room_id)
.map_ok(Event::into_format)
@@ -107,12 +105,11 @@ pub(crate) async fn get_state_events_route(
/// readable
pub(crate) async fn get_state_events_for_key_route(
State(services): State<crate::State>,
body: Ruma<get_state_events_for_key::v3::Request>,
) -> Result<get_state_events_for_key::v3::Response> {
body: Ruma<get_state_event_for_key::v3::Request>,
) -> Result<get_state_event_for_key::v3::Response> {
let sender_user = body.sender_user();
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)
.await
@@ -123,7 +120,6 @@ pub(crate) async fn get_state_events_for_key_route(
}
let event = services
.rooms
.state_accessor
.room_state_get(&body.room_id, &body.event_type, &body.state_key)
.await
@@ -140,7 +136,7 @@ pub(crate) async fn get_state_events_for_key_route(
.as_ref()
.is_some_and(|f| f.to_lowercase().eq("event"));
Ok(get_state_events_for_key::v3::Response {
Ok(get_state_event_for_key::v3::Response {
content: event_format.or(|| event.get_content_as_value()),
event: event_format.then(|| {
json!({
@@ -167,8 +163,8 @@ pub(crate) async fn get_state_events_for_key_route(
/// readable
pub(crate) async fn get_state_events_for_empty_key_route(
State(services): State<crate::State>,
body: Ruma<get_state_events_for_key::v3::Request>,
) -> Result<RumaResponse<get_state_events_for_key::v3::Response>> {
body: Ruma<get_state_event_for_key::v3::Request>,
) -> Result<RumaResponse<get_state_event_for_key::v3::Response>> {
get_state_events_for_key_route(State(services), body)
.await
.map(RumaResponse)
@@ -184,9 +180,8 @@ async fn send_state_event_for_key_helper(
timestamp: Option<ruma::MilliSecondsSinceUnixEpoch>,
) -> Result<OwnedEventId> {
allowed_to_send_state_event(services, room_id, event_type, state_key, json).await?;
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let state_lock = services.state.mutex.lock(room_id).await;
let event_id = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
@@ -222,7 +217,7 @@ async fn allowed_to_send_state_event(
| StateEventType::RoomServerAcl => {
// prevents common ACL paw-guns as ACL management is difficult and prone to
// irreversible mistakes
match json.deserialize_as::<RoomServerAclEventContent>() {
match json.deserialize_as_unchecked::<RoomServerAclEventContent>() {
| Ok(acl_content) => {
if acl_content.allow_is_empty() {
return Err!(Request(BadJson(debug_warn!(
@@ -282,7 +277,7 @@ async fn allowed_to_send_state_event(
// admin room is a sensitive room, it should not ever be made public
if let Ok(admin_room_id) = services.admin.get_admin_room().await {
if admin_room_id == room_id {
match json.deserialize_as::<RoomJoinRulesEventContent>() {
match json.deserialize_as_unchecked::<RoomJoinRulesEventContent>() {
| Ok(join_rule) =>
if join_rule.join_rule == JoinRule::Public {
return Err!(Request(Forbidden(
@@ -301,7 +296,7 @@ async fn allowed_to_send_state_event(
| StateEventType::RoomHistoryVisibility => {
// admin room is a sensitive room, it should not ever be made world readable
if let Ok(admin_room_id) = services.admin.get_admin_room().await {
match json.deserialize_as::<RoomHistoryVisibilityEventContent>() {
match json.deserialize_as_unchecked::<RoomHistoryVisibilityEventContent>() {
| Ok(visibility_content) => {
if admin_room_id == room_id
&& visibility_content.history_visibility
@@ -322,7 +317,7 @@ async fn allowed_to_send_state_event(
}
},
| StateEventType::RoomCanonicalAlias => {
match json.deserialize_as::<RoomCanonicalAliasEventContent>() {
match json.deserialize_as_unchecked::<RoomCanonicalAliasEventContent>() {
| Ok(canonical_alias_content) => {
let mut aliases = canonical_alias_content.alt_aliases.clone();
@@ -332,7 +327,6 @@ async fn allowed_to_send_state_event(
for alias in aliases {
let (alias_room_id, _servers) = services
.rooms
.alias
.resolve_alias(&alias, None)
.await
@@ -354,52 +348,52 @@ async fn allowed_to_send_state_event(
},
}
},
| StateEventType::RoomMember => match json.deserialize_as::<RoomMemberEventContent>() {
| Ok(membership_content) => {
let Ok(_state_key) = UserId::parse(state_key) else {
return Err!(Request(BadJson(
"Membership event has invalid or non-existent state key"
)));
};
if let Some(authorising_user) =
membership_content.join_authorized_via_users_server
{
if membership_content.membership != MembershipState::Join {
| StateEventType::RoomMember =>
match json.deserialize_as_unchecked::<RoomMemberEventContent>() {
| Ok(membership_content) => {
let Ok(_state_key) = UserId::parse(state_key) else {
return Err!(Request(BadJson(
"join_authorised_via_users_server is only for member joins"
"Membership event has invalid or non-existent state key"
)));
}
};
if !services.globals.user_is_local(&authorising_user) {
return Err!(Request(InvalidParam(
"Authorising user {authorising_user} does not belong to this \
homeserver"
)));
}
if let Some(authorising_user) =
membership_content.join_authorized_via_users_server
{
if membership_content.membership != MembershipState::Join {
return Err!(Request(BadJson(
"join_authorised_via_users_server is only for member joins"
)));
}
services
.rooms
.state_cache
.is_joined(&authorising_user, room_id)
.map(is_false!())
.map(BoolExt::into_result)
.map_err(|()| {
err!(Request(InvalidParam(
"Authorising user {authorising_user} is not in the room. They \
cannot authorise the join."
)))
})
.await?;
}
if !services.globals.user_is_local(&authorising_user) {
return Err!(Request(InvalidParam(
"Authorising user {authorising_user} does not belong to this \
homeserver"
)));
}
services
.state_cache
.is_joined(&authorising_user, room_id)
.map(is_false!())
.map(BoolExt::into_result)
.map_err(|()| {
err!(Request(InvalidParam(
"Authorising user {authorising_user} is not in the room. \
They cannot authorise the join."
)))
})
.await?;
}
},
| Err(e) => {
return Err!(Request(BadJson(
"Membership content must have a valid JSON body with at least a valid \
membership state: {e}"
)));
},
},
| Err(e) => {
return Err!(Request(BadJson(
"Membership content must have a valid JSON body with at least a valid \
membership state: {e}"
)));
},
},
| _ => (),
}
+6 -10
View File
@@ -11,7 +11,7 @@
use tuwunel_core::{
Error, PduCount, Result,
matrix::pdu::PduEvent,
utils::stream::{BroadbandExt, ReadyExt, TryIgnore},
utils::stream::{BroadbandExt, ReadyExt},
};
use tuwunel_service::Services;
@@ -27,22 +27,20 @@ async fn load_timeline(
roomsincecount: PduCount,
next_batch: Option<PduCount>,
limit: usize,
) -> Result<(Vec<(PduCount, PduEvent)>, bool), Error> {
) -> Result<(Vec<(PduCount, PduEvent)>, bool, PduCount), Error> {
let last_timeline_count = services
.rooms
.timeline
.last_timeline_count(Some(sender_user), room_id)
.last_timeline_count(Some(sender_user), room_id, next_batch)
.await?;
if last_timeline_count <= roomsincecount {
return Ok((Vec::new(), false));
return Ok((Vec::new(), false, last_timeline_count));
}
let non_timeline_pdus = services
.rooms
.timeline
.pdus_rev(Some(sender_user), room_id, None)
.ignore_err()
.ready_filter_map(Result::ok)
.ready_skip_while(|&(pducount, _)| pducount > next_batch.unwrap_or_else(PduCount::max))
.ready_take_while(|&(pducount, _)| pducount > roomsincecount);
@@ -60,7 +58,7 @@ async fn load_timeline(
// is limited unless there are events in non_timeline_pdus
let limited = non_timeline_pdus.next().await.is_some();
Ok((timeline_pdus, limited))
Ok((timeline_pdus, limited, last_timeline_count))
}
async fn share_encrypted_room(
@@ -70,14 +68,12 @@ async fn share_encrypted_room(
ignore_room: Option<&RoomId>,
) -> bool {
services
.rooms
.state_cache
.get_shared_rooms(sender_user, user_id)
.ready_filter(|&room_id| Some(room_id) != ignore_room)
.map(ToOwned::to_owned)
.broad_any(async |other_room_id| {
services
.rooms
.state_accessor
.is_encrypted_room(&other_room_id)
.await
+51 -116
View File
@@ -6,7 +6,7 @@
use axum::extract::State;
use futures::{
FutureExt, StreamExt, TryFutureExt, TryStreamExt,
future::{OptionFuture, join, join3, join4, join5, try_join4},
future::{OptionFuture, join, join3, join4, join5, try_join3},
pin_mut,
};
use ruma::{
@@ -18,7 +18,7 @@
v3::{
Ephemeral, Filter, GlobalAccountData, InviteState, InvitedRoom, JoinedRoom,
KnockState, KnockedRoom, LeftRoom, Presence, RoomAccountData, RoomSummary, Rooms,
State as RoomState, Timeline, ToDevice,
State as RoomState, StateEvents, Timeline, ToDevice,
},
},
uiaa::UiaaResponse,
@@ -177,7 +177,7 @@ pub(crate) async fn sync_events_route(
}
// Wait for activity
if time::timeout_at(stop_at, watchers).await.is_err() {
if time::timeout_at(stop_at, watchers).await.is_err() || services.server.is_stopping() {
trace!(since, next_batch, "empty response");
return Ok(sync_events::v3::Response::new(next_batch.to_string()));
}
@@ -225,7 +225,6 @@ async fn build_sync_events(
};
let joined_rooms = services
.rooms
.state_cache
.rooms_joined(sender_user)
.map(ToOwned::to_owned)
@@ -258,7 +257,6 @@ async fn build_sync_events(
);
let left_rooms = services
.rooms
.state_cache
.rooms_left(sender_user)
.broad_filter_map(|(room_id, _)| {
@@ -278,12 +276,10 @@ async fn build_sync_events(
.collect();
let invited_rooms = services
.rooms
.state_cache
.rooms_invited(sender_user)
.fold_default(async |mut invited_rooms: BTreeMap<_, _>, (room_id, invite_state)| {
let invite_count = services
.rooms
.state_cache
.get_invite_count(&room_id, sender_user)
.await
@@ -303,12 +299,10 @@ async fn build_sync_events(
});
let knocked_rooms = services
.rooms
.state_cache
.rooms_knocked(sender_user)
.fold_default(async |mut knocked_rooms: BTreeMap<_, _>, (room_id, knock_state)| {
let knock_count = services
.rooms
.state_cache
.get_knock_count(&room_id, sender_user)
.await
@@ -439,7 +433,6 @@ async fn process_presence_updates(
.presence_since(since, Some(next_batch))
.filter(|(user_id, ..)| {
services
.rooms
.state_cache
.user_sees_user(syncing_user, user_id)
})
@@ -475,7 +468,6 @@ async fn handle_left_room(
filter: &FilterDefinition,
) -> Result<Option<LeftRoom>> {
let left_count = services
.rooms
.state_cache
.get_left_count(room_id, sender_user)
.await
@@ -504,11 +496,11 @@ async fn handle_left_room(
return Ok(None);
}
let is_not_found = services.rooms.metadata.exists(room_id).eq(&false);
let is_not_found = services.metadata.exists(room_id).eq(&false);
let is_disabled = services.rooms.metadata.is_disabled(room_id);
let is_disabled = services.metadata.is_disabled(room_id);
let is_banned = services.rooms.metadata.is_banned(room_id);
let is_banned = services.metadata.is_banned(room_id);
pin_mut!(is_not_found, is_disabled, is_banned);
if is_not_found.or(is_disabled).or(is_banned).await {
@@ -540,21 +532,19 @@ async fn handle_left_room(
prev_batch: Some(next_batch.to_string()),
events: Vec::new(),
},
state: RoomState { events: vec![event.into_format()] },
state: RoomState::Before(StateEvents { events: vec![event.into_format()] }),
}));
}
let mut left_state_events = Vec::new();
let since_shortstatehash = services
.rooms
.user
.get_token_shortstatehash(room_id, since);
let since_state_ids: HashMap<_, OwnedEventId> = since_shortstatehash
.map_ok(|since_shortstatehash| {
services
.rooms
.state_accessor
.state_full_ids(since_shortstatehash)
.map(Ok)
@@ -565,7 +555,6 @@ async fn handle_left_room(
.unwrap_or_default();
let Ok(left_event_id): Result<OwnedEventId> = services
.rooms
.state_accessor
.room_state_get_id(room_id, &StateEventType::RoomMember, sender_user.as_str())
.await
@@ -575,7 +564,6 @@ async fn handle_left_room(
};
let Ok(left_shortstatehash) = services
.rooms
.state_accessor
.pdu_shortstatehash(&left_event_id)
.await
@@ -585,14 +573,12 @@ async fn handle_left_room(
};
let mut left_state_ids: HashMap<_, _> = services
.rooms
.state_accessor
.state_full_ids(left_shortstatehash)
.collect()
.await;
let leave_shortstatekey = services
.rooms
.short
.get_or_create_shortstatekey(&StateEventType::RoomMember, sender_user.as_str())
.await;
@@ -602,7 +588,6 @@ async fn handle_left_room(
for (shortstatekey, event_id) in left_state_ids {
if full_state || since_state_ids.get(&shortstatekey) != Some(&event_id) {
let (event_type, state_key) = services
.rooms
.short
.get_statekey_from_short(shortstatekey)
.await?;
@@ -618,7 +603,7 @@ async fn handle_left_room(
continue;
}
let Ok(pdu) = services.rooms.timeline.get_pdu(&event_id).await else {
let Ok(pdu) = services.timeline.get_pdu(&event_id).await else {
error!("Pdu in state not found: {event_id}");
continue;
};
@@ -635,7 +620,7 @@ async fn handle_left_room(
prev_batch: Some(next_batch.to_string()),
events: Vec::new(), // and so we dont need to set this to empty vec
},
state: RoomState { events: left_state_events },
state: RoomState::Before(StateEvents { events: left_state_events }),
}))
}
@@ -658,17 +643,7 @@ async fn load_joined_room(
full_state: bool,
filter: &FilterDefinition,
) -> Result<(JoinedRoom, HashSet<OwnedUserId>, HashSet<OwnedUserId>)> {
let sincecount = PduCount::Normal(since);
let next_batchcount = PduCount::Normal(next_batch);
let current_shortstatehash = services
.rooms
.state
.get_room_shortstatehash(room_id)
.map_err(|_| err!(Database(error!("Room {room_id} has no state"))));
let since_shortstatehash = services
.rooms
.user
.get_token_shortstatehash(room_id, since)
.ok()
@@ -685,13 +660,12 @@ async fn load_joined_room(
services,
sender_user,
room_id,
sincecount,
Some(next_batchcount),
PduCount::Normal(since),
Some(PduCount::Normal(next_batch)),
timeline_limit,
);
let receipt_events = services
.rooms
.read_receipt
.readreceipts_since(room_id, since, Some(next_batch))
.filter_map(async |(read_user, _, edu)| {
@@ -704,29 +678,40 @@ async fn load_joined_room(
.collect::<HashMap<OwnedUserId, Raw<AnySyncEphemeralRoomEvent>>>()
.map(Ok);
let (current_shortstatehash, since_shortstatehash, (timeline_pdus, limited), receipt_events) =
try_join4(current_shortstatehash, since_shortstatehash, timeline, receipt_events)
let (since_shortstatehash, (timeline_pdus, limited, last_timeline_count), receipt_events) =
try_join3(since_shortstatehash, timeline, receipt_events)
.boxed()
.await?;
// State was changed after the cutoff for this sync; similar to other handlers.
if current_shortstatehash > next_batch {
// Transfer the since_shortstatehash not the current over to the next sync.
if let Some(since_shortstatehash) = since_shortstatehash {
let horizon_shortstatehash: OptionFuture<_> = timeline_pdus
.iter()
.map(at!(0))
.map(PduCount::into_unsigned)
.map(|shorteventid| {
services
.rooms
.user
.associate_token_shortstatehash(room_id, next_batch, since_shortstatehash)
.await;
}
.state_accessor
.get_shortstatehash(shorteventid)
})
.next()
.into();
return Ok((JoinedRoom::default(), HashSet::new(), HashSet::new()));
}
let current_shortstatehash = services
.state_accessor
.get_shortstatehash(last_timeline_count.into_unsigned())
.or_else(|_| services.state.get_room_shortstatehash(room_id));
let associate_token = services
.rooms
.user
.associate_token_shortstatehash(room_id, next_batch, current_shortstatehash);
let (horizon_shortstatehash, current_shortstatehash) =
join(horizon_shortstatehash, current_shortstatehash)
.boxed()
.await;
let current_shortstatehash = current_shortstatehash
.map_err(|_| err!(Database(error!("Room {room_id} has no state"))))?;
let associate_token =
services
.user
.associate_token_shortstatehash(room_id, next_batch, current_shortstatehash);
let lazy_loading_enabled = filter.room.state.lazy_load_options.is_enabled()
|| filter
@@ -747,12 +732,7 @@ async fn load_joined_room(
// Reset lazy loading because this is an initial sync
let lazy_load_reset: OptionFuture<_> = initial
.then(|| {
services
.rooms
.lazy_loading
.reset(lazy_loading_context)
})
.then(|| services.lazy_loading.reset(lazy_loading_context))
.into();
lazy_load_reset.await;
@@ -767,30 +747,15 @@ async fn load_joined_room(
.collect();
services
.rooms
.lazy_loading
.witness_retain(witness, lazy_loading_context)
})
.into();
let horizon_shortstatehash: OptionFuture<_> = timeline_pdus
.iter()
.map(at!(0))
.map(PduCount::into_unsigned)
.map(|shorteventid| {
services
.rooms
.state_accessor
.get_shortstatehash(shorteventid)
})
.next()
.into();
let last_notification_read: OptionFuture<_> = timeline_pdus
.is_empty()
.then(|| {
services
.rooms
.user
.last_notification_read(sender_user, room_id)
})
@@ -799,32 +764,26 @@ async fn load_joined_room(
let since_sender_member: OptionFuture<_> = since_shortstatehash
.map(|short| {
services
.rooms
.state_accessor
.state_get_content(short, &StateEventType::RoomMember, sender_user.as_str())
.ok()
})
.into();
let encrypted_room = services
.rooms
.state_accessor
.state_get(current_shortstatehash, &StateEventType::RoomEncryption, "")
.is_ok();
let encrypted_room = services.state_accessor.is_encrypted_room(room_id);
let last_privateread_update = services
.rooms
.read_receipt
.last_privateread_update(sender_user, room_id);
let (
witness,
(encrypted_room, last_privateread_update, last_notification_read),
(since_sender_member, horizon_shortstatehash, ()),
(witness, since_sender_member),
(encrypted_room, ()),
(last_privateread_update, last_notification_read),
) = join3(
witness,
join3(encrypted_room, last_privateread_update, last_notification_read),
join3(since_sender_member, horizon_shortstatehash, associate_token),
join(witness, since_sender_member),
join(encrypted_room, associate_token),
join(last_privateread_update, last_notification_read),
)
.boxed()
.await;
@@ -877,7 +836,6 @@ async fn load_joined_room(
let notification_count: OptionFuture<_> = send_notification_counts
.then(|| {
services
.rooms
.user
.notification_count(sender_user, room_id)
.map(TryInto::try_into)
@@ -888,7 +846,6 @@ async fn load_joined_room(
let highlight_count: OptionFuture<_> = send_notification_counts
.then(|| {
services
.rooms
.user
.highlight_count(sender_user, room_id)
.map(TryInto::try_into)
@@ -900,7 +857,6 @@ async fn load_joined_room(
.gt(&since)
.then(|| {
services
.rooms
.read_receipt
.private_read_get(room_id, sender_user)
.map(Result::ok)
@@ -908,7 +864,6 @@ async fn load_joined_room(
.into();
let typing_events = services
.rooms
.typing
.last_typing_update(room_id)
.and_then(async |count| {
@@ -1042,7 +997,7 @@ async fn load_joined_room(
let joined_room = JoinedRoom {
account_data: RoomAccountData { events: account_data_events },
ephemeral: Ephemeral { events: edus },
state: RoomState { events: state_events },
state: RoomState::Before(StateEvents { events: state_events }),
summary: RoomSummary {
joined_member_count: joined_member_count.map(ruma_from_u64),
invited_member_count: invited_member_count.map(ruma_from_u64),
@@ -1096,7 +1051,6 @@ async fn calculate_state_changes<'a>(
let state_get_shorteventid = |user_id: &'a UserId| {
services
.rooms
.state_accessor
.state_get_shortid(
horizon_shortstatehash,
@@ -1122,7 +1076,6 @@ async fn calculate_state_changes<'a>(
.then(|| {
StreamExt::into_future(
services
.rooms
.state_accessor
.state_added((since_shortstatehash, horizon_shortstatehash))
.boxed(),
@@ -1134,7 +1087,6 @@ async fn calculate_state_changes<'a>(
.then(|| {
StreamExt::into_future(
services
.rooms
.state_accessor
.state_full_shortids(horizon_shortstatehash)
.expect_ok(),
@@ -1155,18 +1107,12 @@ async fn calculate_state_changes<'a>(
.chain(lazy_state_ids.stream())
.broad_filter_map(|shorteventid| {
services
.rooms
.short
.get_eventid_from_short(shorteventid)
.ok()
})
.broad_filter_map(async |event_id: OwnedEventId| {
services
.rooms
.timeline
.get_pdu(&event_id)
.ok()
.await
services.timeline.get_pdu(&event_id).ok().await
})
.collect::<Vec<_>>()
.boxed()
@@ -1198,7 +1144,6 @@ async fn lazy_filter(
shorteventid: ShortEventId,
) -> Option<ShortEventId> {
let (event_type, state_key) = services
.rooms
.short
.get_statekey_from_short(shortstatekey)
.await
@@ -1214,13 +1159,11 @@ async fn calculate_counts(
sender_user: &UserId,
) -> (Option<u64>, Option<u64>, Option<Vec<OwnedUserId>>) {
let joined_member_count = services
.rooms
.state_cache
.room_joined_count(room_id)
.unwrap_or(0);
let invited_member_count = services
.rooms
.state_cache
.room_invited_count(room_id)
.unwrap_or(0);
@@ -1243,7 +1186,6 @@ async fn calculate_heroes(
sender_user: &UserId,
) -> Vec<OwnedUserId> {
services
.rooms
.timeline
.all_pdus(sender_user, room_id)
.ready_filter(|(_, pdu)| pdu.kind == RoomMember)
@@ -1287,14 +1229,8 @@ async fn fold_hero(
}
let (is_invited, is_joined) = join(
services
.rooms
.state_cache
.is_invited(user_id, room_id),
services
.rooms
.state_cache
.is_joined(user_id, room_id),
services.state_cache.is_invited(user_id, room_id),
services.state_cache.is_joined(user_id, room_id),
)
.await;
@@ -1314,7 +1250,6 @@ async fn typings_event_for_user(
Ok(SyncEphemeralRoomEvent {
content: TypingEventContent {
user_ids: services
.rooms
.typing
.typing_users_for_user(room_id, sender_user)
.await?,
+13 -38
View File
@@ -13,7 +13,9 @@
};
use ruma::{
DeviceId, OwnedEventId, OwnedRoomId, RoomId, UInt, UserId,
api::client::sync::sync_events::{self, DeviceLists, UnreadNotificationsCount},
api::client::sync::sync_events::{
self, DeviceLists, UnreadNotificationsCount, v5::request::ExtensionRoomConfig,
},
directory::RoomTypeFilter,
events::{
AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, StateEventType, TimelineEventType,
@@ -96,21 +98,18 @@ pub(crate) async fn sync_events_v5_route(
.update_snake_sync_request_with_cache(&snake_key, &mut cached);
let all_joined_rooms = services
.rooms
.state_cache
.rooms_joined(sender_user)
.map(ToOwned::to_owned)
.collect::<Vec<OwnedRoomId>>();
let all_invited_rooms = services
.rooms
.state_cache
.rooms_invited(sender_user)
.map(|r| r.0)
.collect::<Vec<OwnedRoomId>>();
let all_knocked_rooms = services
.rooms
.state_cache
.rooms_knocked(sender_user)
.map(|r| r.0)
@@ -232,11 +231,11 @@ async fn fetch_subscriptions(
) {
let mut known_subscription_rooms = BTreeSet::new();
for (room_id, room) in &body.room_subscriptions {
let not_exists = services.rooms.metadata.exists(room_id).eq(&false);
let not_exists = services.metadata.exists(room_id).eq(&false);
let is_disabled = services.rooms.metadata.is_disabled(room_id);
let is_disabled = services.metadata.is_disabled(room_id);
let is_banned = services.rooms.metadata.is_banned(room_id);
let is_banned = services.metadata.is_banned(room_id);
pin_mut!(not_exists, is_disabled, is_banned);
if not_exists.or(is_disabled).or(is_banned).await {
@@ -412,7 +411,6 @@ async fn process_rooms<'a, Rooms>(
{
// TODO: figure out a timestamp we can use for remote invites
invite_state = services
.rooms
.state_cache
.invite_state(sender_user, room_id)
.await
@@ -420,7 +418,7 @@ async fn process_rooms<'a, Rooms>(
(timeline_pdus, limited) = (Vec::new(), true);
} else {
(timeline_pdus, limited) = match load_timeline(
(timeline_pdus, limited, _) = match load_timeline(
services,
sender_user,
room_id,
@@ -451,7 +449,6 @@ async fn process_rooms<'a, Rooms>(
}
let last_privateread_update = services
.rooms
.read_receipt
.last_privateread_update(sender_user, room_id)
.await;
@@ -459,7 +456,6 @@ async fn process_rooms<'a, Rooms>(
let private_read_event: OptionFuture<_> = (last_privateread_update > *roomsince)
.then(|| {
services
.rooms
.read_receipt
.private_read_get(room_id, sender_user)
.ok()
@@ -467,7 +463,6 @@ async fn process_rooms<'a, Rooms>(
.into();
let mut receipts: Vec<Raw<AnySyncEphemeralRoomEvent>> = services
.rooms
.read_receipt
.readreceipts_since(room_id, *roomsince, Some(next_batch))
.filter_map(async |(read_user, _ts, v)| {
@@ -550,7 +545,6 @@ async fn process_rooms<'a, Rooms>(
.stream()
.filter_map(async |state| {
services
.rooms
.state_accessor
.room_state_get(room_id, &state.0, &state.1)
.await
@@ -561,7 +555,6 @@ async fn process_rooms<'a, Rooms>(
.await;
let room_name = services
.rooms
.state_accessor
.get_name(room_id)
.await
@@ -570,13 +563,11 @@ async fn process_rooms<'a, Rooms>(
// Heroes
let heroes: Vec<_> = if room_name.is_none() {
services
.rooms
.state_cache
.room_members(room_id)
.ready_filter(|member| *member != sender_user)
.filter_map(|user_id| {
services
.rooms
.state_accessor
.get_member(room_id, user_id)
.map_ok(|memberevent| sync_events::v5::response::Hero {
@@ -627,12 +618,7 @@ async fn process_rooms<'a, Rooms>(
None
};
let room_avatar = match services
.rooms
.state_accessor
.get_avatar(room_id)
.await
{
let room_avatar = match services.state_accessor.get_avatar(room_id).await {
| ruma::JsOption::Some(avatar) => ruma::JsOption::from_option(avatar.url),
| ruma::JsOption::Null => ruma::JsOption::Null,
| ruma::JsOption::Undefined => ruma::JsOption::Undefined,
@@ -651,7 +637,6 @@ async fn process_rooms<'a, Rooms>(
unread_notifications: UnreadNotificationsCount {
highlight_count: Some(
services
.rooms
.user
.highlight_count(sender_user, room_id)
.await
@@ -660,7 +645,6 @@ async fn process_rooms<'a, Rooms>(
),
notification_count: Some(
services
.rooms
.user
.notification_count(sender_user, room_id)
.await
@@ -674,7 +658,6 @@ async fn process_rooms<'a, Rooms>(
limited,
joined_count: Some(
services
.rooms
.state_cache
.room_joined_count(room_id)
.await
@@ -684,7 +667,6 @@ async fn process_rooms<'a, Rooms>(
),
invited_count: Some(
services
.rooms
.state_cache
.room_invited_count(room_id)
.await
@@ -727,7 +709,10 @@ async fn collect_account_data(
.await;
if let Some(rooms) = &body.extensions.account_data.rooms {
for room in rooms {
for room in rooms
.iter()
.filter_map(|erc| extract_variant!(erc, ExtensionRoomConfig::Room))
{
account_data.rooms.insert(
room.clone(),
services
@@ -775,7 +760,6 @@ async fn collect_e2ee<'a, Rooms>(
for room_id in all_joined_rooms {
let Ok(current_shortstatehash) = services
.rooms
.state
.get_room_shortstatehash(room_id)
.await
@@ -785,14 +769,12 @@ async fn collect_e2ee<'a, Rooms>(
};
let since_shortstatehash = services
.rooms
.user
.get_token_shortstatehash(room_id, globalsince)
.await
.ok();
let encrypted_room = services
.rooms
.state_accessor
.state_get(current_shortstatehash, &StateEventType::RoomEncryption, "")
.await
@@ -805,13 +787,11 @@ async fn collect_e2ee<'a, Rooms>(
}
let since_encryption = services
.rooms
.state_accessor
.state_get(since_shortstatehash, &StateEventType::RoomEncryption, "")
.await;
let since_sender_member: Option<RoomMemberEventContent> = services
.rooms
.state_accessor
.state_get_content(
since_shortstatehash,
@@ -829,14 +809,12 @@ async fn collect_e2ee<'a, Rooms>(
if encrypted_room {
let current_state_ids: HashMap<_, OwnedEventId> = services
.rooms
.state_accessor
.state_full_ids(current_shortstatehash)
.collect()
.await;
let since_state_ids: HashMap<_, _> = services
.rooms
.state_accessor
.state_full_ids(since_shortstatehash)
.collect()
@@ -847,7 +825,7 @@ async fn collect_e2ee<'a, Rooms>(
continue;
}
let Ok(pdu) = services.rooms.timeline.get_pdu(&id).await else {
let Ok(pdu) = services.timeline.get_pdu(&id).await else {
error!("Pdu in state not found: {id}");
continue;
};
@@ -892,7 +870,6 @@ async fn collect_e2ee<'a, Rooms>(
// If the user is in a new encrypted room, give them all joined users
device_list_changes.extend(
services
.rooms
.state_cache
.room_members(room_id)
// Don't send key updates from the sender to the sender
@@ -990,7 +967,6 @@ async fn collect_typing_events<'a, Rooms>(
.stream()
.filter_map(async |room_id| {
services
.rooms
.typing
.typing_users_for_user(room_id, sender_user)
.inspect_err(|e| warn!(%room_id, "Failed to get typing events for room: {e}"))
@@ -1032,7 +1008,6 @@ fn filter_rooms<'a, Rooms>(
{
rooms.filter_map(async |room_id| {
let room_type = services
.rooms
.state_accessor
.get_room_type(room_id)
.await;
+1 -11
View File
@@ -3,7 +3,7 @@
use ruma::api::client::thirdparty::get_protocols;
use tuwunel_core::Result;
use crate::{Ruma, RumaResponse};
use crate::Ruma;
/// # `GET /_matrix/client/r0/thirdparty/protocols`
///
@@ -14,13 +14,3 @@ pub(crate) async fn get_protocols_route(
// TODO
Ok(get_protocols::v3::Response { protocols: BTreeMap::new() })
}
/// # `GET /_matrix/client/unstable/thirdparty/protocols`
///
/// Same as `get_protocols_route`, except for some reason Element Android legacy
/// calls this
pub(crate) async fn get_protocols_route_unstable(
body: Ruma<get_protocols::v3::Request>,
) -> Result<RumaResponse<get_protocols::v3::Response>> {
get_protocols_route(body).await.map(RumaResponse)
}
-2
View File
@@ -32,13 +32,11 @@ pub(crate) async fn get_threads_route(
.unwrap_or_else(PduCount::max);
let threads: Vec<(PduCount, PduEvent)> = services
.rooms
.threads
.threads_until(body.sender_user(), &body.room_id, from, &body.include)
.take(limit)
.try_filter_map(async |(count, pdu)| {
Ok(services
.rooms
.state_accessor
.user_can_see_event(body.sender_user(), &body.room_id, &pdu.event_id)
.await
-3
View File
@@ -19,7 +19,6 @@ pub(crate) async fn create_typing_event_route(
}
if !services
.rooms
.state_cache
.is_joined(sender_user, &body.room_id)
.await
@@ -46,7 +45,6 @@ pub(crate) async fn create_typing_event_route(
.try_mul(1000)?,
);
services
.rooms
.typing
.typing_add(
sender_user,
@@ -59,7 +57,6 @@ pub(crate) async fn create_typing_event_route(
},
| _ => {
services
.rooms
.typing
.typing_remove(sender_user, &body.room_id)
.await?;
+58 -90
View File
@@ -1,5 +1,3 @@
use std::collections::BTreeMap;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use futures::StreamExt;
@@ -10,8 +8,8 @@
error::ErrorKind,
membership::mutual_rooms,
profile::{
delete_profile_key, delete_timezone_key, get_profile_key, get_timezone_key,
set_profile_key, set_timezone_key,
ProfileFieldName, ProfileFieldValue, delete_profile_field, delete_timezone_key,
get_profile_field, get_timezone_key, set_profile_field, set_timezone_key,
},
},
federation,
@@ -20,7 +18,6 @@
};
use tuwunel_core::{Err, Error, Result};
use super::{update_avatar_url, update_displayname};
use crate::Ruma;
/// # `GET /_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms`
@@ -47,7 +44,6 @@ pub(crate) async fn get_mutual_rooms_route(
}
let mutual_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.get_shared_rooms(sender_user, &body.user_id)
.map(ToOwned::to_owned)
@@ -123,80 +119,55 @@ pub(crate) async fn set_timezone_key_route(
/// Updates the profile key-value field of a user, as per MSC4133.
///
/// This also handles the avatar_url and displayname being updated.
pub(crate) async fn set_profile_key_route(
pub(crate) async fn set_profile_field_route(
State(services): State<crate::State>,
body: Ruma<set_profile_key::unstable::Request>,
) -> Result<set_profile_key::unstable::Response> {
body: Ruma<set_profile_field::v3::Request>,
) -> Result<set_profile_field::v3::Response> {
let sender_user = body.sender_user();
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
if body.kv_pair.is_empty() {
return Err!(Request(BadJson(
"The key-value pair JSON body is empty. Use DELETE to delete a key"
)));
}
if body.kv_pair.len() > 1 {
// TODO: support PATCH or "recursively" adding keys in some sort
return Err!(Request(BadJson(
"This endpoint can only take one key-value pair at a time"
)));
}
let Some(profile_key_value) = body.kv_pair.get(&body.key_name) else {
return Err!(Request(BadJson(
"The key does not match the URL field key, or JSON body is empty (use DELETE)"
)));
};
if body
.kv_pair
.keys()
.any(|key| key.starts_with("u.") && !profile_key_value.is_string())
{
return Err!(Request(BadJson("u.* profile key fields must be strings")));
}
if body.kv_pair.keys().any(|key| key.len() > 128) {
if body.value.field_name().as_str().len() > 128 {
return Err!(Request(BadJson("Key names cannot be longer than 128 bytes")));
}
if body.key_name == "displayname" {
if body.value.field_name() == ProfileFieldName::DisplayName {
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(&body.user_id)
.map(Into::into)
.collect()
.await;
update_displayname(
&services,
&body.user_id,
Some(profile_key_value.to_string()),
&all_joined_rooms,
)
.await;
} else if body.key_name == "avatar_url" {
let mxc = ruma::OwnedMxcUri::from(profile_key_value.to_string());
services
.users
.update_displayname(
&body.user_id,
Some(body.value.value().to_string()),
&all_joined_rooms,
)
.await;
} else if body.value.field_name() == ProfileFieldName::AvatarUrl {
let mxc = ruma::OwnedMxcUri::from(body.value.value().to_string());
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(&body.user_id)
.map(Into::into)
.collect()
.await;
update_avatar_url(&services, &body.user_id, Some(mxc), None, &all_joined_rooms).await;
services
.users
.update_avatar_url(&body.user_id, Some(mxc), None, &all_joined_rooms)
.await;
} else {
services.users.set_profile_key(
&body.user_id,
&body.key_name,
Some(profile_key_value.clone()),
body.value.field_name().as_str(),
Some(body.value.value().into_owned()),
);
}
@@ -208,7 +179,7 @@ pub(crate) async fn set_profile_key_route(
.await?;
}
Ok(set_profile_key::unstable::Response {})
Ok(set_profile_field::v3::Response {})
}
/// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}`
@@ -216,47 +187,44 @@ pub(crate) async fn set_profile_key_route(
/// Deletes the profile key-value field of a user, as per MSC4133.
///
/// This also handles the avatar_url and displayname being updated.
pub(crate) async fn delete_profile_key_route(
pub(crate) async fn delete_profile_field_route(
State(services): State<crate::State>,
body: Ruma<delete_profile_key::unstable::Request>,
) -> Result<delete_profile_key::unstable::Response> {
body: Ruma<delete_profile_field::v3::Request>,
) -> Result<delete_profile_field::v3::Response> {
let sender_user = body.sender_user();
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
if body.kv_pair.len() > 1 {
// TODO: support PATCH or "recursively" adding keys in some sort
return Err!(Request(BadJson(
"This endpoint can only take one key-value pair at a time"
)));
}
if body.key_name == "displayname" {
if body.field == ProfileFieldName::DisplayName {
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(&body.user_id)
.map(Into::into)
.collect()
.await;
update_displayname(&services, &body.user_id, None, &all_joined_rooms).await;
} else if body.key_name == "avatar_url" {
services
.users
.update_displayname(&body.user_id, None, &all_joined_rooms)
.await;
} else if body.field == ProfileFieldName::AvatarUrl {
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(&body.user_id)
.map(Into::into)
.collect()
.await;
update_avatar_url(&services, &body.user_id, None, None, &all_joined_rooms).await;
services
.users
.update_avatar_url(&body.user_id, None, None, &all_joined_rooms)
.await;
} else {
services
.users
.set_profile_key(&body.user_id, &body.key_name, None);
.set_profile_key(&body.user_id, body.field.as_str(), None);
}
if services.config.allow_local_presence {
@@ -267,7 +235,7 @@ pub(crate) async fn delete_profile_key_route(
.await?;
}
Ok(delete_profile_key::unstable::Response {})
Ok(delete_profile_field::v3::Response {})
}
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/us.cloke.msc4175.tz`
@@ -337,12 +305,10 @@ pub(crate) async fn get_timezone_key_route(
///
/// - If user is on another server and we do not have a local copy already fetch
/// `timezone` over federation
pub(crate) async fn get_profile_key_route(
pub(crate) async fn get_profile_field_route(
State(services): State<crate::State>,
body: Ruma<get_profile_key::unstable::Request>,
) -> Result<get_profile_key::unstable::Response> {
let mut profile_key_value: BTreeMap<String, serde_json::Value> = BTreeMap::new();
body: Ruma<get_profile_field::v3::Request>,
) -> Result<get_profile_field::v3::Response> {
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
if let Ok(response) = services
@@ -379,25 +345,29 @@ pub(crate) async fn get_profile_key_route(
.users
.set_timezone(&body.user_id, response.tz.clone());
match response.custom_profile_fields.get(&body.key_name) {
let profile_key_value: Option<ProfileFieldValue> = match response
.custom_profile_fields
.get(body.field.as_str())
{
| Some(value) => {
profile_key_value.insert(body.key_name.clone(), value.clone());
services.users.set_profile_key(
&body.user_id,
&body.key_name,
body.field.as_str(),
Some(value.clone()),
);
Some(ProfileFieldValue::new(body.field.as_str(), value.clone())?)
},
| _ => {
return Err!(Request(NotFound("The requested profile key does not exist.")));
},
}
};
if profile_key_value.is_empty() {
if profile_key_value.is_none() {
return Err!(Request(NotFound("The requested profile key does not exist.")));
}
return Ok(get_profile_key::unstable::Response { value: profile_key_value });
return Ok(get_profile_field::v3::Response { value: profile_key_value });
}
}
@@ -407,22 +377,20 @@ pub(crate) async fn get_profile_key_route(
return Err!(Request(NotFound("Profile was not found.")));
}
match services
let profile_key_value: Option<ProfileFieldValue> = match services
.users
.profile_key(&body.user_id, &body.key_name)
.profile_key(&body.user_id, body.field.as_str())
.await
{
| Ok(value) => {
profile_key_value.insert(body.key_name.clone(), value);
},
| Ok(value) => Some(ProfileFieldValue::new(body.field.as_str(), value)?),
| _ => {
return Err!(Request(NotFound("The requested profile key does not exist.")));
},
}
};
if profile_key_value.is_empty() {
if profile_key_value.is_none() {
return Err!(Request(NotFound("The requested profile key does not exist.")));
}
Ok(get_profile_key::unstable::Response { value: profile_key_value })
Ok(get_profile_field::v3::Response { value: profile_key_value })
}
-3
View File
@@ -57,13 +57,11 @@ pub(crate) async fn search_users_route(
}
let user_in_public_room = services
.rooms
.state_cache
.rooms_joined(&user_id)
.map(ToOwned::to_owned)
.broad_any(async |room_id| {
services
.rooms
.state_accessor
.get_join_rules(&room_id)
.map(|rule| matches!(rule, JoinRule::Public))
@@ -71,7 +69,6 @@ pub(crate) async fn search_users_route(
});
let user_sees_user = services
.rooms
.state_cache
.user_sees_user(sender_user, &user_id);
+16
View File
@@ -0,0 +1,16 @@
use ruma::{RoomId, UserId};
use tuwunel_core::{Err, Result, warn};
use tuwunel_service::Services;
pub(crate) async fn invite_check(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
) -> Result {
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
warn!("{sender_user} is not an admin and attempted to send an invite to {room_id}");
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
}
Ok(())
}
+11 -14
View File
@@ -1,12 +1,9 @@
use axum::{Json, extract::State, response::IntoResponse};
use ruma::api::client::{
discovery::{
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
discover_support::{self, Contact},
},
error::ErrorKind,
use ruma::api::client::discovery::{
discover_homeserver::{self, HomeserverInfo},
discover_support::{self, Contact},
};
use tuwunel_core::{Error, Result};
use tuwunel_core::{Err, Result};
use crate::Ruma;
@@ -19,13 +16,12 @@ pub(crate) async fn well_known_client(
) -> Result<discover_homeserver::Response> {
let client_url = match services.server.config.well_known.client.as_ref() {
| Some(url) => url.to_string(),
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
| None => return Err!(Request(NotFound("Not found."))),
};
Ok(discover_homeserver::Response {
homeserver: HomeserverInfo { base_url: client_url.clone() },
homeserver: HomeserverInfo { base_url: client_url },
identity_server: None,
sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }),
tile_server: None,
})
}
@@ -54,7 +50,7 @@ pub(crate) async fn well_known_support(
// support page or role must be either defined for this to be valid
if support_page.is_none() && role.is_none() {
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found."));
return Err!(Request(NotFound("Not found.")));
}
let email_address = services
@@ -63,6 +59,7 @@ pub(crate) async fn well_known_support(
.well_known
.support_email
.clone();
let matrix_id = services
.server
.config
@@ -72,7 +69,7 @@ pub(crate) async fn well_known_support(
// if a role is specified, an email address or matrix id is required
if role.is_some() && (email_address.is_none() && matrix_id.is_none()) {
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found."));
return Err!(Request(NotFound("Not found.")));
}
// TODO: support defining multiple contacts in the config
@@ -86,7 +83,7 @@ pub(crate) async fn well_known_support(
// support page or role+contacts must be either defined for this to be valid
if contacts.is_empty() && support_page.is_none() {
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found."));
return Err!(Request(NotFound("Not found.")));
}
Ok(discover_support::Response { contacts, support_page })
@@ -103,7 +100,7 @@ pub(crate) async fn syncv3_client_server_json(
| Some(url) => url.to_string(),
| None => match services.server.config.well_known.server.as_ref() {
| Some(url) => url.to_string(),
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
| None => return Err!(Request(NotFound("Not found."))),
},
};
+1 -1
View File
@@ -1,4 +1,4 @@
#![type_length_limit = "65536"] //TODO: reduce me
#![type_length_limit = "163840"] //TODO: REDUCE ME
#![allow(clippy::toplevel_ref_arg)]
pub mod client;
+3 -5
View File
@@ -23,9 +23,9 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
let config = &server.config;
let mut router = router
.ruma_route(&client::get_timezone_key_route)
.ruma_route(&client::get_profile_key_route)
.ruma_route(&client::set_profile_key_route)
.ruma_route(&client::delete_profile_key_route)
.ruma_route(&client::get_profile_field_route)
.ruma_route(&client::set_profile_field_route)
.ruma_route(&client::delete_profile_field_route)
.ruma_route(&client::set_timezone_key_route)
.ruma_route(&client::delete_timezone_key_route)
.ruma_route(&client::appservice_ping)
@@ -116,8 +116,6 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::search_users_route)
.ruma_route(&client::get_member_events_route)
.ruma_route(&client::get_protocols_route)
.route("/_matrix/client/unstable/thirdparty/protocols",
get(client::get_protocols_route_unstable))
.ruma_route(&client::send_message_event_route)
.ruma_route(&client::send_state_event_for_key_route)
.ruma_route(&client::get_state_events_route)
+7 -1
View File
@@ -82,7 +82,13 @@ impl<T> FromRequest<State, Body> for Args<T>
{
type Rejection = Error;
#[tracing::instrument(name = "ar", level = "debug", skip(services), ret, err)]
#[tracing::instrument(
name = "ar",
level = "debug",
skip(services),
err(level = "debug")
ret,
)]
async fn from_request(
request: hyper::Request<Body>,
services: &State,
+27 -18
View File
@@ -22,7 +22,8 @@
directory::get_public_rooms,
error::ErrorKind,
profile::{
get_avatar_url, get_display_name, get_profile, get_profile_key, get_timezone_key,
get_avatar_url, get_display_name, get_profile, get_profile_field,
get_timezone_key,
},
voip::get_turn_server_info,
},
@@ -60,8 +61,8 @@ pub(super) struct Auth {
#[tracing::instrument(
level = "trace",
skip(services, request, json_body),
ret,
err
err(level = "debug"),
ret
)]
pub(super) async fn auth(
services: &Services,
@@ -69,7 +70,10 @@ pub(super) async fn auth(
json_body: Option<&CanonicalJsonValue>,
metadata: &Metadata,
) -> Result<Auth> {
use AuthScheme::{AccessToken, AccessTokenOptional, AppserviceToken, ServerSignatures};
use AuthScheme::{
AccessToken, AccessTokenOptional, AppserviceToken, AppserviceTokenOptional,
ServerSignatures,
};
use Error::BadRequest;
use ErrorKind::UnknownToken;
use Token::{Appservice, Expired, Invalid, User};
@@ -129,7 +133,7 @@ pub(super) async fn auth(
| (AccessToken, Appservice(info)) => Ok(auth_appservice(services, request, info).await?),
| (AccessToken, Token::None) => match metadata {
| (AccessToken | AppserviceToken, Token::None) => match metadata {
| &get_turn_server_info::v3::Request::METADATA
if services.server.config.turn_allow_guests =>
Ok(Auth::default()),
@@ -137,22 +141,25 @@ pub(super) async fn auth(
| _ => Err!(Request(MissingToken("Missing access token."))),
},
| (AccessToken | AccessTokenOptional | AuthScheme::None, User(user)) => Ok(Auth {
| (
AccessToken | AccessTokenOptional | AppserviceTokenOptional | AuthScheme::None,
User(user),
) => Ok(Auth {
sender_user: Some(user.0),
sender_device: Some(user.1),
_expires_at: user.2,
..Auth::default()
}),
//TODO: add AppserviceTokenOptional
| (AccessTokenOptional | AppserviceToken | AuthScheme::None, Appservice(info)) =>
Ok(Auth {
appservice_info: Some(*info),
..Auth::default()
}),
| (
AccessTokenOptional | AppserviceTokenOptional | AppserviceToken | AuthScheme::None,
Appservice(info),
) => Ok(Auth {
appservice_info: Some(*info),
..Auth::default()
}),
//TODO: add AppserviceTokenOptional
| (AccessTokenOptional | AppserviceToken | AuthScheme::None, Token::None) =>
| (AccessTokenOptional | AppserviceTokenOptional | AuthScheme::None, Token::None) =>
Ok(Auth::default()),
}
}
@@ -166,7 +173,7 @@ fn check_auth_still_required(services: &Services, metadata: &Metadata, token: &T
match metadata {
| &get_profile::v3::Request::METADATA
| &get_profile_key::unstable::Request::METADATA
| &get_profile_field::v3::Request::METADATA
| &get_display_name::v3::Request::METADATA
| &get_avatar_url::v3::Request::METADATA
| &get_timezone_key::unstable::Request::METADATA
@@ -302,11 +309,13 @@ async fn auth_server(
.server_keys
.get_verify_key(origin, &x_matrix.key)
.await
.map_err(|e| err!(Request(Forbidden(warn!("Failed to fetch signing keys: {e}")))))?;
.map_err(|e| {
err!(Request(Forbidden(debug_warn!("Failed to fetch signing keys: {e}"))))
})?;
let keys: PubKeys = [(x_matrix.key.to_string(), key.key)].into();
let keys: PubKeyMap = [(origin.as_str().into(), keys)].into();
if let Err(e) = ruma::signatures::verify_json(&keys, authorization) {
if let Err(e) = ruma::signatures::verify_json(&keys, &authorization) {
debug_error!("Failed to verify federation request from {origin}: {e}");
if request.parts.uri.to_string().contains('@') {
warn!(
@@ -360,7 +369,7 @@ async fn parse_x_matrix(request: &mut Request) -> Result<XMatrix> {
| _ => "Unknown header-related error",
};
err!(Request(Forbidden(warn!("{msg}: {e}"))))
err!(Request(Forbidden(debug_warn!("{msg}: {e}"))))
})?;
Ok(x_matrix)
+2 -6
View File
@@ -46,7 +46,6 @@ pub(crate) async fn get_backfill_route(
.stream()
.filter_map(|event_id| {
services
.rooms
.timeline
.get_pdu_count(event_id)
.map(Result::ok)
@@ -60,13 +59,11 @@ pub(crate) async fn get_backfill_route(
origin: services.globals.server_name().to_owned(),
pdus: services
.rooms
.timeline
.pdus_rev(None, &body.room_id, Some(from.saturating_add(1)))
.try_take(limit)
.try_filter_map(async |(_, pdu)| {
Ok(services
.rooms
.state_accessor
.server_can_see_event(body.origin(), &pdu.room_id, &pdu.event_id)
.await
@@ -74,7 +71,6 @@ pub(crate) async fn get_backfill_route(
})
.try_filter_map(async |pdu| {
Ok(services
.rooms
.timeline
.get_pdu_json(&pdu.event_id)
.await
@@ -82,8 +78,8 @@ pub(crate) async fn get_backfill_route(
})
.and_then(|pdu| {
services
.sending
.convert_to_outgoing_federation_event(pdu)
.federation
.format_pdu_into(pdu, None)
.map(Ok)
})
.try_collect()
+2 -3
View File
@@ -16,7 +16,6 @@ pub(crate) async fn get_event_route(
body: Ruma<get_event::v1::Request>,
) -> Result<get_event::v1::Response> {
let event = services
.rooms
.timeline
.get_pdu_json(&body.event_id)
.await
@@ -42,8 +41,8 @@ pub(crate) async fn get_event_route(
origin: services.globals.server_name().to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
pdu: services
.sending
.convert_to_outgoing_federation_event(event)
.federation
.format_pdu_into(event, None)
.await,
})
}
+2 -15
View File
@@ -30,7 +30,6 @@ pub(crate) async fn get_event_authorization_route(
.await?;
let event = services
.rooms
.timeline
.get_pdu_json(&body.event_id)
.await
@@ -45,23 +44,11 @@ pub(crate) async fn get_event_authorization_route(
.map_err(|_| Error::bad_database("Invalid room_id in event in database."))?;
let auth_chain = services
.rooms
.auth_chain
.event_ids_iter(room_id, once(body.event_id.borrow()))
.ready_filter_map(Result::ok)
.filter_map(async |id| {
services
.rooms
.timeline
.get_pdu_json(&id)
.await
.ok()
})
.then(|pdu| {
services
.sending
.convert_to_outgoing_federation_event(pdu)
})
.filter_map(async |id| services.timeline.get_pdu_json(&id).await.ok())
.then(|pdu| services.federation.format_pdu_into(pdu, None))
.collect()
.await;
+3 -9
View File
@@ -38,12 +38,7 @@ pub(crate) async fn get_missing_events_route(
let mut i: usize = 0;
while i < queued_events.len() && events.len() < limit {
let Ok(pdu) = services
.rooms
.timeline
.get_pdu(&queued_events[i])
.await
else {
let Ok(pdu) = services.timeline.get_pdu(&queued_events[i]).await else {
debug!(
?body.origin,
"Event {} does not exist locally, skipping", &queued_events[i]
@@ -58,7 +53,6 @@ pub(crate) async fn get_missing_events_route(
}
if !services
.rooms
.state_accessor
.server_can_see_event(body.origin(), &body.room_id, &queued_events[i])
.await
@@ -83,8 +77,8 @@ pub(crate) async fn get_missing_events_route(
let prev_events = pdu.prev_events.iter().map(ToOwned::to_owned);
let event = services
.sending
.convert_to_outgoing_federation_event(event)
.federation
.format_pdu_into(event, None)
.await;
queued_events.extend(prev_events);
+13 -19
View File
@@ -17,12 +17,7 @@ pub(crate) async fn get_hierarchy_route(
State(services): State<crate::State>,
body: Ruma<get_hierarchy::v1::Request>,
) -> Result<get_hierarchy::v1::Response> {
if !services
.rooms
.metadata
.exists(&body.room_id)
.await
{
if !services.metadata.exists(&body.room_id).await {
return Err!(Request(NotFound("Room does not exist.")));
}
@@ -30,7 +25,6 @@ pub(crate) async fn get_hierarchy_route(
let suggested_only = body.suggested_only;
let ref identifier = Identifier::ServerName(body.origin());
match services
.rooms
.spaces
.get_summary_and_children_local(room_id, identifier)
.await?
@@ -47,7 +41,6 @@ pub(crate) async fn get_hierarchy_route(
.stream()
.broad_filter_map(async |(child, _via)| {
match services
.rooms
.spaces
.get_summary_and_children_local(&child, identifier)
.await
@@ -64,17 +57,18 @@ pub(crate) async fn get_hierarchy_route(
})
.unzip()
.map(|(children, inaccessible_children): (Vec<_>, Vec<_>)| {
(
children
.into_iter()
.flatten()
.map(Into::into)
.collect(),
inaccessible_children
.into_iter()
.flatten()
.collect(),
)
let children = children
.into_iter()
.flatten()
.map(|parent| parent.summary)
.collect();
let inaccessible_children = inaccessible_children
.into_iter()
.flatten()
.collect();
(children, inaccessible_children)
})
.await;
+15 -14
View File
@@ -3,12 +3,15 @@
use base64::{Engine as _, engine::general_purpose};
use ruma::{
CanonicalJsonValue, OwnedUserId, UserId,
api::{client::error::ErrorKind, federation::membership::create_invite},
api::{
client::error::ErrorKind,
federation::membership::{RawStrippedState, create_invite},
},
events::room::member::{MembershipState, RoomMemberEventContent},
serde::JsonObject,
};
use tuwunel_core::{
Err, Error, Result, err,
Err, Error, Result, err, extract_variant,
matrix::{Event, PduEvent, event::gen_event_id},
utils,
utils::hash::sha256,
@@ -28,7 +31,6 @@ pub(crate) async fn create_invite_route(
) -> Result<create_invite::v2::Response> {
// ACL check origin
services
.rooms
.event_handler
.acl_check(body.origin(), &body.room_id)
.await?;
@@ -85,7 +87,6 @@ pub(crate) async fn create_invite_route(
// Make sure we're not ACL'ed from their room.
services
.rooms
.event_handler
.acl_check(invited_user.server_name(), &body.room_id)
.await?;
@@ -106,11 +107,8 @@ pub(crate) async fn create_invite_route(
.try_into()
.map_err(|e| err!(Request(InvalidParam("Invalid sender property: {e}"))))?;
if services
.rooms
.metadata
.is_banned(&body.room_id)
.await && !services.users.is_admin(&invited_user).await
if services.metadata.is_banned(&body.room_id).await
&& !services.users.is_admin(&invited_user).await
{
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
}
@@ -119,7 +117,12 @@ pub(crate) async fn create_invite_route(
return Err!(Request(Forbidden("This server does not allow room invites.")));
}
let mut invite_state = body.invite_room_state.clone();
let mut invite_state: Vec<_> = body
.invite_room_state
.clone()
.into_iter()
.filter_map(|s| extract_variant!(s, RawStrippedState::Stripped))
.collect();
let mut event: JsonObject = serde_json::from_str(body.event.get())
.map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?;
@@ -136,13 +139,11 @@ pub(crate) async fn create_invite_route(
// record the invited state for client /sync through update_membership(), and
// send the invite PDU to the relevant appservices.
if !services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), &body.room_id)
.await
{
services
.rooms
.state_cache
.update_membership(
&body.room_id,
@@ -177,8 +178,8 @@ pub(crate) async fn create_invite_route(
Ok(create_invite::v2::Response {
event: services
.sending
.convert_to_outgoing_federation_event(signed_event)
.federation
.format_pdu_into(signed_event, Some(&body.room_version))
.await,
})
}
+22 -45
View File
@@ -1,7 +1,7 @@
use axum::extract::State;
use futures::{StreamExt, pin_mut};
use futures::{StreamExt, TryFutureExt, pin_mut};
use ruma::{
CanonicalJsonObject, OwnedUserId, RoomId, RoomVersionId, UserId,
OwnedUserId, RoomId, RoomVersionId, UserId,
api::{client::error::ErrorKind, federation::membership::prepare_join_event},
events::{
StateEventType,
@@ -11,9 +11,8 @@
},
},
};
use serde_json::value::to_raw_value;
use tuwunel_core::{
Err, Error, Result, debug_info, matrix::pdu::PduBuilder, utils::IterStream, warn,
Err, Error, Result, at, debug_info, matrix::pdu::PduBuilder, utils::IterStream, warn,
};
use tuwunel_service::Services;
@@ -26,12 +25,7 @@ pub(crate) async fn create_join_event_template_route(
State(services): State<crate::State>,
body: Ruma<prepare_join_event::v1::Request>,
) -> Result<prepare_join_event::v1::Response> {
if !services
.rooms
.metadata
.exists(&body.room_id)
.await
{
if !services.metadata.exists(&body.room_id).await {
return Err!(Request(NotFound("Room is unknown to this server.")));
}
@@ -41,7 +35,6 @@ pub(crate) async fn create_join_event_template_route(
// ACL check origin server
services
.rooms
.event_handler
.acl_check(body.origin(), &body.room_id)
.await?;
@@ -74,10 +67,10 @@ pub(crate) async fn create_join_event_template_route(
}
let room_version_id = services
.rooms
.state
.get_room_version(&body.room_id)
.await?;
if !body.ver.contains(&room_version_id) {
return Err(Error::BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
@@ -85,12 +78,7 @@ pub(crate) async fn create_join_event_template_route(
));
}
let state_lock = services
.rooms
.state
.mutex
.lock(&body.room_id)
.await;
let state_lock = services.state.mutex.lock(&body.room_id).await;
let join_authorized_via_users_server: Option<OwnedUserId> = {
use RoomVersionId::*;
@@ -106,11 +94,10 @@ pub(crate) async fn create_join_event_template_route(
.await?
{
let users = services
.rooms
.state_cache
.local_users_in_room(&body.room_id)
.filter(|user| {
services.rooms.state_accessor.user_can_invite(
services.state_accessor.user_can_invite(
&body.room_id,
user,
&body.user_id,
@@ -132,8 +119,7 @@ pub(crate) async fn create_join_event_template_route(
}
};
let (_pdu, mut pdu_json) = services
.rooms
let pdu_json = services
.timeline
.create_hash_and_sign_event(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
@@ -144,16 +130,17 @@ pub(crate) async fn create_join_event_template_route(
&body.room_id,
&state_lock,
)
.map_ok(at!(1))
.await?;
drop(state_lock);
// room v3 and above removed the "event_id" field from remote PDU format
maybe_strip_event_id(&mut pdu_json, &room_version_id)?;
Ok(prepare_join_event::v1::Response {
room_version: Some(room_version_id),
event: to_raw_value(&pdu_json).expect("CanonicalJson can be serialized to JSON"),
room_version: Some(room_version_id.clone()),
event: services
.federation
.format_pdu_into(pdu_json, Some(&room_version_id))
.await,
})
}
@@ -172,7 +159,6 @@ pub(crate) async fn user_can_perform_restricted_join(
}
if services
.rooms
.state_cache
.is_joined(user_id, room_id)
.await
@@ -181,8 +167,15 @@ pub(crate) async fn user_can_perform_restricted_join(
return Ok(false);
}
if services
.state_cache
.is_invited(user_id, room_id)
.await
{
return Ok(true);
}
let Ok(join_rules_event_content) = services
.rooms
.state_accessor
.room_state_get_content::<RoomJoinRulesEventContent>(
room_id,
@@ -217,7 +210,6 @@ pub(crate) async fn user_can_perform_restricted_join(
.stream()
.any(|m| {
services
.rooms
.state_cache
.is_joined(user_id, &m.room_id)
})
@@ -230,18 +222,3 @@ pub(crate) async fn user_can_perform_restricted_join(
)))
}
}
pub(crate) fn maybe_strip_event_id(
pdu_json: &mut CanonicalJsonObject,
room_version_id: &RoomVersionId,
) -> Result {
use RoomVersionId::*;
match room_version_id {
| V1 | V2 => Ok(()),
| _ => {
pdu_json.remove("event_id");
Ok(())
},
}
}
+21 -33
View File
@@ -1,12 +1,12 @@
use RoomVersionId::*;
use axum::extract::State;
use futures::TryFutureExt;
use ruma::{
RoomVersionId,
api::{client::error::ErrorKind, federation::knock::create_knock_event_template},
api::{client::error::ErrorKind, federation::membership::prepare_knock_event},
events::room::member::{MembershipState, RoomMemberEventContent},
};
use serde_json::value::to_raw_value;
use tuwunel_core::{Err, Error, Result, debug_warn, matrix::pdu::PduBuilder, warn};
use tuwunel_core::{Err, Error, Result, at, debug_warn, matrix::pdu::PduBuilder, warn};
use crate::Ruma;
@@ -15,14 +15,9 @@
/// Creates a knock template.
pub(crate) async fn create_knock_event_template_route(
State(services): State<crate::State>,
body: Ruma<create_knock_event_template::v1::Request>,
) -> Result<create_knock_event_template::v1::Response> {
if !services
.rooms
.metadata
.exists(&body.room_id)
.await
{
body: Ruma<prepare_knock_event::v1::Request>,
) -> Result<prepare_knock_event::v1::Response> {
if !services.metadata.exists(&body.room_id).await {
return Err!(Request(NotFound("Room is unknown to this server.")));
}
@@ -32,7 +27,6 @@ pub(crate) async fn create_knock_event_template_route(
// ACL check origin server
services
.rooms
.event_handler
.acl_check(body.origin(), &body.room_id)
.await?;
@@ -62,35 +56,28 @@ pub(crate) async fn create_knock_event_template_route(
}
}
let room_version_id = services
.rooms
let room_version = services
.state
.get_room_version(&body.room_id)
.await?;
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6) {
if matches!(room_version, V1 | V2 | V3 | V4 | V5 | V6) {
return Err(Error::BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
ErrorKind::IncompatibleRoomVersion { room_version },
"Room version does not support knocking.",
));
}
if !body.ver.contains(&room_version_id) {
if !body.ver.contains(&room_version) {
return Err(Error::BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
ErrorKind::IncompatibleRoomVersion { room_version },
"Your homeserver does not support the features required to knock on this room.",
));
}
let state_lock = services
.rooms
.state
.mutex
.lock(&body.room_id)
.await;
let state_lock = services.state.mutex.lock(&body.room_id).await;
if let Ok(membership) = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
@@ -101,12 +88,12 @@ pub(crate) async fn create_knock_event_template_route(
&body.user_id,
&body.room_id
);
return Err!(Request(Forbidden("You cannot knock on a room you are banned from.")));
}
}
let (_pdu, mut pdu_json) = services
.rooms
let pdu_json = services
.timeline
.create_hash_and_sign_event(
PduBuilder::state(
@@ -117,15 +104,16 @@ pub(crate) async fn create_knock_event_template_route(
&body.room_id,
&state_lock,
)
.map_ok(at!(1))
.await?;
drop(state_lock);
// room v3 and above removed the "event_id" field from remote PDU format
super::maybe_strip_event_id(&mut pdu_json, &room_version_id)?;
let event = services
.federation
.format_pdu_into(pdu_json, Some(&room_version))
.await;
Ok(create_knock_event_template::v1::Response {
room_version: room_version_id,
event: to_raw_value(&pdu_json).expect("CanonicalJson can be serialized to JSON"),
})
// room v3 and above removed the "event_id" field from remote PDU format
Ok(prepare_knock_event::v1::Response { room_version, event })
}
+14 -26
View File
@@ -1,12 +1,11 @@
use axum::extract::State;
use futures::TryFutureExt;
use ruma::{
api::federation::membership::prepare_leave_event,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use serde_json::value::to_raw_value;
use tuwunel_core::{Err, Result, matrix::pdu::PduBuilder};
use tuwunel_core::{Err, Result, at, matrix::pdu::PduBuilder};
use super::make_join::maybe_strip_event_id;
use crate::Ruma;
/// # `GET /_matrix/federation/v1/make_leave/{roomId}/{eventId}`
@@ -16,12 +15,7 @@ pub(crate) async fn create_leave_event_template_route(
State(services): State<crate::State>,
body: Ruma<prepare_leave_event::v1::Request>,
) -> Result<prepare_leave_event::v1::Response> {
if !services
.rooms
.metadata
.exists(&body.room_id)
.await
{
if !services.metadata.exists(&body.room_id).await {
return Err!(Request(NotFound("Room is unknown to this server.")));
}
@@ -33,25 +27,19 @@ pub(crate) async fn create_leave_event_template_route(
// ACL check origin
services
.rooms
.event_handler
.acl_check(body.origin(), &body.room_id)
.await?;
let room_version_id = services
.rooms
let room_version = services
.state
.get_room_version(&body.room_id)
.map_ok(Some)
.await?;
let state_lock = services
.rooms
.state
.mutex
.lock(&body.room_id)
.await;
let (_pdu, mut pdu_json) = services
.rooms
let state_lock = services.state.mutex.lock(&body.room_id).await;
let pdu_json = services
.timeline
.create_hash_and_sign_event(
PduBuilder::state(
@@ -62,15 +50,15 @@ pub(crate) async fn create_leave_event_template_route(
&body.room_id,
&state_lock,
)
.map_ok(at!(1))
.await?;
drop(state_lock);
// room v3 and above removed the "event_id" field from remote PDU format
maybe_strip_event_id(&mut pdu_json, &room_version_id)?;
let event = services
.federation
.format_pdu_into(pdu_json, room_version.as_ref())
.await;
Ok(prepare_leave_event::v1::Response {
room_version: Some(room_version_id),
event: to_raw_value(&pdu_json).expect("CanonicalJson can be serialized to JSON"),
})
Ok(prepare_leave_event::v1::Response { room_version, event })
}
-2
View File
@@ -23,14 +23,12 @@ pub(crate) async fn get_room_information_route(
body: Ruma<get_room_information::v1::Request>,
) -> Result<get_room_information::v1::Response> {
let room_id = services
.rooms
.alias
.resolve_local_alias(&body.room_alias)
.await
.map_err(|_| err!(Request(NotFound("Room alias not found."))))?;
let mut servers: Vec<OwnedServerName> = services
.rooms
.state_cache
.room_servers(&room_id)
.map(ToOwned::to_owned)
+1 -15
View File
@@ -92,12 +92,7 @@ pub(crate) async fn send_transaction_message_route(
.pdus
.iter()
.stream()
.broad_then(|pdu| {
services
.rooms
.event_handler
.parse_incoming_pdu(pdu)
})
.broad_then(|pdu| services.event_handler.parse_incoming_pdu(pdu))
.inspect_err(|e| debug_warn!("Could not parse PDU: {e}"))
.ready_filter_map(Result::ok);
@@ -186,7 +181,6 @@ async fn handle_room(
pdus: impl Iterator<Item = Pdu> + Send,
) -> Result<ResolvedMap> {
let _room_lock = services
.rooms
.event_handler
.mutex_federation
.lock(room_id)
@@ -197,7 +191,6 @@ async fn handle_room(
services.server.check_running()?;
let pdu_start_time = Instant::now();
let result = services
.rooms
.event_handler
.handle_incoming_pdu(origin, &room_id, &event_id, value, true)
.map_ok(|_| ())
@@ -311,7 +304,6 @@ async fn handle_edu_receipt_room(
room_updates: ReceiptMap,
) {
if services
.rooms
.event_handler
.acl_check(origin, &room_id)
.await
@@ -351,7 +343,6 @@ async fn handle_edu_receipt_room_user(
}
if !services
.rooms
.state_cache
.server_in_room(origin, room_id)
.await
@@ -373,7 +364,6 @@ async fn handle_edu_receipt_room_user(
let receipts = [(ReceiptType::Read, BTreeMap::from(user_data))];
let content = [(event_id.clone(), BTreeMap::from(receipts))];
services
.rooms
.read_receipt
.readreceipt_update(user_id, room_id, &ReceiptEvent {
content: ReceiptEventContent(content.into()),
@@ -399,7 +389,6 @@ async fn handle_edu_typing(
}
if services
.rooms
.event_handler
.acl_check(typing.user_id.server_name(), &typing.room_id)
.await
@@ -413,7 +402,6 @@ async fn handle_edu_typing(
}
if !services
.rooms
.state_cache
.is_joined(&typing.user_id, &typing.room_id)
.await
@@ -430,7 +418,6 @@ async fn handle_edu_typing(
let timeout = millis_since_unix_epoch().saturating_add(secs.saturating_mul(1000));
services
.rooms
.typing
.typing_add(&typing.user_id, &typing.room_id, timeout)
.await
@@ -438,7 +425,6 @@ async fn handle_edu_typing(
.ok();
} else {
services
.rooms
.typing
.typing_remove(&typing.user_id, &typing.room_id)
.await
+47 -62
View File
@@ -3,17 +3,16 @@
use std::borrow::Borrow;
use axum::extract::State;
use futures::{FutureExt, StreamExt, TryStreamExt};
use futures::{FutureExt, StreamExt, TryStreamExt, future::try_join3};
use ruma::{
CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
ServerName,
OwnedEventId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName,
api::federation::membership::create_join_event,
events::{
StateEventType,
room::member::{MembershipState, RoomMemberEventContent},
},
};
use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
use serde_json::value::RawValue as RawJsonValue;
use tuwunel_core::{
Err, Result, at, err,
matrix::event::gen_event_id_canonical_json,
@@ -31,13 +30,12 @@ async fn create_join_event(
room_id: &RoomId,
pdu: &RawJsonValue,
) -> Result<create_join_event::v1::RoomState> {
if !services.rooms.metadata.exists(room_id).await {
if !services.metadata.exists(room_id).await {
return Err!(Request(NotFound("Room is unknown to this server.")));
}
// ACL check origin server
services
.rooms
.event_handler
.acl_check(origin, room_id)
.await?;
@@ -45,7 +43,6 @@ async fn create_join_event(
// We need to return the state prior to joining, let's keep a reference to that
// here
let shortstatehash = services
.rooms
.state
.get_room_shortstatehash(room_id)
.await
@@ -53,11 +50,7 @@ async fn create_join_event(
// We do not add the event_id field to the pdu here because of signature and
// hashes checks
let room_version_id = services
.rooms
.state
.get_room_version(room_id)
.await?;
let room_version_id = services.state.get_room_version(room_id).await?;
let Ok((event_id, mut value)) = gen_event_id_canonical_json(pdu, &room_version_id) else {
// Event could not be converted to canonical json
@@ -118,7 +111,6 @@ async fn create_join_event(
.map_err(|e| err!(Request(BadJson(warn!("sender property is not a valid user ID: {e}")))))?;
services
.rooms
.event_handler
.acl_check(sender.server_name(), room_id)
.await?;
@@ -159,7 +151,6 @@ async fn create_join_event(
}
if !services
.rooms
.state_cache
.is_joined(&authorising_user, room_id)
.await
@@ -199,14 +190,12 @@ async fn create_join_event(
.map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
let mutex_lock = services
.rooms
.event_handler
.mutex_federation
.lock(room_id)
.await;
let pdu_id = services
.rooms
.event_handler
.handle_incoming_pdu(&origin, room_id, &event_id, value.clone(), true)
.boxed()
@@ -216,59 +205,51 @@ async fn create_join_event(
drop(mutex_lock);
let state_ids: Vec<OwnedEventId> = services
.rooms
.state_accessor
.state_full_ids(shortstatehash)
.map(at!(1))
.collect()
.await;
let starting_events = state_ids.iter().map(Borrow::borrow);
let auth_chain = services
.auth_chain
.event_ids_iter(room_id, starting_events)
.broad_and_then(async |event_id| services.timeline.get_pdu_json(&event_id).await)
.broad_and_then(|pdu| {
services
.federation
.format_pdu_into(pdu, Some(&room_version_id))
.map(Ok)
})
.try_collect();
let state = state_ids
.iter()
.try_stream()
.broad_and_then(|event_id| services.rooms.timeline.get_pdu_json(event_id))
.broad_and_then(|event_id| services.timeline.get_pdu_json(event_id))
.broad_and_then(|pdu| {
services
.sending
.convert_to_outgoing_federation_event(pdu)
.federation
.format_pdu_into(pdu, Some(&room_version_id))
.map(Ok)
})
.try_collect()
.boxed()
.await?;
.try_collect();
let starting_events = state_ids.iter().map(Borrow::borrow);
let auth_chain = services
.rooms
.auth_chain
.event_ids_iter(room_id, starting_events)
.broad_and_then(async |event_id| {
services
.rooms
.timeline
.get_pdu_json(&event_id)
.await
})
.broad_and_then(|pdu| {
services
.sending
.convert_to_outgoing_federation_event(pdu)
.map(Ok)
})
.try_collect()
.boxed()
.await?;
let event = services
.federation
.format_pdu_into(value, Some(&room_version_id))
.map(Some)
.map(Ok);
let (auth_chain, state, event) = try_join3(auth_chain, state, event).await?;
services
.sending
.send_pdu_room(room_id, &pdu_id)
.await?;
Ok(create_join_event::v1::RoomState {
auth_chain,
state,
event: to_raw_value(&CanonicalJsonValue::Object(value)).ok(),
})
Ok(create_join_event::v1::RoomState { auth_chain, state, event })
}
/// # `PUT /_matrix/federation/v1/send_join/{roomId}/{eventId}`
@@ -289,6 +270,7 @@ pub(crate) async fn create_join_event_v1_route(
body.origin(),
&body.room_id,
);
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
}
@@ -304,17 +286,18 @@ pub(crate) async fn create_join_event_v1_route(
body.origin(),
&body.room_id,
);
return Err!(Request(Forbidden(warn!(
"Room ID server name {server} is banned on this homeserver."
))));
}
}
let room_state = create_join_event(&services, body.origin(), &body.room_id, &body.pdu)
.boxed()
.await?;
Ok(create_join_event::v1::Response { room_state })
Ok(create_join_event::v1::Response {
room_state: create_join_event(&services, body.origin(), &body.room_id, &body.pdu)
.boxed()
.await?,
})
}
/// # `PUT /_matrix/federation/v2/send_join/{roomId}/{eventId}`
@@ -344,6 +327,7 @@ pub(crate) async fn create_join_event_v2_route(
body.origin(),
&body.room_id,
);
return Err!(Request(Forbidden(warn!(
"Room ID server name {server} is banned on this homeserver."
))));
@@ -354,13 +338,14 @@ pub(crate) async fn create_join_event_v2_route(
create_join_event(&services, body.origin(), &body.room_id, &body.pdu)
.boxed()
.await?;
let room_state = create_join_event::v2::RoomState {
members_omitted: false,
auth_chain,
state,
event,
servers_in_room: None,
};
Ok(create_join_event::v2::Response { room_state })
Ok(create_join_event::v2::Response {
room_state: create_join_event::v2::RoomState {
members_omitted: false,
auth_chain,
state,
event,
servers_in_room: None,
},
})
}
+13 -17
View File
@@ -3,7 +3,7 @@
use ruma::{
OwnedServerName, OwnedUserId,
RoomVersionId::*,
api::federation::knock::send_knock,
api::federation::membership::create_knock_event,
events::{
StateEventType,
room::member::{MembershipState, RoomMemberEventContent},
@@ -23,8 +23,8 @@
/// Submits a signed knock event.
pub(crate) async fn create_knock_event_v1_route(
State(services): State<crate::State>,
body: Ruma<send_knock::v1::Request>,
) -> Result<send_knock::v1::Response> {
body: Ruma<create_knock_event::v1::Request>,
) -> Result<create_knock_event::v1::Response> {
if services
.config
.forbidden_remote_server_names
@@ -55,24 +55,17 @@ pub(crate) async fn create_knock_event_v1_route(
}
}
if !services
.rooms
.metadata
.exists(&body.room_id)
.await
{
if !services.metadata.exists(&body.room_id).await {
return Err!(Request(NotFound("Room is unknown to this server.")));
}
// ACL check origin server
services
.rooms
.event_handler
.acl_check(body.origin(), &body.room_id)
.await?;
let room_version_id = services
.rooms
.state
.get_room_version(&body.room_id)
.await?;
@@ -127,7 +120,6 @@ pub(crate) async fn create_knock_event_v1_route(
.map_err(|e| err!(Request(BadJson("Event sender is not a valid user ID: {e}"))))?;
services
.rooms
.event_handler
.acl_check(sender.server_name(), &body.room_id)
.await?;
@@ -168,14 +160,12 @@ pub(crate) async fn create_knock_event_v1_route(
.map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
let mutex_lock = services
.rooms
.event_handler
.mutex_federation
.lock(&body.room_id)
.await;
let pdu_id = services
.rooms
.event_handler
.handle_incoming_pdu(&origin, &body.room_id, &event_id, value.clone(), true)
.boxed()
@@ -189,7 +179,13 @@ pub(crate) async fn create_knock_event_v1_route(
.send_pdu_room(&body.room_id, &pdu_id)
.await?;
let knock_room_state = services.rooms.state.summary_stripped(&pdu).await;
Ok(send_knock::v1::Response { knock_room_state })
Ok(create_knock_event::v1::Response {
knock_room_state: services
.state
.summary_stripped(&pdu)
.await
.into_iter()
.map(Into::into)
.collect(),
})
}

Some files were not shown because too many files have changed in this diff Show More