Compare commits

...

41 Commits

Author SHA1 Message Date
Jade Ellis
082c44f355 fix: Only sync LDAP admin status when admin_filter is configured
Closes #1307
2026-02-15 16:17:26 +00:00
Jade Ellis
117c581948 fix: Correct incorrectly inverted boolean expression 2026-02-15 16:11:19 +00:00
timedout
cb846a3ad1 style: Invert pending_invite_state check 2026-02-15 16:11:19 +00:00
timedout
81b984b2cc style: Compress should_rescind_invite 2026-02-15 16:11:19 +00:00
timedout
e2961390ee feat: Support rescinding invites over federation 2026-02-15 16:11:19 +00:00
timedout
cb75e836e0 style: Update error messages in make_join.rs 2026-02-15 16:11:19 +00:00
nexy7574
cb7a988b1b chore: Add news frag 2026-02-15 16:11:19 +00:00
nexy7574
aa5400bcef style: Fix IncompatibleRoomVersion log line 2026-02-15 16:11:18 +00:00
nexy7574
ff4dddd673 fix: Refactor local join process 2026-02-15 16:11:18 +00:00
nexy7574
c22b17fb29 fix: Return accurate errors in make_join for restricted rooms 2026-02-15 16:11:18 +00:00
timedout
3da7fa24db fix: Produce more useful errors in make_join_request 2026-02-15 16:11:18 +00:00
timedout
d15ac1d3c1 fix: Use 404 instead of 400 (and include sender) 2026-02-15 15:55:36 +00:00
timedout
a9ebdf58e2 feat: Filter ignored PDUs in relations 2026-02-15 15:55:35 +00:00
timedout
f1ab27d344 feat: Return SENDER_IGNORED error for context and relations 2026-02-15 15:55:35 +00:00
timedout
8bc6e6ccca feat: Return SENDER_IGNORED error in is_ignored_pdu 2026-02-15 15:55:32 +00:00
Jade Ellis
60a3abe752 refactor: Use HashSet 2026-02-15 15:35:29 +00:00
Ellie
e3b874d336 fix(sync): handle wildcard state keys in sliding sync required_state 2026-02-15 15:35:29 +00:00
Jade Ellis
f3f82831b4 docs: Changelog 2026-02-15 15:23:15 +00:00
Jade Ellis
26aac1408e fix: Correct user agent changes
Correct the domain
Remove "embed" in the UA because the
global UA was modified, rather than
just the one for preview requests
2026-02-15 15:21:06 +00:00
Trash Panda
be8f62396a feat(core): Change default user agent 2026-02-15 15:21:06 +00:00
Trash Panda
40996a6602 feat(core): Add config option for the url preview user agent 2026-02-15 15:21:05 +00:00
Jade Ellis
9cae531f90 doc: Changelog 2026-02-15 15:19:03 +00:00
Jade Ellis
56eea935b6 feat: Deadlock detector thread 2026-02-15 15:19:02 +00:00
Renovate Bot
fcb646f8c4 chore(deps): update rust-patch-updates 2026-02-15 05:02:30 +00:00
Jade Ellis
57b21c1b32 docs: Add links to matrix guides 2026-02-14 19:29:07 +00:00
Ginger
8d66500c99 chore: Code cleanup 2026-02-14 14:12:57 -05:00
Simon Gardling
abacf1dc20 chore: News fragment 2026-02-14 14:12:42 -05:00
Simon Gardling
134e5cadaf fix(sliding-sync): Properly handle wildcard state_key
Fixes calls as described in https://forgejo.ellis.link/continuwuation/continuwuity/issues/1306
2026-02-14 14:12:35 -05:00
Renovate Bot
8ec0f0d830 chore(deps): update dependency @rspress/plugin-client-redirects to v2.0.3 2026-02-14 14:14:40 +00:00
Renovate Bot
0453544036 chore(deps): update dependency cargo-bins/cargo-binstall to v1.17.5 2026-02-14 05:03:21 +00:00
Jade Ellis
89ad809270 docs: Correct comment on rtc_focus_server_urls 2026-02-13 19:52:39 +00:00
Chris W Jones
ecd3a4eb41 build: Update ruwuma for RTC Foci responses 2026-02-13 19:52:39 +00:00
Chris W Jones
5506997ca0 feat: Add config option for livekit
This adds a new config option under `global.well_known` for livekit
server URLs.  It also updates the well_known client API endpoint to
return this list.

Closes #1355
2026-02-13 19:52:39 +00:00
Renovate Bot
abc0683d59 chore(deps): update dependency @rspress/core to v2.0.3 2026-02-13 19:32:56 +00:00
Renovate Bot
dd60beb9fb chore(deps): update dependency @rspress/plugin-sitemap to v2.0.3 2026-02-13 05:04:20 +00:00
arxari
d9520f9382 Change the federation testing site in the docs to a more verbose one
The new site is easy to use at a glance but provides more advanced info if needed

Nexxy approved https://matrix.to/#/#offtopic:continuwuity.org/$rHSywj-s3v9onrROBcwDCHnnOpPVFbu0-Xgrh9A4btw
2026-02-12 20:13:47 +00:00
arxari
40bb5366bb Change the federation testing site to a more verbose one
The new site is easy to use at a glance but provides more advanced info if needed

Nexxy approved https://matrix.to/#/#offtopic:continuwuity.org/$rHSywj-s3v9onrROBcwDCHnnOpPVFbu0-Xgrh9A4btw
2026-02-12 20:11:20 +00:00
timedout
f82bd77073 style: Fix clippy issues 2026-02-12 19:10:13 +00:00
timedout
7d84ba5ff2 fix: Don't include latest_events in output 2026-02-12 17:37:29 +00:00
timedout
69a8937584 fix: Complement runner 2026-02-12 17:23:39 +00:00
timedout
b2ec13d342 fix: Redo the get_missing_events federation route 2026-02-12 16:48:12 +00:00
42 changed files with 804 additions and 578 deletions

View File

@@ -1,9 +1,9 @@
# Local build and dev artifacts
target/
!target/debug/conduwuit
# Docker files
Dockerfile*
docker/
# IDE files
.vscode

107
Cargo.lock generated
View File

