mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-04-11 21:55:43 +00:00
Compare commits
52 Commits
v0.5.4
...
jade/ldap-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
082c44f355 | ||
|
|
117c581948 | ||
|
|
cb846a3ad1 | ||
|
|
81b984b2cc | ||
|
|
e2961390ee | ||
|
|
cb75e836e0 | ||
|
|
cb7a988b1b | ||
|
|
aa5400bcef | ||
|
|
ff4dddd673 | ||
|
|
c22b17fb29 | ||
|
|
3da7fa24db | ||
|
|
d15ac1d3c1 | ||
|
|
a9ebdf58e2 | ||
|
|
f1ab27d344 | ||
|
|
8bc6e6ccca | ||
|
|
60a3abe752 | ||
|
|
e3b874d336 | ||
|
|
f3f82831b4 | ||
|
|
26aac1408e | ||
|
|
be8f62396a | ||
|
|
40996a6602 | ||
|
|
9cae531f90 | ||
|
|
56eea935b6 | ||
|
|
fcb646f8c4 | ||
|
|
57b21c1b32 | ||
|
|
8d66500c99 | ||
|
|
abacf1dc20 | ||
|
|
134e5cadaf | ||
|
|
8ec0f0d830 | ||
|
|
0453544036 | ||
|
|
89ad809270 | ||
|
|
ecd3a4eb41 | ||
|
|
5506997ca0 | ||
|
|
abc0683d59 | ||
|
|
dd60beb9fb | ||
|
|
d9520f9382 | ||
|
|
40bb5366bb | ||
|
|
f82bd77073 | ||
|
|
7d84ba5ff2 | ||
|
|
69a8937584 | ||
|
|
b2ec13d342 | ||
|
|
4e55e1ea90 | ||
|
|
f5f3108d5f | ||
|
|
d1e1ee6156 | ||
|
|
ae16a45515 | ||
|
|
077bda23a6 | ||
|
|
a2bf0c1223 | ||
|
|
b9b1ff87f2 | ||
|
|
3c0146d437 | ||
|
|
7485d4aa91 | ||
|
|
39bdb4c5a2 | ||
|
|
55fb3b8848 |
@@ -1,9 +1,9 @@
|
||||
# Local build and dev artifacts
|
||||
target/
|
||||
!target/debug/conduwuit
|
||||
|
||||
# Docker files
|
||||
Dockerfile*
|
||||
docker/
|
||||
|
||||
# IDE files
|
||||
.vscode
|
||||
|
||||
@@ -23,7 +23,7 @@ repos:
|
||||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.43.2
|
||||
rev: v1.43.4
|
||||
hooks:
|
||||
- id: typos
|
||||
- id: typos
|
||||
|
||||
107
Cargo.lock
generated
107
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
|
||||
1
changelog.d/1249.bugfix.md
Normal file
1
changelog.d/1249.bugfix.md
Normal file
@@ -0,0 +1 @@
|
||||
Fixed invites sent to other users in the same homeserver not being properly sent down sync. Users with missing or broken invites should clear their client caches after updating to make them appear.
|
||||
1
changelog.d/1307.bugfix.md
Normal file
1
changelog.d/1307.bugfix.md
Normal file
@@ -0,0 +1 @@
|
||||
LDAP-enabled servers will no longer have all admins demoted when LDAP-controlled admins are not configured. Contributed by @Jade
|
||||
2
changelog.d/1308.feature.md
Normal file
2
changelog.d/1308.feature.md
Normal 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
1
changelog.d/1344.misc.md
Normal file
@@ -0,0 +1 @@
|
||||
Continuwuity will now print information to the console when it detects a deadlock
|
||||
1
changelog.d/1349.feature
Normal file
1
changelog.d/1349.feature
Normal file
@@ -0,0 +1 @@
|
||||
Introduce a resolver command to allow flushing a server from the cache or to flush the complete cache. Contributed by @Omar007
|
||||
1
changelog.d/1368.feature
Normal file
1
changelog.d/1368.feature
Normal file
@@ -0,0 +1 @@
|
||||
Improved the handling of restricted join rules and improved the performance of local-first joins. Contributed by @nex.
|
||||
1
changelog.d/1370.bugfix.md
Normal file
1
changelog.d/1370.bugfix.md
Normal file
@@ -0,0 +1 @@
|
||||
Fixed sliding sync not resolving wildcard state key requests, enabling Video/Audio calls in Element X.
|
||||
1
changelog.d/1372.feature.md
Normal file
1
changelog.d/1372.feature.md
Normal 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
|
||||
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"id": 9,
|
||||
"mention_room": false,
|
||||
"date": "2026-01-12",
|
||||
"message": "Hey everyone!\n\nJust letting you know we've released [v0.5.3](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.3) - this one is a bit of a hotfix for an issue with inviting and allowing others to join rooms.\n\nIf you appreceate the round-the-clock work we've been doing to keep your servers secure over this holiday period, we'd really appreciate your support - you can sponsor individuals on our team using the 'sponsor' button at the top of [our GitHub repository](https://github.com/continuwuity/continuwuity). If you can't do that, even a star helps - spreading the word and advocating for our project helps keep it going.\n\nHave a lovely rest of your year \\\n[Jade \\(she/her\\)](https://matrix.to/#/%40jade%3Aellis.link) \n🩵"
|
||||
"date": "2026-02-09",
|
||||
"message": "Yesterday we released [v0.5.4](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.4). Bugfixes, performance improvements and more moderation features! There's also a security fix, so please update as soon as possible. Don't forget to join [our announcements channel](https://matrix.to/#/!jIdNjSM5X-V5JVx2h2kAhUZIIQ08GyzPL55NFZAH1vM/%2489TY9CqRg4-ff1MGo3Ulc5r5X4pakfdzT-99RD8Docc?via=ellis.link&via=explodie.org&via=matrix.org) to get important information sooner <3 "
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -112,6 +112,19 @@ ### `!admin query resolver overrides-cache`
|
||||
|
||||
Query the overrides cache
|
||||
|
||||
### `!admin query resolver flush-cache`
|
||||
|
||||
Flush a given server from the resolver caches or flush them completely
|
||||
|
||||
* Examples:
|
||||
* Flush a specific server:
|
||||
|
||||
`!admin query resolver flush-cache matrix.example.com`
|
||||
|
||||
* Flush all resolver caches completely:
|
||||
|
||||
`!admin query resolver flush-cache --all`
|
||||
|
||||
## `!admin query pusher`
|
||||
|
||||
pusher service
|
||||
|
||||
@@ -20,6 +20,16 @@ ### Lost access to admin room
|
||||
|
||||
## General potential issues
|
||||
|
||||
### Configuration not working as expected
|
||||
|
||||
Sometimes you can make a mistake in your configuration that
|
||||
means things don't get passed to Continuwuity correctly.
|
||||
This is particularly easy to do with environment variables.
|
||||
To check what configuration Continuwuity actually sees, you can
|
||||
use the `!admin server show-config` command in your admin room.
|
||||
Beware that this prints out any secrets in your configuration,
|
||||
so you might want to delete the result afterwards!
|
||||
|
||||
### Potential DNS issues when using Docker
|
||||
|
||||
Docker's DNS setup for containers in a non-default network intercepts queries to
|
||||
|
||||
163
package-lock.json
generated
163
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use clap::Subcommand;
|
||||
use conduwuit::{Result, utils::time};
|
||||
use conduwuit::{Err, Result, utils::time};
|
||||
use futures::StreamExt;
|
||||
use ruma::OwnedServerName;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
/// Resolver service and caches
|
||||
pub enum ResolverCommand {
|
||||
/// Query the destinations cache
|
||||
@@ -18,6 +19,14 @@ pub enum ResolverCommand {
|
||||
OverridesCache {
|
||||
name: Option<String>,
|
||||
},
|
||||
|
||||
/// Flush a specific server from the resolver caches or everything
|
||||
FlushCache {
|
||||
name: Option<OwnedServerName>,
|
||||
|
||||
#[arg(short, long)]
|
||||
all: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
@@ -69,3 +78,18 @@ async fn overrides_cache(&self, server_name: Option<String>) -> Result {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn flush_cache(&self, name: Option<OwnedServerName>, all: bool) -> Result {
|
||||
if all {
|
||||
self.services.resolver.cache.clear().await;
|
||||
writeln!(self, "Resolver caches cleared!").await
|
||||
} else if let Some(name) = name {
|
||||
self.services.resolver.cache.del_destination(&name);
|
||||
self.services.resolver.cache.del_override(&name);
|
||||
self.write_str(&format!("Cleared {name} from resolver caches!"))
|
||||
.await
|
||||
} else {
|
||||
Err!("Missing name. Supply a name or use --all to flush the whole cache.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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."))
|
||||
}
|
||||
|
||||
@@ -253,7 +253,6 @@ async fn knock_room_by_id_helper(
|
||||
room_id,
|
||||
reason.clone(),
|
||||
servers,
|
||||
None,
|
||||
&None,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.")));
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
36
src/main/deadlock.rs
Normal 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");
|
||||
}
|
||||
@@ -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()))?;
|
||||
|
||||
|
||||
@@ -406,6 +406,10 @@ pub async fn get_admins(&self) -> Vec<OwnedUserId> {
|
||||
|
||||
/// Checks whether a given user is an admin of this server
|
||||
pub async fn user_is_admin(&self, user_id: &UserId) -> bool {
|
||||
if self.services.globals.server_user == user_id {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self
|
||||
.services
|
||||
.server
|
||||
|
||||
@@ -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)?
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{cmp, collections::HashMap};
|
||||
use std::{cmp, collections::HashMap, future::ready};
|
||||
|
||||
use conduwuit::{
|
||||
Err, Pdu, Result, debug, debug_info, debug_warn, error, info,
|
||||
Err, Event, Pdu, Result, debug, debug_info, debug_warn, error, info,
|
||||
result::NotFound,
|
||||
utils::{
|
||||
IterStream, ReadyExt,
|
||||
@@ -15,8 +15,9 @@
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
events::{
|
||||
GlobalAccountDataEventType, StateEventType, push_rules::PushRulesEvent,
|
||||
room::member::MembershipState,
|
||||
AnyStrippedStateEvent, GlobalAccountDataEventType, StateEventType,
|
||||
push_rules::PushRulesEvent,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
push::Ruleset,
|
||||
serde::Raw,
|
||||
@@ -162,6 +163,14 @@ async fn migrate(services: &Services) -> Result<()> {
|
||||
populate_userroomid_leftstate_table(services).await?;
|
||||
}
|
||||
|
||||
if db["global"]
|
||||
.get(FIXED_LOCAL_INVITE_STATE_MARKER)
|
||||
.await
|
||||
.is_not_found()
|
||||
{
|
||||
fix_local_invite_state(services).await?;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
services.globals.db.database_version().await,
|
||||
DATABASE_VERSION,
|
||||
@@ -721,3 +730,46 @@ async fn populate_userroomid_leftstate_table(services: &Services) -> Result {
|
||||
db.db.sort()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const FIXED_LOCAL_INVITE_STATE_MARKER: &str = "fix_local_invite_state";
|
||||
async fn fix_local_invite_state(services: &Services) -> Result {
|
||||
// Clean up the effects of !1249 by caching stripped state for invites
|
||||
|
||||
type KeyVal<'a> = (Key<'a>, Raw<Vec<AnyStrippedStateEvent>>);
|
||||
type Key<'a> = (&'a UserId, &'a RoomId);
|
||||
|
||||
let db = &services.db;
|
||||
let cork = db.cork_and_sync();
|
||||
let userroomid_invitestate = services.db["userroomid_invitestate"].clone();
|
||||
|
||||
// for each user invited to a room
|
||||
let fixed = userroomid_invitestate.stream()
|
||||
// if they're a local user on this homeserver
|
||||
.try_filter(|((user_id, _), _): &KeyVal<'_>| ready(services.globals.user_is_local(user_id)))
|
||||
.and_then(async |((user_id, room_id), stripped_state): KeyVal<'_>| Ok::<_, conduwuit::Error>((user_id.to_owned(), room_id.to_owned(), stripped_state.deserialize()?)))
|
||||
.try_fold(0_usize, async |mut fixed, (user_id, room_id, stripped_state)| {
|
||||
// and their invite state is None
|
||||
if stripped_state.is_empty()
|
||||
// and they are actually invited to the room
|
||||
&& let Ok(membership_event) = services.rooms.state_accessor.room_state_get(&room_id, &StateEventType::RoomMember, user_id.as_str()).await
|
||||
&& membership_event.get_content::<RoomMemberEventContent>().is_ok_and(|content| content.membership == MembershipState::Invite)
|
||||
// and the invite was sent by a local user
|
||||
&& services.globals.user_is_local(&membership_event.sender) {
|
||||
|
||||
// build and save stripped state for their invite in the database
|
||||
let stripped_state = services.rooms.state.summary_stripped(&membership_event, &room_id).await;
|
||||
userroomid_invitestate.put((&user_id, &room_id), Json(stripped_state));
|
||||
fixed = fixed.saturating_add(1);
|
||||
}
|
||||
|
||||
Ok(fixed)
|
||||
})
|
||||
.await?;
|
||||
|
||||
drop(cork);
|
||||
info!(?fixed, "Fixed local invite state cache entries.");
|
||||
|
||||
db["global"].insert(FIXED_LOCAL_INVITE_STATE_MARKER, []);
|
||||
db.db.sort()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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}")))
|
||||
})?;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use conduwuit::{
|
||||
Result, err, implement,
|
||||
Pdu, Result, err, implement,
|
||||
matrix::{Event, StateKey},
|
||||
};
|
||||
use futures::{Stream, StreamExt, TryFutureExt};
|
||||
@@ -84,7 +84,7 @@ pub async fn room_state_get(
|
||||
room_id: &RoomId,
|
||||
event_type: &StateEventType,
|
||||
state_key: &str,
|
||||
) -> Result<impl Event> {
|
||||
) -> Result<Pdu> {
|
||||
self.services
|
||||
.state
|
||||
.get_room_shortstatehash(room_id)
|
||||
|
||||
@@ -30,6 +30,7 @@ struct Services {
|
||||
config: Dep<config::Service>,
|
||||
globals: Dep<globals::Service>,
|
||||
metadata: Dep<rooms::metadata::Service>,
|
||||
state: Dep<rooms::state::Service>,
|
||||
state_accessor: Dep<rooms::state_accessor::Service>,
|
||||
users: Dep<users::Service>,
|
||||
}
|
||||
@@ -64,6 +65,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
config: args.depend::<config::Service>("config"),
|
||||
globals: args.depend::<globals::Service>("globals"),
|
||||
metadata: args.depend::<rooms::metadata::Service>("rooms::metadata"),
|
||||
state: args.depend::<rooms::state::Service>("rooms::state"),
|
||||
state_accessor: args
|
||||
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
|
||||
users: args.depend::<users::Service>("users"),
|
||||
|
||||
@@ -118,10 +118,8 @@ pub async fn update_membership(
|
||||
self.mark_as_joined(user_id, room_id);
|
||||
},
|
||||
| MembershipState::Invite => {
|
||||
// TODO: make sure that passing None for `last_state` is correct behavior.
|
||||
// the call from `append_pdu` used to use `services.state.summary_stripped`
|
||||
// to fill that parameter.
|
||||
self.mark_as_invited(user_id, room_id, pdu.sender(), None, None)
|
||||
let last_state = self.services.state.summary_stripped(pdu, room_id).await;
|
||||
self.mark_as_invited(user_id, room_id, pdu.sender(), Some(last_state), None)
|
||||
.await?;
|
||||
},
|
||||
| MembershipState::Leave | MembershipState::Ban => {
|
||||
|
||||
@@ -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)))
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user