@@ -841,7 +841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77"
dependencies = [
"serde",
"toml 0.9.11+spec-1.1.0",
"toml 0.9.12+spec-1.1.0",
]
[[package]]
@@ -917,9 +917,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.57"
version = "4.5.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a"
checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806"
dependencies = [
"clap_builder",
"clap_derive",
@@ -927,9 +927,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.57"
version = "4.5.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238"
checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2"
dependencies = [
"anstyle",
"clap_lex",
@@ -949,9 +949,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.7"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]]
name = "cmake"
@@ -1030,6 +1030,7 @@ dependencies = [
"opentelemetry",
"opentelemetry-otlp",
"opentelemetry_sdk",
"parking_lot",
"sentry",
"sentry-tower",
"sentry-tracing",
@@ -1149,14 +1150,14 @@ dependencies = [
"serde_json",
"serde_regex",
"smallstr",
"smallvec 1.15.1",
"smallvec",
"thiserror 2.0.18",
"tikv-jemalloc-ctl",
"tikv-jemalloc-sys",
"tikv-jemallocator",
"tokio",
"tokio-metrics",
"toml 0.9.11+spec-1.1.0",
"toml 0.9.12+spec-1.1.0",
"tracing",
"tracing-core",
"tracing-subscriber",
@@ -1343,7 +1344,7 @@ dependencies = [
[[package]]
name = "continuwuity-admin-api"
version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"ruma-common",
"serde",
@@ -1758,7 +1759,7 @@ dependencies = [
[[package]]
name = "draupnir-antispam"
version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"ruma-common",
"serde",
@@ -1917,7 +1918,7 @@ dependencies = [
"lebe",
"miniz_oxide",
"rayon-core",
"smallvec 1.15.1",
"smallvec",
"zune-inflate",
]
@@ -2152,7 +2153,7 @@ checksum = "3a74b56a4039a46e8c91cc9d84e8a7df4e1f8b24239ca57d1304b3263cb599b9"
dependencies = [
"compact_str",
"garde_derive",
"smallvec 1.15.1",
"smallvec",
]
[[package]]
@@ -2364,7 +2365,7 @@ dependencies = [
"rand 0.9.2",
"resolv-conf",
"serde",
"smallvec 1.15.1",
"smallvec",
"thiserror 2.0.18",
"tokio",
"tracing",
@@ -2482,7 +2483,7 @@ dependencies = [
"itoa",
"pin-project-lite",
"pin-utils",
"smallvec 1.15.1",
"smallvec",
"tokio",
"want",
]
@@ -2577,7 +2578,7 @@ dependencies = [
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec 1.15.1",
"smallvec",
"zerovec",
]
@@ -2635,7 +2636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec 1.15.1",
"smallvec",
"utf8_iter",
]
@@ -2920,9 +2921,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
[[package]]
name = "libc"
version = "0.2.180"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "libfuzzer-sys"
@@ -3116,7 +3117,7 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "meowlnir-antispam"
version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"ruma-common",
"serde",
@@ -3224,7 +3225,7 @@ dependencies = [
"equivalent",
"parking_lot",
"portable-atomic",
"smallvec 1.15.1",
"smallvec",
"tagptr",
"uuid",
]
@@ -3721,7 +3722,7 @@ dependencies = [
"libc",
"petgraph",
"redox_syscall",
"smallvec 1.15.1",
"smallvec",
"windows-link",
]
@@ -4389,7 +4390,7 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.10.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"assign",
"continuwuity-admin-api",
@@ -4412,7 +4413,7 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.10.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"js_int",
"ruma-common",
@@ -4424,7 +4425,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.18.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"as_variant",
"assign",
@@ -4447,7 +4448,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"as_variant",
"base64 0.22.1",
@@ -4466,7 +4467,7 @@ dependencies = [
"serde",
"serde_html_form",
"serde_json",
"smallvec 1.15.1",
"smallvec",
"thiserror 2.0.18",
"time",
"tracing",
@@ -4479,7 +4480,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.28.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"as_variant",
"indexmap",
@@ -4493,7 +4494,7 @@ dependencies = [
"ruma-macros",
"serde",
"serde_json",
"smallvec 1.15.1",
"smallvec",
"thiserror 2.0.18",
"tracing",
"url",
@@ -4504,7 +4505,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"bytes",
"headers",
@@ -4526,7 +4527,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.9.5"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"js_int",
"thiserror 2.0.18",
@@ -4535,7 +4536,7 @@ dependencies = [
[[package]]
name = "ruma-identity-service-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"js_int",
"ruma-common",
@@ -4545,7 +4546,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"cfg-if",
"proc-macro-crate",
@@ -4560,7 +4561,7 @@ dependencies = [
[[package]]
name = "ruma-push-gateway-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"js_int",
"ruma-common",
@@ -4572,7 +4573,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.15.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
dependencies = [
"base64 0.22.1",
"ed25519-dalek",
@@ -4751,12 +4752,12 @@ dependencies = [
[[package]]
name = "saphyr-parser-bw"
version = "0.0.607"
version = "0.0.608"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f9bae8d059bf1ca32753cf3cdafbf5d391502de2fc2ca54510811fe9c100d90"
checksum = "d55ae5ea09894b6d5382621db78f586df37ef18ab581bf32c754e75076b124b1"
dependencies = [
"arraydeque",
"smallvec 2.0.0-alpha.12",
"smallvec",
"thiserror 2.0.18",
]
@@ -4963,9 +4964,9 @@ dependencies = [
[[package]]
name = "serde-saphyr"
version = "0.0.17"
version = "0.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc14a55107113a16346915d7e3d78acc539a923458385db89670e22cac106d7a"
checksum = "191a4f997fef5e095212c5790898516e9567d2d8502c4159317603ff0321e394"
dependencies = [
"ahash",
"annotate-snippets",
@@ -4981,7 +4982,7 @@ dependencies = [
"saphyr-parser-bw",
"serde",
"serde_json",
"smallvec 2.0.0-alpha.12",
"smallvec",
"validator",
"zmij",
]
@@ -5195,7 +5196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862077b1e764f04c251fe82a2ef562fd78d7cadaeb072ca7c2bcaf7217b1ff3b"
dependencies = [
"serde",
"smallvec 1.15.1",
"smallvec",
]
[[package]]
@@ -5207,12 +5208,6 @@ dependencies = [
"serde",
]
[[package]]
name = "smallvec"
version = "2.0.0-alpha.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef784004ca8777809dcdad6ac37629f0a97caee4c685fcea805278d81dd8b857"
[[package]]
name = "socket2"
version = "0.5.10"
@@ -5330,9 +5325,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
[[package]]
name = "syn"
version = "2.0.114"
version = "2.0.115"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12"
dependencies = [
"proc-macro2",
"quote",
@@ -5657,9 +5652,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.9.11+spec-1.1.0"
version = "0.9.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
dependencies = [
"indexmap",
"serde_core",
@@ -5716,9 +5711,9 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.0.6+spec-1.1.0"
version = "1.0.8+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc"
dependencies = [
"winnow",
]
@@ -5903,7 +5898,7 @@ checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc"
dependencies = [
"js-sys",
"opentelemetry",
"smallvec 1.15.1",
"smallvec",
"tracing",
"tracing-core",
"tracing-log",
@@ -5922,7 +5917,7 @@ dependencies = [
"once_cell",
"regex-automata",
"sharded-slab",
"smallvec 1.15.1",
"smallvec",
"thread_local",
"tracing",
"tracing-core",

View File

@@ -158,7 +158,7 @@ features = ["raw_value"]
# Used for appservice registration files
[workspace.dependencies.serde-saphyr]
version = "0.0.17"
version = "0.0.18"
# Used to load forbidden room/user regex from config
[workspace.dependencies.serde_regex]
@@ -342,7 +342,7 @@ version = "0.1.2"
# Used for matrix spec type definitions and helpers
[workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
rev = "458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
rev = "b496b7f38d517149361a882e75d3fd4faf210441"
features = [
"compat",
"rand",
@@ -378,7 +378,8 @@ features = [
"unstable-msc4210", # remove legacy mentions
"unstable-extensible-events",
"unstable-pdu",
"unstable-msc4155"
"unstable-msc4155",
"unstable-msc4143", # livekit well_known response
]
[workspace.dependencies.rust-rocksdb]

View File

@@ -0,0 +1 @@
LDAP-enabled servers will no longer have all admins demoted when LDAP-controlled admins are not configured. Contributed by @Jade

View File

@@ -0,0 +1,2 @@
Added unstable support for [MSC4406: `M_SENDER_IGNORED`](https://github.com/matrix-org/matrix-spec-proposals/pull/4406).
Contributed by @nex

1
changelog.d/1344.misc.md Normal file
View File

@@ -0,0 +1 @@
Continuwuity will now print information to the console when it detects a deadlock

1
changelog.d/1368.feature Normal file
View File

@@ -0,0 +1 @@
Improved the handling of restricted join rules and improved the performance of local-first joins. Contributed by @nex.

View File

@@ -0,0 +1 @@
Fixed sliding sync not resolving wildcard state key requests, enabling Video/Audio calls in Element X.

View File

@@ -0,0 +1 @@
You can now set a custom User Agent for URL previews; the default one has been modified to be less likely to be rejected. Contributed by @trashpanda

View File

@@ -1474,6 +1474,10 @@
#
#url_preview_check_root_domain = false
# User agent that is used specifically when fetching url previews.
#
#url_preview_user_agent = "continuwuity/<version> (bot; +https://continuwuity.org)"
# List of forbidden room aliases and room IDs as strings of regex
# patterns.
#
@@ -1820,6 +1824,17 @@
#
#support_mxid =
# A list of MatrixRTC foci URLs which will be served as part of the
# MSC4143 client endpoint at /.well-known/matrix/client. If you're
# setting up livekit, you'd want something like:
# rtc_focus_server_urls = [
# { type = "livekit", livekit_service_url = "https://livekit.example.com" },
# ]
#
# To disable, set this to be an empty vector (`[]`).
#
#rtc_focus_server_urls = []
[global.blurhashing]
# blurhashing x component, 4 is recommended by https://blurha.sh/

View File

@@ -48,7 +48,7 @@ EOF
# Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.17.4
ENV BINSTALL_VERSION=1.17.5
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree

View File

@@ -2,9 +2,9 @@ FROM ubuntu:latest
EXPOSE 8008
EXPOSE 8448
RUN apt-get update && apt-get install -y ca-certificates liburing2 && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /etc/continuwuity /var/lib/continuwuity
COPY docker/complement-entrypoint.sh /usr/local/bin/complement-entrypoint.sh
COPY docker/complement.config.toml /etc/continuwuity/config.toml
RUN mkdir -p /etc/continuwuity /var/lib/continuwuity /usr/local/bin/
COPY complement/complement-entrypoint.sh /usr/local/bin/complement-entrypoint.sh
COPY complement/complement.config.toml /etc/continuwuity/config.toml
COPY target/debug/conduwuit /usr/local/bin/conduwuit
RUN chmod +x /usr/local/bin/conduwuit /usr/local/bin/complement-entrypoint.sh
#HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://localhost:8008/_continuwuity/server_version || exit 1

View File

@@ -18,7 +18,7 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
# Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.17.4
ENV BINSTALL_VERSION=1.17.5
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree

View File

@@ -269,7 +269,7 @@ # If federation is enabled
```
- To check if your server can communicate with other homeservers, use the
[Matrix Federation Tester](https://federationtester.matrix.org/). If you can
[Matrix Federation Tester](https://federationtester.mtrnord.blog/). If you can
register but cannot join federated rooms, check your configuration and verify
that port 8448 is open and forwarded correctly.

View File

@@ -19,6 +19,16 @@
src: /assets/logo.svg
alt: continuwuity logo
beforeFeatures:
- title: Matrix for Discord users
details: New to Matrix? Learn how Matrix compares to Discord
link: https://joinmatrix.org/guide/matrix-vs-discord/
buttonText: Find Out the Difference
- title: How Matrix Works
details: Learn how Matrix works under the hood, and what that means
link: https://matrix.org/docs/matrix-concepts/elements-of-matrix/
buttonText: Read the Guide
features:
- title: 🚀 High Performance
details: Built with Rust for exceptional speed and efficiency. Designed to run smoothly even on modest hardware.

163
package-lock.json generated
View File

@@ -119,14 +119,13 @@
}
},
"node_modules/@rsbuild/core": {
"version": "2.0.0-beta.1",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.1.tgz",
"integrity": "sha512-m7L3oi4evTDODcY+Qk3cmY/p7GCaauSRe00D0AkXVohNvxFBt7F49uPwBSThS24I9d31zFuAED2jFqBeBlDqWw==",
"version": "2.0.0-beta.3",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.3.tgz",
"integrity": "sha512-dfH+Pt2GuF3rWOWGsf5XOhn3Zarvr4DoHwoI1arAsCGvpzoeud3DNGmWPy13tngj0r/YvQRcPTRBCRV4RP5CMw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rspack/core": "2.0.0-alpha.1",
"@rspack/core": "2.0.0-beta.0",
"@swc/helpers": "^0.5.18",
"jiti": "^2.6.1"
},
@@ -160,28 +159,28 @@
}
},
"node_modules/@rspack/binding": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-alpha.1.tgz",
"integrity": "sha512-Glz0SNFYPtNVM+ExJ4ocSzW+oQhb1iHTmxqVEAILbL17Hq3N/nwZpo1cWEs6hJjn8cosJIb1VKbbgb/1goEtCQ==",
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-beta.0.tgz",
"integrity": "sha512-L6PPqhwZWC2vzwdhBItNPXw+7V4sq+MBDRXLdd8NMqaJSCB5iKdJIbpbEQucST9Nn7V28IYoQTXs6+ol5vWUBA==",
"dev": true,
"license": "MIT",
"optionalDependencies": {
"@rspack/binding-darwin-arm64": "2.0.0-alpha.1",
"@rspack/binding-darwin-x64": "2.0.0-alpha.1",
"@rspack/binding-linux-arm64-gnu": "2.0.0-alpha.1",
"@rspack/binding-linux-arm64-musl": "2.0.0-alpha.1",
"@rspack/binding-linux-x64-gnu": "2.0.0-alpha.1",
"@rspack/binding-linux-x64-musl": "2.0.0-alpha.1",
"@rspack/binding-wasm32-wasi": "2.0.0-alpha.1",
"@rspack/binding-win32-arm64-msvc": "2.0.0-alpha.1",
"@rspack/binding-win32-ia32-msvc": "2.0.0-alpha.1",
"@rspack/binding-win32-x64-msvc": "2.0.0-alpha.1"
"@rspack/binding-darwin-arm64": "2.0.0-beta.0",
"@rspack/binding-darwin-x64": "2.0.0-beta.0",
"@rspack/binding-linux-arm64-gnu": "2.0.0-beta.0",
"@rspack/binding-linux-arm64-musl": "2.0.0-beta.0",
"@rspack/binding-linux-x64-gnu": "2.0.0-beta.0",
"@rspack/binding-linux-x64-musl": "2.0.0-beta.0",
"@rspack/binding-wasm32-wasi": "2.0.0-beta.0",
"@rspack/binding-win32-arm64-msvc": "2.0.0-beta.0",
"@rspack/binding-win32-ia32-msvc": "2.0.0-beta.0",
"@rspack/binding-win32-x64-msvc": "2.0.0-beta.0"
}
},
"node_modules/@rspack/binding-darwin-arm64": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-alpha.1.tgz",
"integrity": "sha512-+6E6pYgpKvs41cyOlqRjpCT3djjL9hnntF61JumM/TNo1aTYXMNNG4b8ZsLMpBq5ZwCy9Dg8oEDe8AZ84rfM7A==",
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-beta.0.tgz",
"integrity": "sha512-PPx1+SPEROSvDKmBuCbsE7W9tk07ajPosyvyuafv2wbBI6PW2rNcz62uzpIFS+FTgwwZ5u/06WXRtlD2xW9bKg==",
"cpu": [
"arm64"
],
@@ -193,9 +192,9 @@
]
},
"node_modules/@rspack/binding-darwin-x64": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-alpha.1.tgz",
"integrity": "sha512-Ccf9NNupVe67vlaS9zKQJ+BvsAn385uBC1vXnYaUxxHoY/tEwNJf6t+XyDARt7mCtT7+Bu4L/iJ/JEF/MsO5zg==",
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-beta.0.tgz",
"integrity": "sha512-GucsfjrSKBZ9cuOTXmHWxeY2wPmaNyvGNxTyzttjRcfwqOWz8r+ku6PCsMSXUqxZRYWW1L9mvtTdlDrzTYJZ0w==",
"cpu": [
"x64"
],
@@ -207,9 +206,9 @@
]
},
"node_modules/@rspack/binding-linux-arm64-gnu": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-alpha.1.tgz",
"integrity": "sha512-B7omNsPSsinOq2VRD4d4VFrLgHceMQobqlLg0txFUZ7PDjE307gpTcGViWQlUhNCbkZXMPzDeXBFa5ZlEmxgnA==",
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-beta.0.tgz",
"integrity": "sha512-nTtYtklRZD4sb2RIFCF9YS8tZ/MjpqIBKVS3YIvdXcfHUdVfmQHTZGtwEuZGg6AxTC5L1hcvkYmTXCG0ok7auw==",
"cpu": [
"arm64"
],
@@ -221,9 +220,9 @@
]
},
"node_modules/@rspack/binding-linux-arm64-musl": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-alpha.1.tgz",
"integrity": "sha512-NCG401ofZcDKlTWD8VHv76Y+02Stmd9Nu5MRbVUBOCTVgXMj8Mgrm5XsGBWUjzd5J/Mvo2hstCKIZxNzmPd8uQ==",
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-beta.0.tgz",
"integrity": "sha512-S2fshx0Rf7/XYwoMLaqFsVg4y+VAfHzubrczy8AW5xIs6UNC3eRLVTgShLerUPtF6SG+v6NQxQ9JI3vOo2qPOA==",
"cpu": [
"arm64"
],
@@ -235,9 +234,9 @@
]
},
"node_modules/@rspack/binding-linux-x64-gnu": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-alpha.1.tgz",
"integrity": "sha512-Xgp8wJ5gjpPG8I3VMEsVAesfckWryQVUhJkHcxPfNi72QTv8UkMER7Jl+JrlQk7K7nMO5ltokx/VGl1c3tMx+w==",
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-beta.0.tgz",
"integrity": "sha512-yx5Fk1gl7lfkvqcjolNLCNeduIs6C2alMsQ/kZ1pLeP5MPquVOYNqs6EcDPIp+fUjo3lZYtnJBiZKK+QosbzYg==",
"cpu": [
"x64"
],
@@ -249,9 +248,9 @@
]
},
"node_modules/@rspack/binding-linux-x64-musl": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-alpha.1.tgz",
"integrity": "sha512-lrYKcOgsPA1UMswxzFAV37ofkznbtTLCcEas6lxtlT3Dr28P6VRzC8TgVbIiprkm10I0BlThQWDJ3aGzzLj9Kg==",
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-beta.0.tgz",
"integrity": "sha512-sBX4b2W0PgehlAVT224k0Q6GaH6t9HP+hBNDrbX/g6d0hfxZN56gm5NfOTOD1Rien4v7OBEejJ3/uFbm1WjwYQ==",
"cpu": [
"x64"
],
@@ -263,9 +262,9 @@
]
},
"node_modules/@rspack/binding-wasm32-wasi": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-alpha.1.tgz",
"integrity": "sha512-rppGiT7CtXlM8st+IgzBDqb7V//1xx5Oe0SY1sxxw0cfOGMpIQCwhJqx/uI6ioqJLZLGX/obt359+hPXyqGl4w==",
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-beta.0.tgz",
"integrity": "sha512-o6OatnNvb4kCzXbCaomhENGaCsO3naIyAqqErew90HeAwa1lfY3NhRfDLeIyuANQ+xqFl34/R7n8q3ZDx3nd4Q==",
"cpu": [
"wasm32"
],
@@ -277,9 +276,9 @@
}
},
"node_modules/@rspack/binding-win32-arm64-msvc": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-alpha.1.tgz",
"integrity": "sha512-yD2g1JmnCxrix/344r7lBn+RH+Nv8uWP0UDP8kwv4kQGCWr4U7IP8PKFpoyulVOgOUjvJpgImeyrDJ7R8he+5w==",
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-beta.0.tgz",
"integrity": "sha512-neCzVllXzIqM8p8qKb89qV7wyk233gC/V9VrHIKbGeQjAEzpBsk5GOWlFbq5DDL6tivQ+uzYaTrZWm9tb2qxXg==",
"cpu": [
"arm64"
],
@@ -291,9 +290,9 @@
]
},
"node_modules/@rspack/binding-win32-ia32-msvc": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-alpha.1.tgz",
"integrity": "sha512-5qpQL5Qz3uYb56pwffEGzznXSX9TNkLpigQbIObfnUwX7WkdjgTT7oTHpjn2sRSLLNiJ/jCp2r4ZHvjmnNRsRA==",
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-beta.0.tgz",
"integrity": "sha512-/f0n2eO+DxMKQm9IebeMQJITx8M/+RvY/i8d3sAQZBgR53izn8y7EcDlidXpr24/2DvkLbiub8IyCKPlhLB+1A==",
"cpu": [
"ia32"
],
@@ -305,9 +304,9 @@
]
},
"node_modules/@rspack/binding-win32-x64-msvc": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-alpha.1.tgz",
"integrity": "sha512-dZ76NN9tXLaF2gnB/pU+PcK4Adf9tj8dY06KcWk5F81ur2V4UbrMfkWJkQprur8cgL/F49YtFMRWa4yp/qNbpQ==",
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-beta.0.tgz",
"integrity": "sha512-dx4zgiAT88EQE7kEUpr7Z9EZAwLnO5FhzWzvd/cDK4bkqYsx+rTklgf/c0EYPBeroXCxlGiMsuC9wHAFNK7sFw==",
"cpu": [
"x64"
],
@@ -319,13 +318,13 @@
]
},
"node_modules/@rspack/core": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-alpha.1.tgz",
"integrity": "sha512-2KK3hbxrRqzxtzg+ka7LsiEKIWIGIQz317k9HHC2U4IC5yLJ31K8y/vQfA1aIT2QcFls9gW7GyRjp8A4X5cvLA==",
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-beta.0.tgz",
"integrity": "sha512-aEqlQQjiXixT5i9S4DFtiAap8ZjF6pOgfY2ALHOizins/QqWyB8dyLxSoXdzt7JixmKcFmHkbL9XahO28BlVUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rspack/binding": "2.0.0-alpha.1",
"@rspack/binding": "2.0.0-beta.0",
"@rspack/lite-tapable": "1.1.0"
},
"engines": {
@@ -372,21 +371,20 @@
}
},
"node_modules/@rspress/core": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.2.tgz",
"integrity": "sha512-tU8rUVaPyC8o8k4ezgigRVQuZhBAC41KWdwZZ0BldN6o+QXSEIb722RnxCTpa9FGK2riqcwJgM+OqqcqXsFpmw==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.3.tgz",
"integrity": "sha512-a+JJFiALqMxGJBqR38/lkN6tas42UF4jRIhu6RilC/3DdqpfqR8j6jjQFOmqoNKo6ZGXW2W+i1Pscn6drvoG3w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@mdx-js/mdx": "^3.1.1",
"@mdx-js/react": "^3.1.1",
"@rsbuild/core": "2.0.0-beta.1",
"@rsbuild/core": "2.0.0-beta.3",
"@rsbuild/plugin-react": "~1.4.5",
"@rspress/shared": "2.0.2",
"@rspress/shared": "2.0.3",
"@shikijs/rehype": "^3.21.0",
"@types/unist": "^3.0.3",
"@unhead/react": "^2.1.2",
"@unhead/react": "^2.1.4",
"body-scroll-lock": "4.0.0-beta.0",
"cac": "^6.7.14",
"chokidar": "^3.6.0",
@@ -430,39 +428,39 @@
}
},
"node_modules/@rspress/plugin-client-redirects": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.2.tgz",
"integrity": "sha512-FOxUBDOGP06+1hL4jgbIxUe0XoEduXIQ0rSjWjzpo2mC+qTdhZUGJ0xYE2laQIfJXYv/up5zk25zjxUBnxsejw==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.3.tgz",
"integrity": "sha512-9+SoAbfoxM6OCRWx8jWHHi2zwJDcNaej/URx0CWZk8tvQ618yJW5mXJydknlac62399eYh/F7C3w8TZM3ORGVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"@rspress/core": "^2.0.2"
"@rspress/core": "^2.0.3"
}
},
"node_modules/@rspress/plugin-sitemap": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.2.tgz",
"integrity": "sha512-3E0yEif4Pj3RX+QVOsyWXW6IIjuhwh93bhVSmhShmTKi8opH5vnHcRVZZ1z7X/P3MHXFTrC925F8383Sl2qOEg==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.3.tgz",
"integrity": "sha512-SKa7YEAdkUqya2YjMKbakg3kcYMkXgXhTQdDsHd+QlJWN8j8cDPiCcctMZu8iIPeKZlb+hTJkTWvh27LSIKdOA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"@rspress/core": "^2.0.2"
"@rspress/core": "^2.0.3"
}
},
"node_modules/@rspress/shared": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.2.tgz",
"integrity": "sha512-9+QC8UL1gV2KpRZx4n55vAl6bE38y7eDnGJhdFSHdJkpFbUCiJDk9ZcR6jD/Rrtq7vlT0gfumUk640pxpi3IDQ==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.3.tgz",
"integrity": "sha512-yI9G4P165fSsmm6QoYTUrdgUis1aFnDh04GcM4SQIpL3itvEZhGtItgoeGkX9EWbnEjhriwI8mTqDDJIp+vrGA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rsbuild/core": "2.0.0-beta.1",
"@rsbuild/core": "2.0.0-beta.3",
"@shikijs/rehype": "^3.21.0",
"gray-matter": "4.0.3",
"lodash-es": "^4.17.23",
@@ -564,7 +562,6 @@
"integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.8.0"
}
@@ -667,13 +664,13 @@
"license": "ISC"
},
"node_modules/@unhead/react": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.2.tgz",
"integrity": "sha512-VNKa0JJZq5Jp28VuiOMfjAA7CTLHI0SdW/Hs1ZPq2PsNV/cgxGv8quFBGXWx4gfoHB52pejO929RKjIpYX5+iQ==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.4.tgz",
"integrity": "sha512-3DzMi5nJkUyLVfQF/q78smCvcSy84TTYgTwXVz5s3AjUcLyHro5Z7bLWriwk1dn5+YRfEsec8aPkLCMi5VjMZg==",
"dev": true,
"license": "MIT",
"dependencies": {
"unhead": "2.1.2"
"unhead": "2.1.4"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
@@ -688,7 +685,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -942,7 +938,8 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/debug": {
"version": "4.4.3",
@@ -2972,7 +2969,6 @@
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -2983,7 +2979,6 @@
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -3020,7 +3015,6 @@
"integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3488,7 +3482,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -3570,9 +3563,9 @@
}
},
"node_modules/unhead": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.2.tgz",
"integrity": "sha512-vSihrxyb+zsEUfEbraZBCjdE0p/WSoc2NGDrpwwSNAwuPxhYK1nH3eegf02IENLpn1sUhL8IoO84JWmRQ6tILA==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.4.tgz",
"integrity": "sha512-+5091sJqtNNmgfQ07zJOgUnMIMKzVKAWjeMlSrTdSGPB6JSozhpjUKuMfWEoLxlMAfhIvgOU8Me0XJvmMA/0fA==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -140,7 +140,6 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
self.services.globals.server_name().to_owned(),
room_server_name.to_owned(),
],
None,
&None,
)
.await
@@ -549,7 +548,6 @@ pub(super) async fn force_join_list_of_local_users(
&room_id,
Some(String::from(BULK_JOIN_REASON)),
&servers,
None,
&None,
)
.await
@@ -635,7 +633,6 @@ pub(super) async fn force_join_all_local_users(
&room_id,
Some(String::from(BULK_JOIN_REASON)),
&servers,
None,
&None,
)
.await
@@ -675,8 +672,7 @@ 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)
.await?;
join_room_by_id_helper(self.services, &user_id, &room_id, None, &servers, &None).await?;
self.write_str(&format!("{user_id} has been joined to {room_id}.",))
.await

View File

@@ -583,7 +583,6 @@ pub(crate) async fn register_route(
&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()

View File

@@ -16,7 +16,10 @@
use crate::{
Ruma,
client::message::{event_filter, ignored_filter, lazy_loading_witness, visibility_filter},
client::{
is_ignored_pdu,
message::{event_filter, ignored_filter, lazy_loading_witness, visibility_filter},
},
};
const LIMIT_MAX: usize = 100;
@@ -78,6 +81,9 @@ pub(crate) async fn get_context_route(
return Err!(Request(NotFound("Event not found.")));
}
// Return M_SENDER_IGNORED if the sender of base_event is ignored (MSC4406)
is_ignored_pdu(&services, &base_pdu, sender_user).await?;
let base_count = base_id.pdu_count();
let base_event = ignored_filter(&services, (base_count, base_pdu), sender_user);

View File

@@ -3,7 +3,7 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Result, debug, debug_info, debug_warn, err, error, info,
Err, Result, debug, debug_info, debug_warn, err, error, info, is_true,
matrix::{
StateKey,
event::{gen_event_id, gen_event_id_canonical_json},
@@ -26,7 +26,7 @@
api::{
client::{
error::ErrorKind,
membership::{ThirdPartySigned, join_room_by_id, join_room_by_id_or_alias},
membership::{join_room_by_id, join_room_by_id_or_alias},
},
federation::{self},
},
@@ -34,7 +34,7 @@
events::{
StateEventType,
room::{
join_rules::{AllowRule, JoinRule},
join_rules::JoinRule,
member::{MembershipState, RoomMemberEventContent},
},
},
@@ -48,9 +48,13 @@
timeline::pdu_fits,
},
};
use tokio::join;
use super::{banned_room_check, validate_remote_member_event_stub};
use crate::Ruma;
use crate::{
Ruma,
server::{select_authorising_user, user_can_perform_restricted_join},
};
/// # `POST /_matrix/client/r0/rooms/{roomId}/join`
///
@@ -116,7 +120,6 @@ pub(crate) async fn join_room_by_id_route(
&body.room_id,
body.reason.clone(),
&servers,
body.third_party_signed.as_ref(),
&body.appservice_info,
)
.boxed()
@@ -248,7 +251,6 @@ pub(crate) async fn join_room_by_id_or_alias_route(
&room_id,
body.reason.clone(),
&servers,
body.third_party_signed.as_ref(),
appservice_info,
)
.boxed()
@@ -263,7 +265,6 @@ pub async fn join_room_by_id_helper(
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
third_party_signed: Option<&ThirdPartySigned>,
appservice_info: &Option<RegistrationInfo>,
) -> Result<join_room_by_id::v3::Response> {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
@@ -351,17 +352,9 @@ pub async fn join_room_by_id_helper(
}
if server_in_room {
join_room_by_id_helper_local(
services,
sender_user,
room_id,
reason,
servers,
third_party_signed,
state_lock,
)
.boxed()
.await?;
join_room_by_id_helper_local(services, sender_user, room_id, reason, servers, state_lock)
.boxed()
.await?;
} else {
// Ask a remote server if we are not participating in this room
join_room_by_id_helper_remote(
@@ -370,7 +363,6 @@ pub async fn join_room_by_id_helper(
room_id,
reason,
servers,
third_party_signed,
state_lock,
)
.boxed()
@@ -386,7 +378,6 @@ async fn join_room_by_id_helper_remote(
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>,
state_lock: RoomMutexGuard,
) -> Result {
info!("Joining {room_id} over federation.");
@@ -396,11 +387,10 @@ async fn join_room_by_id_helper_remote(
info!("make_join finished");
let Some(room_version_id) = make_join_response.room_version else {
return Err!(BadServerResponse("Remote room version is not supported by conduwuit"));
};
let room_version_id = make_join_response.room_version.unwrap_or(RoomVersionId::V1);
if !services.server.supported_room_version(&room_version_id) {
// How did we get here?
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
));
@@ -429,10 +419,6 @@ async fn join_room_by_id_helper_remote(
}
};
join_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
join_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
@@ -744,87 +730,45 @@ async fn join_room_by_id_helper_local(
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>,
state_lock: RoomMutexGuard,
) -> Result {
debug_info!("We can join locally");
let join_rules = services.rooms.state_accessor.get_join_rules(room_id).await;
info!("Joining room locally");
let mut restricted_join_authorized = None;
match join_rules {
| JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted) => {
for restriction in restricted.allow {
match restriction {
| AllowRule::RoomMembership(membership) => {
if services
.rooms
.state_cache
.is_joined(sender_user, &membership.room_id)
.await
{
restricted_join_authorized = Some(true);
break;
}
},
| AllowRule::UnstableSpamChecker => {
match services
.antispam
.meowlnir_accept_make_join(room_id.to_owned(), sender_user.to_owned())
.await
{
| Ok(()) => {
restricted_join_authorized = Some(true);
break;
},
| Err(_) =>
return Err!(Request(Forbidden(
"Antispam rejected join request."
))),
}
},
| _ => {},
}
let (room_version, join_rules, is_invited) = join!(
services.rooms.state.get_room_version(room_id),
services.rooms.state_accessor.get_join_rules(room_id),
services.rooms.state_cache.is_invited(sender_user, room_id)
);
let room_version = room_version?;
let mut auth_user: Option<OwnedUserId> = None;
if !is_invited && matches!(join_rules, JoinRule::Restricted(_) | JoinRule::KnockRestricted(_))
{
use RoomVersionId::*;
if !matches!(room_version, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
// This is a restricted room, check if we can complete the join requirements
// locally.
let needs_auth_user =
user_can_perform_restricted_join(services, sender_user, room_id, &room_version)
.await;
if needs_auth_user.is_ok_and(is_true!()) {
// If there was an error or the value is false, we'll try joining over
// federation. Since it's Ok(true), we can authorise this locally.
// If we can't select a local user, this will remain None, the join will fail,
// and we'll fall back to federation.
auth_user = select_authorising_user(services, room_id, sender_user, &state_lock)
.await
.ok();
}
},
| _ => {},
}
let join_authorized_via_users_server = if restricted_join_authorized.is_none() {
None
} else {
match restricted_join_authorized.unwrap() {
| true => services
.rooms
.state_cache
.local_users_in_room(room_id)
.filter(|user| {
trace!("Checking if {user} can invite {sender_user} to {room_id}");
services.rooms.state_accessor.user_can_invite(
room_id,
user,
sender_user,
&state_lock,
)
})
.boxed()
.next()
.await
.map(ToOwned::to_owned),
| false => {
warn!(
"Join authorization failed for restricted join in room {room_id} for user \
{sender_user}"
);
return Err!(Request(Forbidden("You are not authorized to join this room.")));
},
}
};
}
let content = RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: reason.clone(),
join_authorized_via_users_server,
join_authorized_via_users_server: auth_user,
..RoomMemberEventContent::new(MembershipState::Join)
};
@@ -840,6 +784,7 @@ async fn join_room_by_id_helper_local(
)
.await
else {
info!("Joined room locally");
return Ok(());
};
@@ -847,138 +792,13 @@ async fn join_room_by_id_helper_local(
return Err(error);
}
warn!(
info!(
?error,
servers = %servers.len(),
"Could not join restricted room locally, attempting remote join",
remote_servers = %servers.len(),
"Could not join room locally, attempting remote join",
);
let Ok((make_join_response, remote_server)) =
make_join_request(services, sender_user, room_id, servers).await
else {
return Err(error);
};
let Some(room_version_id) = make_join_response.room_version else {
return Err!(BadServerResponse("Remote room version is not supported by conduwuit"));
};
if !services.server.supported_room_version(&room_version_id) {
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
));
}
let mut join_event_stub: CanonicalJsonObject =
serde_json::from_str(make_join_response.event.get()).map_err(|e| {
err!(BadServerResponse("Invalid make_join event json received from server: {e:?}"))
})?;
validate_remote_member_event_stub(
&MembershipState::Join,
sender_user,
room_id,
&join_event_stub,
)?;
let join_authorized_via_users_server = join_event_stub
.get("content")
.map(|s| {
s.as_object()?
.get("join_authorised_via_users_server")?
.as_str()
})
.and_then(|s| OwnedUserId::try_from(s.unwrap_or_default()).ok());
join_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
join_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
join_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
join_authorized_via_users_server,
..RoomMemberEventContent::new(MembershipState::Join)
})
.expect("event is valid, we just created it"),
);
// We keep the "event_id" in the pdu only in v1 or
// v2 rooms
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
join_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 join_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&join_event_stub, &room_version_id)?;
// Add event_id back
join_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 join_event = join_event_stub;
let send_join_response = services
.sending
.send_synapse_request(
&remote_server,
federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
omit_members: false,
pdu: services
.sending
.convert_to_outgoing_federation_event(join_event.clone())
.await,
},
)
.await?;
if let Some(signed_raw) = send_join_response.room_state.event {
let (signed_event_id, signed_value) =
gen_event_id_canonical_json(&signed_raw, &room_version_id).map_err(|e| {
err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
})?;
if signed_event_id != event_id {
return Err!(Request(BadJson(
warn!(%signed_event_id, %event_id, "Server {remote_server} sent event with wrong event ID")
)));
}
drop(state_lock);
services
.rooms
.event_handler
.handle_incoming_pdu(&remote_server, room_id, &signed_event_id, signed_value, true)
.boxed()
.await?;
} else {
return Err(error);
}
Ok(())
join_room_by_id_helper_remote(services, sender_user, room_id, reason, servers, state_lock)
.await
}
async fn make_join_request(
@@ -987,17 +807,16 @@ async fn make_join_request(
room_id: &RoomId,
servers: &[OwnedServerName],
) -> Result<(federation::membership::prepare_join_event::v1::Response, OwnedServerName)> {
let mut make_join_response_and_server =
Err!(BadServerResponse("No server available to assist in joining."));
let mut make_join_counter: usize = 0;
let mut incompatible_room_version_count: usize = 0;
let mut make_join_counter: usize = 1;
for remote_server in servers {
if services.globals.server_is_ours(remote_server) {
continue;
}
info!("Asking {remote_server} for make_join ({make_join_counter})");
info!(
"Asking {remote_server} for make_join (attempt {make_join_counter}/{})",
servers.len()
);
let make_join_response = services
.sending
.send_federation_request(
@@ -1025,47 +844,44 @@ async fn make_join_request(
warn!("make_join response from {remote_server} failed validation: {e}");
continue;
}
make_join_response_and_server = Ok((response, remote_server.clone()));
break;
return Ok((response, remote_server.clone()));
},
| Err(e) => {
info!("make_join request to {remote_server} failed: {e}");
if matches!(
e.kind(),
ErrorKind::IncompatibleRoomVersion { .. } | ErrorKind::UnsupportedRoomVersion
) {
incompatible_room_version_count =
incompatible_room_version_count.saturating_add(1);
}
if incompatible_room_version_count > 15 {
| Err(e) => match e.kind() {
| ErrorKind::UnableToAuthorizeJoin => {
info!(
"15 servers have responded with M_INCOMPATIBLE_ROOM_VERSION or \
M_UNSUPPORTED_ROOM_VERSION, assuming that conduwuit does not support \
the room version {room_id}: {e}"
"{remote_server} was unable to verify the joining user satisfied \
restricted join requirements: {e}. Will continue trying."
);
make_join_response_and_server =
Err!(BadServerResponse("Room version is not supported by Conduwuit"));
return make_join_response_and_server;
}
if make_join_counter > 40 {
},
| ErrorKind::UnableToGrantJoin => {
info!(
"{remote_server} believes the joining user satisfies restricted join \
rules, but is unable to authorise a join for us. Will continue trying."
);
},
| ErrorKind::IncompatibleRoomVersion { room_version } => {
warn!(
"40 servers failed to provide valid make_join response, assuming no \
server can assist in joining."
"{remote_server} reports the room we are trying to join is \
v{room_version}, which we do not support."
);
make_join_response_and_server =
Err!(BadServerResponse("No server available to assist in joining."));
return make_join_response_and_server;
}
return Err(e);
},
| ErrorKind::Forbidden { .. } => {
warn!("{remote_server} refuses to let us join: {e}.");
return Err(e);
},
| ErrorKind::NotFound => {
info!(
"{remote_server} does not know about {room_id}: {e}. Will continue \
trying."
);
},
| _ => {
info!("{remote_server} failed to make_join: {e}. Will continue trying.");
},
},
}
if make_join_response_and_server.is_ok() {
break;
}
}
make_join_response_and_server
info!("All {} servers were unable to assist in joining {room_id} :(", servers.len());
Err!(BadServerResponse("No server available to assist in joining."))
}

View File

@@ -253,7 +253,6 @@ async fn knock_room_by_id_helper(
room_id,
reason.clone(),
servers,
None,
&None,
)
.await

View File

@@ -1,7 +1,7 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Result, at, debug_warn,
Err, Error, Result, at, debug_warn,
matrix::{
event::{Event, Matches},
pdu::PduCount,
@@ -26,7 +26,7 @@
DeviceId, RoomId, UserId,
api::{
Direction,
client::{filter::RoomEventFilter, message::get_message_events},
client::{error::ErrorKind, filter::RoomEventFilter, message::get_message_events},
},
events::{
AnyStateEvent, StateEventType,
@@ -279,23 +279,30 @@ pub(crate) async fn ignored_filter(
is_ignored_pdu(services, pdu, user_id)
.await
.unwrap_or(true)
.eq(&false)
.then_some(item)
}
/// Determine whether a PDU should be ignored for a given recipient user.
/// Returns True if this PDU should be ignored, returns False otherwise.
///
/// The error SenderIgnored is returned if the sender or the sender's server is
/// ignored by the relevant user. If the error cannot be returned to the user,
/// it should equate to a true value (i.e. ignored).
#[inline]
pub(crate) async fn is_ignored_pdu<Pdu>(
services: &Services,
event: &Pdu,
recipient_user: &UserId,
) -> bool
) -> Result<bool>
where
Pdu: Event + Send + Sync,
{
// exclude Synapse's dummy events from bloating up response bodies. clients
// don't need to see this.
if event.kind().to_cow_str() == "org.matrix.dummy_event" {
return true;
return Ok(true);
}
let sender_user = event.sender();
@@ -310,21 +317,27 @@ pub(crate) async fn is_ignored_pdu<Pdu>(
if !type_ignored {
// We cannot safely ignore this type
return false;
return Ok(false);
}
if server_ignored {
// the sender's server is ignored, so ignore this event
return true;
return Err(Error::BadRequest(
ErrorKind::SenderIgnored { sender: None },
"The sender's server is ignored by this server.",
));
}
if user_ignored && !services.config.send_messages_from_ignored_users_to_client {
// the recipient of this PDU has the sender ignored, and we're not
// configured to send ignored messages to clients
return true;
return Err(Error::BadRequest(
ErrorKind::SenderIgnored { sender: Some(event.sender().to_owned()) },
"You have ignored this sender.",
));
}
false
Ok(false)
}
#[inline]

View File

@@ -1,6 +1,6 @@
use axum::extract::State;
use conduwuit::{
Err, Result, at, debug_warn,
Err, Result, at, debug_warn, err,
matrix::{Event, event::RelationTypeEqual, pdu::PduCount},
utils::{IterStream, ReadyExt, result::FlatOk, stream::WidebandExt},
};
@@ -18,7 +18,7 @@
events::{TimelineEventType, relation::RelationType},
};
use crate::Ruma;
use crate::{Ruma, client::is_ignored_pdu};
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}`
pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
@@ -118,6 +118,14 @@ async fn paginate_relations_with_filter(
debug_warn!(req_evt = %target, %room_id, "Event relations requested by {sender_user} but is not allowed to see it, returning 404");
return Err!(Request(NotFound("Event not found.")));
}
let target_pdu = services
.rooms
.timeline
.get_pdu(target)
.await
.map_err(|_| err!(Request(NotFound("Event not found."))))?;
// Return M_SENDER_IGNORED if the sender of base_event is ignored (MSC4406)
is_ignored_pdu(services, &target_pdu, sender_user).await?;
let start: PduCount = from
.map(str::parse)
@@ -159,6 +167,7 @@ async fn paginate_relations_with_filter(
.ready_take_while(|(count, _)| Some(*count) != to)
.take(limit)
.wide_filter_map(|item| visibility_filter(services, sender_user, item))
.wide_filter_map(|item| ignored_filter(services, item, sender_user))
.then(async |mut pdu| {
if let Err(e) = services
.rooms
@@ -214,3 +223,17 @@ async fn visibility_filter<Pdu: Event + Send + Sync>(
.await
.then_some(item)
}
async fn ignored_filter<Pdu: Event + Send + Sync>(
services: &Services,
item: (PduCount, Pdu),
sender_user: &UserId,
) -> Option<(PduCount, Pdu)> {
let (_, pdu) = &item;
if is_ignored_pdu(services, pdu, sender_user).await.ok()? {
None
} else {
Some(item)
}
}

View File

@@ -29,7 +29,7 @@ pub(crate) async fn get_room_event_route(
let (mut event, visible) = try_join(event, visible).await?;
if !visible || is_ignored_pdu(services, &event, body.sender_user()).await {
if !visible || is_ignored_pdu(services, &event, body.sender_user()).await? {
return Err!(Request(Forbidden("You don't have permission to view this event.")));
}

View File

@@ -107,7 +107,7 @@ pub(super) async fn ldap_login(
) -> Result<OwnedUserId> {
let (user_dn, is_ldap_admin) = match services.config.ldap.bind_dn.as_ref() {
| Some(bind_dn) if bind_dn.contains("{username}") =>
(bind_dn.replace("{username}", lowercased_user_id.localpart()), false),
(bind_dn.replace("{username}", lowercased_user_id.localpart()), None),
| _ => {
debug!("Searching user in LDAP");
@@ -144,12 +144,16 @@ pub(super) async fn ldap_login(
.await?;
}
let is_conduwuit_admin = services.admin.user_is_admin(lowercased_user_id).await;
// Only sync admin status if LDAP can actually determine it.
// None means LDAP cannot determine admin status (manual config required).
if let Some(is_ldap_admin) = is_ldap_admin {
let is_conduwuit_admin = services.admin.user_is_admin(lowercased_user_id).await;
if is_ldap_admin && !is_conduwuit_admin {
Box::pin(services.admin.make_user_admin(lowercased_user_id)).await?;
} else if !is_ldap_admin && is_conduwuit_admin {
Box::pin(services.admin.revoke_admin(lowercased_user_id)).await?;
if is_ldap_admin && !is_conduwuit_admin {
Box::pin(services.admin.make_user_admin(lowercased_user_id)).await?;
} else if !is_ldap_admin && is_conduwuit_admin {
Box::pin(services.admin.revoke_admin(lowercased_user_id)).await?;
}
}
Ok(user_id)

View File

@@ -30,7 +30,8 @@
api::client::sync::sync_events::{self, DeviceLists, UnreadNotificationsCount},
directory::RoomTypeFilter,
events::{
AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, StateEventType, TimelineEventType,
AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, AnySyncStateEvent, StateEventType,
TimelineEventType,
room::member::{MembershipState, RoomMemberEventContent},
typing::TypingEventContent,
},
@@ -533,6 +534,9 @@ async fn process_rooms<'a, Rooms>(
}
});
let required_state =
collect_required_state(services, room_id, required_state_request).await;
let room_events: Vec<_> = timeline_pdus
.iter()
.stream()
@@ -551,21 +555,6 @@ async fn process_rooms<'a, Rooms>(
}
}
let required_state = required_state_request
.iter()
.stream()
.filter_map(|state| async move {
services
.rooms
.state_accessor
.room_state_get(room_id, &state.0, &state.1)
.await
.map(Event::into_format)
.ok()
})
.collect()
.await;
// Heroes
let heroes: Vec<_> = services
.rooms
@@ -689,6 +678,51 @@ async fn process_rooms<'a, Rooms>(
Ok(rooms)
}
/// Collect the required state events for a room
async fn collect_required_state(
services: &Services,
room_id: &RoomId,
required_state_request: &BTreeSet<TypeStateKey>,
) -> Vec<Raw<AnySyncStateEvent>> {
let mut required_state = Vec::new();
let mut wildcard_types: HashSet<&StateEventType> = HashSet::new();
for (event_type, state_key) in required_state_request {
if wildcard_types.contains(event_type) {
continue;
}
if state_key.as_str() == "*" {
wildcard_types.insert(event_type);
if let Ok(keys) = services
.rooms
.state_accessor
.room_state_keys(room_id, event_type)
.await
{
for key in keys {
if let Ok(event) = services
.rooms
.state_accessor
.room_state_get(room_id, event_type, &key)
.await
{
required_state.push(Event::into_format(event));
}
}
}
} else if let Ok(event) = services
.rooms
.state_accessor
.room_state_get(room_id, event_type, state_key)
.await
{
required_state.push(Event::into_format(event));
}
}
required_state
}
async fn collect_typing_events(
services: &Services,
sender_user: &UserId,

View File

@@ -27,6 +27,7 @@ pub(crate) async fn well_known_client(
identity_server: None,
sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }),
tile_server: None,
rtc_foci: services.config.well_known.rtc_focus_server_urls.clone(),
})
}

View File

@@ -1,6 +1,9 @@
use std::collections::{HashSet, VecDeque};
use axum::extract::State;
use conduwuit::{Err, Result, debug, debug_error, info, utils::to_canonical_object};
use ruma::api::federation::event::get_missing_events;
use conduwuit::{Err, Event, Result, debug, info, trace, utils::to_canonical_object, warn};
use ruma::{OwnedEventId, api::federation::event::get_missing_events};
use serde_json::{json, value::RawValue};
use super::AccessCheck;
use crate::Ruma;
@@ -45,59 +48,76 @@ pub(crate) async fn get_missing_events_route(
.unwrap_or(LIMIT_DEFAULT)
.min(LIMIT_MAX);
let mut queued_events = body.latest_events.clone();
// the vec will never have more entries the limit
let mut events = Vec::with_capacity(limit);
let room_version = services.rooms.state.get_room_version(&body.room_id).await?;
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 {
debug!(
body.origin = body.origin.as_ref().map(tracing::field::display),
"Event {} does not exist locally, skipping", &queued_events[i]
);
i = i.saturating_add(1);
let mut queue: VecDeque<OwnedEventId> = VecDeque::from(body.latest_events.clone());
let mut results: Vec<Box<RawValue>> = Vec::with_capacity(limit);
let mut seen: HashSet<OwnedEventId> = HashSet::from_iter(body.earliest_events.clone());
while let Some(next_event_id) = queue.pop_front() {
if seen.contains(&next_event_id) {
trace!(%next_event_id, "already seen event, skipping");
continue;
}
if results.len() >= limit {
debug!(%next_event_id, "reached limit of events to return, breaking");
break;
}
let mut pdu = match services.rooms.timeline.get_pdu(&next_event_id).await {
| Ok(pdu) => pdu,
| Err(e) => {
warn!("could not find event {next_event_id} while walking missing events: {e}");
continue;
},
};
if body.earliest_events.contains(&queued_events[i]) {
i = i.saturating_add(1);
continue;
if pdu.room_id_or_hash() != body.room_id {
return Err!(Request(Unknown(
"Event {next_event_id} is not in room {}",
body.room_id
)));
}
if !services
.rooms
.state_accessor
.server_can_see_event(body.origin(), &body.room_id, &queued_events[i])
.server_can_see_event(body.origin(), &body.room_id, pdu.event_id())
.await
{
debug!(
body.origin = body.origin.as_ref().map(tracing::field::display),
"Server cannot see {:?} in {:?}, skipping", pdu.event_id, pdu.room_id
);
i = i.saturating_add(1);
continue;
debug!(%next_event_id, origin = %body.origin(), "redacting event origin cannot see");
pdu.redact(&room_version, json!({}))?;
}
i = i.saturating_add(1);
let Ok(event) = to_canonical_object(&pdu) else {
debug_error!(
body.origin = body.origin.as_ref().map(tracing::field::display),
"Failed to convert PDU in database to canonical JSON: {pdu:?}"
);
continue;
};
let prev_events = pdu.prev_events.iter().map(ToOwned::to_owned);
let event = services
.sending
.convert_to_outgoing_federation_event(event)
.await;
queued_events.extend(prev_events);
events.push(event);
trace!(
%next_event_id,
prev_events = ?pdu.prev_events().collect::<Vec<_>>(),
"adding event to results and queueing prev events"
);
queue.extend(pdu.prev_events.clone());
seen.insert(next_event_id.clone());
if body.latest_events.contains(&next_event_id) {
continue; // Don't include latest_events in results,
// but do include their prev_events in the queue
}
results.push(
services
.sending
.convert_to_outgoing_federation_event(to_canonical_object(pdu)?)
.await,
);
trace!(
%next_event_id,
queue_len = queue.len(),
seen_len = seen.len(),
results_len = results.len(),
"event added to results"
);
}
Ok(get_missing_events::v1::Response { events })
if !queue.is_empty() {
debug!("limit reached before queue was empty");
}
results.reverse(); // return oldest first
Ok(get_missing_events::v1::Response { events: results })
}

View File

@@ -16,6 +16,8 @@
},
};
use serde_json::value::to_raw_value;
use service::rooms::state::RoomMutexGuard;
use tokio::join;
use crate::Ruma;
@@ -85,16 +87,24 @@ pub(crate) async fn create_join_event_template_route(
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let is_invited = services
.rooms
.state_cache
.is_invited(&body.user_id, &body.room_id)
.await;
let (is_invited, is_joined) = join!(
services
.rooms
.state_cache
.is_invited(&body.user_id, &body.room_id),
services
.rooms
.state_cache
.is_joined(&body.user_id, &body.room_id)
);
let join_authorized_via_users_server: Option<OwnedUserId> = {
use RoomVersionId::*;
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) || is_invited {
// room version does not support restricted join rules, or the user is currently
// already invited
if is_joined || is_invited {
// User is already joined or invited and consequently does not need an
// authorising user
None
} else if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
// room version does not support restricted join rules
None
} else if user_can_perform_restricted_join(
&services,
@@ -104,32 +114,10 @@ pub(crate) async fn create_join_event_template_route(
)
.await?
{
let Some(auth_user) = services
.rooms
.state_cache
.local_users_in_room(&body.room_id)
.filter(|user| {
services.rooms.state_accessor.user_can_invite(
&body.room_id,
user,
&body.user_id,
&state_lock,
)
})
.boxed()
.next()
.await
.map(ToOwned::to_owned)
else {
info!(
"No local user is able to authorize the join of {} into {}",
&body.user_id, &body.room_id
);
return Err!(Request(UnableToGrantJoin(
"No user on this server is able to assist in joining."
)));
};
Some(auth_user)
Some(
select_authorising_user(&services, &body.room_id, &body.user_id, &state_lock)
.await?,
)
} else {
None
}
@@ -159,9 +147,7 @@ pub(crate) async fn create_join_event_template_route(
)
.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)?;
pdu_json.remove("event_id");
Ok(prepare_join_event::v1::Response {
room_version: Some(room_version_id),
@@ -169,6 +155,38 @@ pub(crate) async fn create_join_event_template_route(
})
}
/// Attempts to find a user who is able to issue an invite in the target room.
pub(crate) async fn select_authorising_user(
services: &Services,
room_id: &RoomId,
user_id: &UserId,
state_lock: &RoomMutexGuard,
) -> Result<OwnedUserId> {
let auth_user = services
.rooms
.state_cache
.local_users_in_room(room_id)
.filter(|user| {
services
.rooms
.state_accessor
.user_can_invite(room_id, user, user_id, state_lock)
})
.boxed()
.next()
.await
.map(ToOwned::to_owned);
match auth_user {
| Some(auth_user) => Ok(auth_user),
| None => {
Err!(Request(UnableToGrantJoin(
"No user on this server is able to assist in joining."
)))
},
}
}
/// Checks whether the given user can join the given room via a restricted join.
pub(crate) async fn user_can_perform_restricted_join(
services: &Services,
@@ -180,12 +198,9 @@ pub(crate) async fn user_can_perform_restricted_join(
// restricted rooms are not supported on <=v7
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
return Ok(false);
}
if services.rooms.state_cache.is_joined(user_id, room_id).await {
// joining user is already joined, there is nothing we need to do
return Ok(false);
// This should be impossible as it was checked earlier on, but retain this check
// for safety.
unreachable!("user_can_perform_restricted_join got incompatible room version");
}
let Ok(join_rules_event_content) = services
@@ -205,17 +220,31 @@ pub(crate) async fn user_can_perform_restricted_join(
let (JoinRule::Restricted(r) | JoinRule::KnockRestricted(r)) =
join_rules_event_content.join_rule
else {
// This is not a restricted room
return Ok(false);
};
if r.allow.is_empty() {
debug_info!("{room_id} is restricted but the allow key is empty");
return Ok(false);
// This will never be authorisable, return forbidden.
return Err!(Request(Forbidden("You are not invited to this room.")));
}
let mut could_satisfy = true;
for allow_rule in &r.allow {
match allow_rule {
| AllowRule::RoomMembership(membership) => {
if !services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), &membership.room_id)
.await
{
// Since we can't check this room, mark could_satisfy as false
// so that we can return M_UNABLE_TO_AUTHORIZE_JOIN later.
could_satisfy = false;
continue;
}
if services
.rooms
.state_cache
@@ -239,6 +268,8 @@ pub(crate) async fn user_can_perform_restricted_join(
| Err(_) => Err!(Request(Forbidden("Antispam rejected join request."))),
},
| _ => {
// We don't recognise this join rule, so we cannot satisfy the request.
could_satisfy = false;
debug_info!(
"Unsupported allow rule in restricted join for room {}: {:?}",
room_id,
@@ -248,9 +279,23 @@ pub(crate) async fn user_can_perform_restricted_join(
}
}
Err!(Request(UnableToAuthorizeJoin(
"Joining user is not known to be in any required room."
)))
if could_satisfy {
// We were able to check all the restrictions and can be certain that the
// prospective member is not permitted to join.
Err!(Request(Forbidden(
"You do not belong to any of the rooms or spaces required to join this room."
)))
} else {
// We were unable to check all the restrictions. This usually means we aren't in
// one of the rooms this one is restricted to, ergo can't check its state for
// the user's membership, and consequently the user *might* be able to join if
// they ask another server.
Err!(Request(UnableToAuthorizeJoin(
"You do not belong to any of the recognised rooms or spaces required to join this \
room, but this server is unable to verify every requirement. You may be able to \
join via another server."
)))
}
}
pub(crate) fn maybe_strip_event_id(

View File

@@ -19,7 +19,7 @@
use regex::RegexSet;
use ruma::{
OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
api::client::discovery::discover_support::ContactRole,
api::client::discovery::{discover_homeserver::RtcFocusInfo, discover_support::ContactRole},
};
use serde::{Deserialize, de::IgnoredAny};
use url::Url;
@@ -1696,6 +1696,11 @@ pub struct Config {
#[serde(default)]
pub url_preview_check_root_domain: bool,
/// User agent that is used specifically when fetching url previews.
///
/// default: "continuwuity/<version> (bot; +https://continuwuity.org)"
pub url_preview_user_agent: Option<String>,
/// List of forbidden room aliases and room IDs as strings of regex
/// patterns.
///
@@ -2111,6 +2116,19 @@ pub struct WellKnownConfig {
/// If no email or mxid is specified, all of the server's admins will be
/// listed.
pub support_mxid: Option<OwnedUserId>,
/// A list of MatrixRTC foci URLs which will be served as part of the
/// MSC4143 client endpoint at /.well-known/matrix/client. If you're
/// setting up livekit, you'd want something like:
/// rtc_focus_server_urls = [
/// { type = "livekit", livekit_service_url = "https://livekit.example.com" },
/// ]
///
/// To disable, set this to be an empty vector (`[]`).
///
/// default: []
#[serde(default = "default_rtc_focus_urls")]
pub rtc_focus_server_urls: Vec<RtcFocusInfo>,
}
#[derive(Clone, Copy, Debug, Deserialize, Default)]
@@ -2608,6 +2626,9 @@ fn default_rocksdb_stats_level() -> u8 { 1 }
#[inline]
pub fn default_default_room_version() -> RoomVersionId { RoomVersionId::V11 }
#[must_use]
pub fn default_rtc_focus_urls() -> Vec<RtcFocusInfo> { vec![] }
fn default_ip_range_denylist() -> Vec<String> {
vec![
"127.0.0.0/8".to_owned(),

View File

@@ -85,7 +85,8 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
| Unrecognized => StatusCode::METHOD_NOT_ALLOWED,
// 404
| NotFound | NotImplemented | FeatureDisabled => StatusCode::NOT_FOUND,
| NotFound | NotImplemented | FeatureDisabled | SenderIgnored { .. } =>
StatusCode::NOT_FOUND,
// 403
| GuestAccessForbidden

View File

@@ -8,9 +8,11 @@
use std::sync::OnceLock;
static BRANDING: &str = "continuwuity";
static WEBSITE: &str = "https://continuwuity.org";
static SEMANTIC: &str = env!("CARGO_PKG_VERSION");
static VERSION: OnceLock<String> = OnceLock::new();
static VERSION_UA: OnceLock<String> = OnceLock::new();
static USER_AGENT: OnceLock<String> = OnceLock::new();
#[inline]
@@ -19,11 +21,18 @@ pub fn name() -> &'static str { BRANDING }
#[inline]
pub fn version() -> &'static str { VERSION.get_or_init(init_version) }
#[inline]
pub fn version_ua() -> &'static str { VERSION_UA.get_or_init(init_version_ua) }
#[inline]
pub fn user_agent() -> &'static str { USER_AGENT.get_or_init(init_user_agent) }
fn init_user_agent() -> String { format!("{}/{}", name(), version()) }
fn init_user_agent() -> String { format!("{}/{} (bot; +{WEBSITE})", name(), version_ua()) }
fn init_version_ua() -> String {
conduwuit_build_metadata::version_tag()
.map_or_else(|| SEMANTIC.to_owned(), |extra| format!("{SEMANTIC}+{extra}"))
}
fn init_version() -> String {
conduwuit_build_metadata::version_tag()

View File

@@ -230,6 +230,7 @@ tracing-opentelemetry.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
tracing-journald = { workspace = true, optional = true }
parking_lot.workspace = true
[target.'cfg(all(not(target_env = "msvc"), target_os = "linux"))'.dependencies]

36
src/main/deadlock.rs Normal file
View File

@@ -0,0 +1,36 @@
use std::{thread, time::Duration};
/// Runs a loop that checks for deadlocks every 10 seconds.
///
/// Note that this requires the `deadlock_detection` parking_lot feature to be
/// enabled.
pub(crate) fn deadlock_detection_thread() {
loop {
thread::sleep(Duration::from_secs(10));
let deadlocks = parking_lot::deadlock::check_deadlock();
if deadlocks.is_empty() {
continue;
}
eprintln!("{} deadlocks detected", deadlocks.len());
for (i, threads) in deadlocks.iter().enumerate() {
eprintln!("Deadlock #{i}");
for t in threads {
eprintln!("Thread Id {:#?}", t.thread_id());
eprintln!("{:#?}", t.backtrace());
}
}
}
}
/// Spawns the deadlock detection thread.
///
/// This thread will run in the background and check for deadlocks every 10
/// seconds. When a deadlock is detected, it will print detailed information to
/// stderr.
pub(crate) fn spawn() {
thread::Builder::new()
.name("deadlock_detector".to_owned())
.spawn(deadlock_detection_thread)
.expect("failed to spawn deadlock detection thread");
}

View File

@@ -5,6 +5,7 @@
use conduwuit_core::{debug_info, error};
mod clap;
mod deadlock;
mod logging;
mod mods;
mod panic;
@@ -27,6 +28,9 @@ pub fn run() -> Result<()> {
}
pub fn run_with_args(args: &Args) -> Result<()> {
// Spawn deadlock detection thread
deadlock::spawn();
let runtime = runtime::new(args)?;
let server = Server::new(args, Some(runtime.handle()))?;

View File

@@ -36,6 +36,11 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
.clone()
.and_then(Either::right);
let url_preview_user_agent = config
.url_preview_user_agent
.clone()
.unwrap_or_else(|| conduwuit::version::user_agent().to_owned());
Ok(Arc::new(Self {
default: base(config)?
.dns_resolver(resolver.resolver.clone())
@@ -49,6 +54,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
.dns_resolver(resolver.resolver.clone())
.timeout(Duration::from_secs(config.url_preview_timeout))
.redirect(redirect::Policy::limited(3))
.user_agent(url_preview_user_agent)
.build()?,
extern_media: base(config)?

View File

@@ -4,18 +4,83 @@
};
use conduwuit::{
Err, Event, Result, debug::INFO_SPAN_LEVEL, defer, err, implement, info,
utils::stream::IterStream, warn,
Err, Event, PduEvent, Result, debug::INFO_SPAN_LEVEL, debug_error, debug_info, defer, err,
implement, info, trace, utils::stream::IterStream, warn,
};
use futures::{
FutureExt, TryFutureExt, TryStreamExt,
future::{OptionFuture, try_join5},
future::{OptionFuture, try_join4},
};
use ruma::{
CanonicalJsonValue, EventId, OwnedUserId, RoomId, ServerName, UserId,
events::{
StateEventType, TimelineEventType,
room::member::{MembershipState, RoomMemberEventContent},
},
};
use ruma::{CanonicalJsonValue, EventId, RoomId, ServerName, UserId, events::StateEventType};
use tracing::debug;
use crate::rooms::timeline::{RawPduId, pdu_fits};
async fn should_rescind_invite(
services: &crate::rooms::event_handler::Services,
content: &mut BTreeMap<String, CanonicalJsonValue>,
sender: &UserId,
room_id: &RoomId,
) -> Result<Option<PduEvent>> {
// We insert a bogus event ID since we can't actually calculate the right one
content.insert("event_id".to_owned(), CanonicalJsonValue::String("$rescind".to_owned()));
let pdu_event = serde_json::from_value::<PduEvent>(
serde_json::to_value(&content).expect("CanonicalJsonObj is a valid JsonValue"),
)
.map_err(|e| err!("invalid PDU: {e}"))?;
if pdu_event.room_id().is_none_or(|r| r != room_id)
&& pdu_event.sender() != sender
&& pdu_event.event_type() != &TimelineEventType::RoomMember
&& pdu_event.state_key().is_none_or(|v| v == sender.as_str())
{
return Ok(None);
}
let target_user_id = UserId::parse(pdu_event.state_key().unwrap())?;
if pdu_event
.get_content::<RoomMemberEventContent>()?
.membership
!= MembershipState::Leave
{
return Ok(None); // Not a leave event
}
// Does the target user have a pending invite?
let Ok(pending_invite_state) = services
.state_cache
.invite_state(target_user_id, room_id)
.await
else {
return Ok(None); // No pending invite, so nothing to rescind
};
for event in pending_invite_state {
if event
.get_field::<String>("type")?
.is_some_and(|t| t == "m.room.member")
|| event
.get_field::<OwnedUserId>("state_key")?
.is_some_and(|s| s == *target_user_id)
|| event
.get_field::<OwnedUserId>("sender")?
.is_some_and(|s| s == *sender)
|| event
.get_field::<RoomMemberEventContent>("content")?
.is_some_and(|c| c.membership == MembershipState::Invite)
{
return Ok(Some(pdu_event));
}
}
Ok(None)
}
/// When receiving an event one needs to:
/// 0. Check the server is in the room
/// 1. Skip the PDU if we already know about it
@@ -69,6 +134,7 @@ pub async fn handle_incoming_pdu<'a>(
);
return Err!(Request(TooLarge("PDU is too large")));
}
trace!("processing incoming pdu from {origin} for room {room_id} with event id {event_id}");
// 1.1 Check we even know about the room
let meta_exists = self.services.metadata.exists(room_id).map(Ok);
@@ -91,24 +157,14 @@ pub async fn handle_incoming_pdu<'a>(
.then(|| self.acl_check(sender.server_name(), room_id))
.into();
// Fetch create event
let create_event =
self.services
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "");
let (meta_exists, is_disabled, (), (), ref create_event) = try_join5(
let (meta_exists, is_disabled, (), ()) = try_join4(
meta_exists,
is_disabled,
origin_acl_check,
sender_acl_check.map(|o| o.unwrap_or(Ok(()))),
create_event,
)
.await?;
if !meta_exists {
return Err!(Request(NotFound("Room is unknown to this server")));
}
.await
.inspect_err(|e| debug_error!("failed to handle incoming PDU: {e}"))?;
if is_disabled {
return Err!(Request(Forbidden("Federation of this room is disabled by this server.")));
@@ -120,6 +176,23 @@ pub async fn handle_incoming_pdu<'a>(
.server_in_room(self.services.globals.server_name(), room_id)
.await
{
// Is this a federated invite rescind?
// copied from https://github.com/element-hq/synapse/blob/7e4588a/synapse/handlers/federation_event.py#L255-L300
if value.get("type").and_then(|t| t.as_str()) == Some("m.room.member") {
if let Some(pdu) =
should_rescind_invite(&self.services, &mut value.clone(), sender, room_id).await?
{
debug_info!(
"Invite to {room_id} appears to have been rescinded by {sender}, marking as \
left"
);
self.services
.state_cache
.mark_as_left(sender, room_id, Some(pdu))
.await;
return Ok(None);
}
}
info!(
%origin,
"Dropping inbound PDU for room we aren't participating in"
@@ -127,6 +200,17 @@ pub async fn handle_incoming_pdu<'a>(
return Err!(Request(NotFound("This server is not participating in that room.")));
}
if !meta_exists {
return Err!(Request(NotFound("Room is unknown to this server")));
}
// Fetch create event
let create_event = &(self
.services
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await?);
let (incoming_pdu, val) = self
.handle_outlier_pdu(origin, create_event, event_id, room_id, value, false)
.await?;

View File

@@ -56,7 +56,7 @@ pub async fn parse_incoming_pdu(&self, pdu: &RawJsonValue) -> Result<Parsed> {
.state
.get_room_version(&room_id)
.await
.map_err(|_| err!("Server is not in room {room_id}"))?;
.unwrap_or(RoomVersionId::V1);
let (event_id, value) = gen_event_id_canonical_json(pdu, &room_version_id).map_err(|e| {
err!(Request(InvalidParam("Could not convert event to canonical json: {e}")))
})?;

View File

@@ -1269,12 +1269,12 @@ pub fn set_profile_key(
}
#[cfg(not(feature = "ldap"))]
pub async fn search_ldap(&self, _user_id: &UserId) -> Result<Vec<(String, bool)>> {
pub async fn search_ldap(&self, _user_id: &UserId) -> Result<Vec<(String, Option<bool>)>> {
Err!(FeatureDisabled("ldap"))
}
#[cfg(feature = "ldap")]
pub async fn search_ldap(&self, user_id: &UserId) -> Result<Vec<(String, bool)>> {
pub async fn search_ldap(&self, user_id: &UserId) -> Result<Vec<(String, Option<bool>)>> {
let localpart = user_id.localpart().to_owned();
let lowercased_localpart = localpart.to_lowercase();
@@ -1318,7 +1318,7 @@ pub async fn search_ldap(&self, user_id: &UserId) -> Result<Vec<(String, bool)>>
.inspect(|(entries, result)| trace!(?entries, ?result, "LDAP Search"))
.map_err(|e| err!(Ldap(error!(?attr, ?user_filter, "LDAP search error: {e}"))))?;
let mut dns: HashMap<String, bool> = entries
let mut dns: HashMap<String, Option<bool>> = entries
.into_iter()
.filter_map(|entry| {
let search_entry = SearchEntry::construct(entry);
@@ -1329,11 +1329,16 @@ pub async fn search_ldap(&self, user_id: &UserId) -> Result<Vec<(String, bool)>>
.into_iter()
.chain(search_entry.attrs.get(&config.name_attribute))
.any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart))
.then_some((search_entry.dn, false))
.then_some((search_entry.dn, None))
})
.collect();
if !config.admin_filter.is_empty() {
// Update all existing entries to Some(false) since we can now determine admin
// status
for admin_status in dns.values_mut() {
*admin_status = Some(false);
}
let admin_base_dn = if config.admin_base_dn.is_empty() {
&config.base_dn
} else {
@@ -1362,7 +1367,7 @@ pub async fn search_ldap(&self, user_id: &UserId) -> Result<Vec<(String, bool)>>
.into_iter()
.chain(search_entry.attrs.get(&config.name_attribute))
.any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart))
.then_some((search_entry.dn, true))
.then_some((search_entry.dn, Some(true)))
}));
}

View File

@@ -9,7 +9,7 @@
<li>Read the <a href="https://continuwuity.org/introduction">documentation</a></li>
<li>Join the <a href="https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org">Continuwuity Matrix room</a> or <a href="https://matrix.to/#/#space:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org">space</a></li>
<li>Log in with a <a href="https://matrix.org/ecosystem/clients/">client</a></li>
<li>Ensure <a href="https://federationtester.matrix.org/#{{ server_name }}">federation</a> works</li>
<li>Ensure <a href="https://federationtester.mtrnord.blog/?serverName={{ server_name }}">federation</a> works</li>
</ul>
</div>

View File

@@ -105,3 +105,68 @@ body:not(.notTopArrived) header.rp-nav {
.rspress-logo {
height: 32px;
}
/* pre-hero */
.custom-section {
padding: 4rem 1.5rem;
background: var(--rp-c-bg);
}
.custom-cards {
display: flex;
gap: 2rem;
max-width: 800px;
margin: 0 auto;
justify-content: center;
flex-wrap: wrap;
}
.custom-card {
padding: 2rem;
border: 1px solid var(--rp-c-divider-light);
border-radius: 12px;
background: var(--rp-c-bg-soft);
text-decoration: none;
color: var(--rp-c-text-1);
transition: all 0.3s ease;
display: flex;
flex-direction: column;
flex: 1;
min-width: 280px;
max-width: 350px;
}
.custom-card:hover {
border-color: var(--rp-c-brand);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.custom-card h3 {
margin: 0 0 1rem 0;
font-size: 1.25rem;
font-weight: 600;
color: var(--rp-c-text-0);
}
.custom-card p {
margin: 0 0 1.5rem 0;
color: var(--rp-c-text-2);
line-height: 1.6;
flex: 1;
}
.custom-card-button {
display: inline-block;
padding: 0.5rem 1.5rem;
background: var(--rp-c-brand);
color: white;
border-radius: 6px;
font-weight: 500;
text-align: center;
transition: background 0.2s ease;
}
.custom-card:hover .custom-card-button {
background: var(--rp-c-brand-light);
}

View File

@@ -12,6 +12,23 @@ function HomeLayout(props: HomeLayoutProps) {
return (
<BasicHomeLayout
beforeFeatures={
frontmatter.beforeFeatures ? (
<section className="custom-section">
<div className="rp-container">
<div className="custom-cards">
{frontmatter.beforeFeatures.map((item: any, index: number) => (
<a key={index} href={item.link} className="custom-card" target="_blank" rel="noopener noreferrer">
<h3>{item.title}</h3>
<p>{item.details}</p>
<span className="custom-card-button">{item.buttonText || 'Learn More'} </span>
</a>
))}
</div>
</div>
</section>
) : <></>
}
afterFeatures={
(frontmatter.doc) ?
<main className="rp-doc-layout__doc-container">