mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-08-09 06:09:44 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb4dfafe2d | ||
|
|
8dbea51968 | ||
|
|
7b5905bbca | ||
|
|
42d143c013 | ||
|
|
10fb1cd192 | ||
|
|
1322ba1b00 | ||
|
|
d73b4332bf | ||
|
|
11eeca7e01 | ||
|
|
19dc5fafd4 | ||
|
|
54b347b855 | ||
|
|
8ed61aecb0 | ||
|
|
bf8aa57d03 | ||
|
|
a9f1926654 | ||
|
|
248d778290 | ||
|
|
2e13e87e43 | ||
|
|
ba19d407d0 | ||
|
|
de7c5dcbc8 | ||
|
|
adadafa88f | ||
|
|
deda746222 | ||
|
|
29c2c8a333 | ||
|
|
296018f0cc | ||
|
|
6295ca135a | ||
|
|
b5a9884194 | ||
|
|
1be7fd9247 | ||
|
|
be1264965a | ||
|
|
f6d2ce2f22 | ||
|
|
3cbe1e25cd | ||
|
|
897322964c | ||
|
|
996a5488be | ||
|
|
0ed4bd1e07 | ||
|
|
da79de5381 | ||
|
|
1bcf3ae19a | ||
|
|
91e500efe3 | ||
|
|
35b1ebaf3b | ||
|
|
ea5deb7e85 | ||
|
|
542cc51047 | ||
|
|
3225db0ddd | ||
|
|
a164da8b58 | ||
|
|
7221d466ce | ||
|
|
f6c88e3a16 | ||
|
|
3deebeab78 | ||
|
|
1d2818de58 | ||
|
|
86781522b6 | ||
|
|
4b49aaad53 | ||
|
|
3a78ba2b16 | ||
|
|
685946faed | ||
|
|
13c9385ef7 | ||
|
|
6a583a359e | ||
|
|
eec6e45358 | ||
|
|
804257eb52 | ||
|
|
a1677f05d3 | ||
|
|
1f77200299 | ||
|
|
888b93df91 | ||
|
|
a127724eaa | ||
|
|
00102c4276 | ||
|
|
b59af0530c | ||
|
|
44af849ed0 | ||
|
|
26e7a8e962 | ||
|
|
aa413ae601 | ||
|
|
7c19d1e1ff | ||
|
|
616f20f273 | ||
|
|
91efcf8a8e | ||
|
|
8708e98478 | ||
|
|
4bdc260015 | ||
|
|
7bc47e58d0 | ||
|
|
6172b9c8ec | ||
|
|
9dc16eeab7 | ||
|
|
402f7a2157 | ||
|
|
9c6e0e3350 | ||
|
|
12bdfe3b66 | ||
|
|
54ce221e88 | ||
|
|
33a16f4831 | ||
|
|
d8e6d95f4b | ||
|
|
9c290bc513 | ||
|
|
a4520424bd | ||
|
|
280d7733fc | ||
|
|
79e3c898ac | ||
|
|
757d38e891 | ||
|
|
4b66911886 | ||
|
|
9db3e35f41 | ||
|
|
7977512d5d | ||
|
|
e354be6830 | ||
|
|
f58081838e | ||
|
|
526a138021 | ||
|
|
7fa6894334 | ||
|
|
33d0ad1be2 | ||
|
|
fbc3892904 | ||
|
|
ce6212b161 | ||
|
|
0aea56f02a | ||
|
|
d05d3f710f | ||
|
|
7a3496869b | ||
|
|
146b72265d | ||
|
|
120a43cceb | ||
|
|
c40671bfb0 | ||
|
|
8e9c6661b2 | ||
|
|
6810604629 | ||
|
|
3dd984eff9 | ||
|
|
bcd4f80149 | ||
|
|
f51a7b12b5 | ||
|
|
3a1649bcaf | ||
|
|
d4421422b2 | ||
|
|
4884c55be7 | ||
|
|
a4f589f475 | ||
|
|
b5890b9664 | ||
|
|
26b3a84b88 | ||
|
|
3430d4ac86 | ||
|
|
3e2f2fbffa | ||
|
|
74deb14ca0 | ||
|
|
acd930b9ca | ||
|
|
e2db2029c5 | ||
|
|
4bb83a1972 | ||
|
|
751aa27859 | ||
|
|
628597c318 | ||
|
|
2c6dd78502 | ||
|
|
cdb0e31b21 | ||
|
|
6a6f77a100 | ||
|
|
a3666fe672 | ||
|
|
0c8df52d18 | ||
|
|
d7e5c0ccf3 |
@@ -0,0 +1,2 @@
|
||||
[env]
|
||||
RUMA_UNSTABLE_EXHAUSTIVE_TYPES = "true"
|
||||
@@ -106,6 +106,7 @@ jobs:
|
||||
excludes: ${{inputs.excludes}}
|
||||
includes: ${{inputs.includes}}
|
||||
|
||||
# disabled due to excessive build time issue installing cargo lychee
|
||||
lychee:
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
@@ -113,6 +114,7 @@ jobs:
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
|
||||
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
|
||||
&& false
|
||||
|
||||
name: Lychee
|
||||
uses: ./.github/workflows/bake.yml
|
||||
|
||||
@@ -58,8 +58,8 @@ jobs:
|
||||
- name: Initialize Builder
|
||||
env:
|
||||
runner: ${{matrix.runner}}
|
||||
reserved_space: '{"het": "384GB", "aws": "48GB", "gcp": "160GB"}'
|
||||
max_used_space: '{"het": "512GB", "aws": "64GB", "gcp": "192GB"}'
|
||||
reserved_space: '{"het": "128GB", "aws": "48GB", "gcp": "160GB"}'
|
||||
max_used_space: '{"het": "256GB", "aws": "64GB", "gcp": "192GB"}'
|
||||
run: |
|
||||
set +e
|
||||
docker buildx inspect "${GITHUB_ACTOR}"
|
||||
@@ -129,17 +129,21 @@ jobs:
|
||||
machines: ${{needs.init.outputs.machines}}
|
||||
excludes: >
|
||||
[
|
||||
{"cargo_profile": "test", "feat_set": "logging"},
|
||||
{"cargo_profile": "test", "rust_toolchain": "stable", "feat_set": "none"},
|
||||
{"cargo_profile": "test", "rust_target": "aarch64-unknown-linux-gnu"},
|
||||
{"cargo_profile": "release", "rust_toolchain": "nightly", "feat_set": "none"},
|
||||
{"cargo_profile": "release", "rust_toolchain": "nightly", "feat_set": "default"},
|
||||
{"cargo_profile": "release", "rust_toolchain": "nightly", "feat_set": "logging"},
|
||||
{"cargo_profile": "release-debuginfo"},
|
||||
{"cargo_profile": "release-native", "rust_toolchain": "stable"},
|
||||
{"cargo_profile": "release-native", "feat_set": "none"},
|
||||
{"cargo_profile": "release-native", "feat_set": "default"},
|
||||
{"cargo_profile": "release-native", "feat_set": "logging"},
|
||||
{"cargo_profile": "release-native", "rust_toolchain": "stable"},
|
||||
{"cargo_profile": "release-native", "rust_target": "aarch64-unknown-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "none"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "logging"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "rust_toolchain": "nightly"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
@@ -174,15 +178,17 @@ jobs:
|
||||
machines: ${{needs.init.outputs.machines}}
|
||||
excludes: >
|
||||
[
|
||||
{"feat_set": "logging"},
|
||||
{"cargo_profile": "test", "rust_toolchain": "stable", "feat_set": "none"},
|
||||
{"cargo_profile": "test", "rust_target": "aarch64-unknown-linux-gnu"},
|
||||
{"cargo_profile": "release-debuginfo"},
|
||||
{"cargo_profile": "release-native", "rust_toolchain": "stable"},
|
||||
{"cargo_profile": "release-native", "feat_set": "default"},
|
||||
{"cargo_profile": "release-native", "feat_set": "none"},
|
||||
{"cargo_profile": "release-native", "feat_set": "default"},
|
||||
{"cargo_profile": "release-native", "rust_toolchain": "stable"},
|
||||
{"cargo_profile": "release-native", "rust_target": "aarch64-unknown-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "none"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "logging"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "rust_toolchain": "nightly"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
@@ -228,6 +234,7 @@ jobs:
|
||||
{"cargo_profile": "test"},
|
||||
{"cargo_profile": "release-native"},
|
||||
{"cargo_profile": "release-debuginfo", "feat_set": "default"},
|
||||
{"cargo_profile": "release-debuginfo", "feat_set": "logging"},
|
||||
{"cargo_profile": "release-debuginfo", "rust_toolchain": "nightly"},
|
||||
{"cargo_profile": "release-debuginfo", "bake_target": "oci"},
|
||||
{"cargo_profile": "release-debuginfo", "rust_target": "aarch64-unknown-linux-gnu"},
|
||||
@@ -236,6 +243,7 @@ jobs:
|
||||
{"cargo_profile": "release-debuginfo", "sys_target": "x86_64-v4-linux-gnu"},
|
||||
{"cargo_profile": "release", "rust_toolchain": "nightly"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "logging"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
|
||||
@@ -245,10 +253,13 @@ jobs:
|
||||
{"sys_target": "x86_64-v1-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "logging"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "logging"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "logging"},
|
||||
]
|
||||
|
||||
publish:
|
||||
@@ -273,6 +284,7 @@ jobs:
|
||||
excludes: >
|
||||
[
|
||||
{"feat_set": "none"},
|
||||
{"feat_set": "logging"},
|
||||
{"cargo_profile": "test"},
|
||||
{"cargo_profile": "release-debuginfo"},
|
||||
{"cargo_profile": "release-native"},
|
||||
|
||||
@@ -45,6 +45,28 @@ on:
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
if: >
|
||||
contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
|
||||
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
|
||||
|
||||
name: Docs
|
||||
uses: ./.github/workflows/bake.yml
|
||||
with:
|
||||
bake_targets: '["docs"]'
|
||||
cargo_profiles: '["test"]'
|
||||
feat_sets: '["all"]'
|
||||
rust_toolchains: '["nightly"]'
|
||||
sys_names: ${{inputs.sys_names}}
|
||||
sys_versions: ${{inputs.sys_versions}}
|
||||
rust_targets: ${{inputs.rust_targets}}
|
||||
sys_targets: '["x86_64-v1-linux-gnu"]'
|
||||
machines: ${{inputs.machines}}
|
||||
excludes: ${{inputs.excludes}}
|
||||
includes: ${{inputs.includes}}
|
||||
|
||||
unit:
|
||||
if: >
|
||||
contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
|
||||
@@ -89,13 +111,16 @@ jobs:
|
||||
excludes: >
|
||||
[
|
||||
{"cargo_profile": "test", "feat_set": "default"},
|
||||
{"cargo_profile": "test", "feat_set": "logging"},
|
||||
{"cargo_profile": "test", "feat_set": "none", "bake_target": "smoke-valgrind"},
|
||||
{"cargo_profile": "test", "rust_toolchain": "stable"},
|
||||
{"cargo_profile": "test", "rust_target": "aarch64-unknown-linux-gnu"},
|
||||
{"cargo_profile": "test", "sys_target": "x86_64-v2-linux-gnu", "bake_target": "smoke-valgrind"},
|
||||
{"cargo_profile": "test", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
{"cargo_profile": "test", "sys_target": "x86_64-v3-linux-gnu"},
|
||||
{"cargo_profile": "release", "rust_toolchain": "nightly"},
|
||||
{"cargo_profile": "release", "rust_toolchain": "stable", "feat_set": "none"},
|
||||
{"cargo_profile": "release", "bake_target": "smoke-valgrind"},
|
||||
{"cargo_profile": "release-debuginfo", "feat_set": "logging"},
|
||||
{"cargo_profile": "release-debuginfo", "rust_toolchain": "nightly"},
|
||||
{"cargo_profile": "release-debuginfo", "rust_toolchain": "stable", "feat_set": "none"},
|
||||
{"cargo_profile": "release-debuginfo", "bake_target": "smoke"},
|
||||
@@ -103,6 +128,7 @@ jobs:
|
||||
{"cargo_profile": "release-native", "rust_toolchain": "stable"},
|
||||
{"cargo_profile": "release-native", "feat_set": "none"},
|
||||
{"cargo_profile": "release-native", "feat_set": "default"},
|
||||
{"cargo_profile": "release-native", "feat_set": "logging"},
|
||||
{"cargo_profile": "release-native", "bake_target": "smoke-valgrind"},
|
||||
{"cargo_profile": "release-native", "rust_target": "aarch64-unknown-linux-gnu"},
|
||||
{"cargo_profile": "release-native", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
@@ -110,6 +136,7 @@ jobs:
|
||||
{"cargo_profile": "release-native", "sys_target": "x86_64-v4-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "none"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "logging"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
|
||||
@@ -120,13 +147,16 @@ jobs:
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "none"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "logging"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "none"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "logging"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "bake_target": "smoke-valgrind"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "none"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "logging"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "bake_target": "smoke-valgrind"},
|
||||
]
|
||||
|
||||
|
||||
Generated
+646
-544
File diff suppressed because it is too large
Load Diff
+96
-113
@@ -29,7 +29,7 @@ license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-construct/tuwunel"
|
||||
rust-version = "1.88.0"
|
||||
version = "1.3.0"
|
||||
version = "1.4.2"
|
||||
|
||||
[workspace.metadata.crane]
|
||||
name = "tuwunel"
|
||||
@@ -39,7 +39,7 @@ name = "tuwunel"
|
||||
#
|
||||
|
||||
[workspace.dependencies.argon2]
|
||||
version = "0.5.3"
|
||||
version = "0.5"
|
||||
default-features = false
|
||||
features = [
|
||||
"alloc",
|
||||
@@ -47,17 +47,17 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.arrayvec]
|
||||
version = "0.7.6"
|
||||
version = "0.7"
|
||||
features = ["serde"]
|
||||
|
||||
[workspace.dependencies.async-channel]
|
||||
version = "2.3.1"
|
||||
|
||||
[workspace.dependencies.async-trait]
|
||||
version = "0.1.88"
|
||||
version = "0.1"
|
||||
|
||||
[workspace.dependencies.axum]
|
||||
version = "0.8.4"
|
||||
version = "0.8"
|
||||
default-features = false
|
||||
features = [
|
||||
"form",
|
||||
@@ -73,7 +73,7 @@ features = [
|
||||
version = "0.7"
|
||||
|
||||
[workspace.dependencies.axum-extra]
|
||||
version = "0.10.1"
|
||||
version = "0.10"
|
||||
default-features = false
|
||||
features = [
|
||||
"typed-header",
|
||||
@@ -81,7 +81,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.axum-server]
|
||||
version = "0.7.2"
|
||||
version = "0.7"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.axum-server-dual-protocol]
|
||||
@@ -89,11 +89,11 @@ version = "0.7"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.base64]
|
||||
version = "0.22.1"
|
||||
version = "0.22"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.blurhash]
|
||||
version = "0.2.3"
|
||||
version = "0.2"
|
||||
default-features = false
|
||||
features = [
|
||||
"fast-linear-to-srgb",
|
||||
@@ -101,13 +101,13 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.bytes]
|
||||
version = "1.10.1"
|
||||
version = "1.10"
|
||||
|
||||
[workspace.dependencies.bytesize]
|
||||
version = "2.0.1"
|
||||
version = "2.0"
|
||||
|
||||
[workspace.dependencies.cargo_toml]
|
||||
version = "0.22.1"
|
||||
version = "0.22"
|
||||
default-features = false
|
||||
features = ["features"]
|
||||
|
||||
@@ -115,12 +115,12 @@ features = ["features"]
|
||||
version = "0.1"
|
||||
|
||||
[workspace.dependencies.chrono]
|
||||
version = "0.4.41"
|
||||
version = "0.4"
|
||||
features = ["alloc", "std"]
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.clap]
|
||||
version = "4.5.39"
|
||||
version = "4.5"
|
||||
default-features = false
|
||||
features = [
|
||||
"derive",
|
||||
@@ -140,40 +140,31 @@ features = [
|
||||
version = "0.8.3"
|
||||
|
||||
[workspace.dependencies.const-str]
|
||||
version = "0.6.2"
|
||||
version = "0.6"
|
||||
|
||||
[workspace.dependencies.ctor]
|
||||
version = "0.4.2"
|
||||
version = "0.4"
|
||||
|
||||
[workspace.dependencies.cyborgtime]
|
||||
version = "2.1.1"
|
||||
version = "2.1"
|
||||
|
||||
[workspace.dependencies.either]
|
||||
version = "1.15.0"
|
||||
version = "1.15"
|
||||
default-features = false
|
||||
features = ["serde"]
|
||||
|
||||
[workspace.dependencies.figment]
|
||||
version = "0.10.19"
|
||||
version = "0.10"
|
||||
default-features = false
|
||||
features = ["env", "toml"]
|
||||
|
||||
[workspace.dependencies.futures]
|
||||
version = "0.3.31"
|
||||
version = "0.3"
|
||||
default-features = false
|
||||
features = ["std", "async-await"]
|
||||
|
||||
[workspace.dependencies.hardened_malloc-rs]
|
||||
version = "0.1.2"
|
||||
default-features = false
|
||||
features = [
|
||||
"static",
|
||||
"gcc",
|
||||
"light",
|
||||
]
|
||||
|
||||
[workspace.dependencies.hickory-resolver]
|
||||
version = "0.25.2"
|
||||
version = "0.25"
|
||||
default-features = false
|
||||
features = [
|
||||
"serde",
|
||||
@@ -182,17 +173,17 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.hmac]
|
||||
version = "0.12.1"
|
||||
version = "0.12"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.http]
|
||||
version = "1.3.1"
|
||||
version = "1.3"
|
||||
|
||||
[workspace.dependencies.http-body-util]
|
||||
version = "0.1.3"
|
||||
version = "0.1"
|
||||
|
||||
[workspace.dependencies.hyper]
|
||||
version = "1.6.0"
|
||||
version = "1.6"
|
||||
default-features = false
|
||||
features = [
|
||||
"server",
|
||||
@@ -210,7 +201,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.image]
|
||||
version = "0.25.6"
|
||||
version = "0.25"
|
||||
default-features = false
|
||||
features = [
|
||||
"jpeg",
|
||||
@@ -219,14 +210,20 @@ features = [
|
||||
"webp",
|
||||
]
|
||||
|
||||
[workspace.dependencies.insta]
|
||||
version = "1.43"
|
||||
features = [
|
||||
"json",
|
||||
]
|
||||
|
||||
[workspace.dependencies.ipaddress]
|
||||
version = "0.1.3"
|
||||
version = "0.1"
|
||||
|
||||
[workspace.dependencies.itertools]
|
||||
version = "0.14.0"
|
||||
version = "0.14"
|
||||
|
||||
[workspace.dependencies.jsonwebtoken]
|
||||
version = "9.3.1"
|
||||
version = "9.3"
|
||||
default-features = false
|
||||
features = ["use_pem"]
|
||||
|
||||
@@ -237,39 +234,39 @@ default-features = false
|
||||
features = ["sync", "tls-rustls"]
|
||||
|
||||
[workspace.dependencies.libc]
|
||||
version = "0.2.172"
|
||||
version = "0.2"
|
||||
|
||||
[workspace.dependencies.libloading]
|
||||
version = "0.8.8"
|
||||
version = "0.8"
|
||||
|
||||
[workspace.dependencies.log]
|
||||
version = "0.4.27"
|
||||
version = "0.4"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.loole]
|
||||
version = "0.4.1"
|
||||
version = "0.4"
|
||||
|
||||
[workspace.dependencies.lru-cache]
|
||||
version = "0.1.2"
|
||||
version = "0.1"
|
||||
|
||||
[workspace.dependencies.maplit]
|
||||
version = "1.0.2"
|
||||
version = "1.0"
|
||||
|
||||
[workspace.dependencies.minicbor]
|
||||
version = "1.0.0"
|
||||
version = "2.0"
|
||||
features = ["std"]
|
||||
|
||||
[workspace.dependencies.minicbor-serde]
|
||||
version = "0.5.0"
|
||||
version = "0.6"
|
||||
features = ["std"]
|
||||
|
||||
[workspace.dependencies.nix]
|
||||
version = "0.30.1"
|
||||
version = "0.30"
|
||||
default-features = false
|
||||
features = ["resource"]
|
||||
|
||||
[workspace.dependencies.num-traits]
|
||||
version = "0.2.19"
|
||||
version = "0.2"
|
||||
|
||||
[workspace.dependencies.opentelemetry]
|
||||
version = "0.30.0"
|
||||
@@ -280,23 +277,23 @@ version = "0.30.0"
|
||||
#features = ["rt-tokio"]
|
||||
|
||||
[workspace.dependencies.opentelemetry_sdk]
|
||||
version = "0.30.0"
|
||||
version = "0.30"
|
||||
features = ["rt-tokio"]
|
||||
|
||||
[workspace.dependencies.proc-macro2]
|
||||
version = "1.0.95"
|
||||
version = "1.0"
|
||||
|
||||
[workspace.dependencies.quote]
|
||||
version = "1.0.40"
|
||||
version = "1.0"
|
||||
|
||||
[workspace.dependencies.rand]
|
||||
version = "0.8.5"
|
||||
version = "0.8"
|
||||
|
||||
[workspace.dependencies.regex]
|
||||
version = "1.11.1"
|
||||
version = "1.11"
|
||||
|
||||
[workspace.dependencies.reqwest]
|
||||
version = "0.12.19"
|
||||
version = "0.12"
|
||||
default-features = false
|
||||
features = [
|
||||
"rustls-tls-native-roots",
|
||||
@@ -306,25 +303,24 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.ring]
|
||||
version = "0.17.14"
|
||||
version = "0.17"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://github.com/matrix-construct/ruma"
|
||||
rev = "0155c2b33233bec9dece79d5134a9574b347f4c1"
|
||||
rev = "5682b88cf1bcaf0f47805d614b476b242ef075d4"
|
||||
features = [
|
||||
"compat",
|
||||
"rand",
|
||||
"__compat",
|
||||
"appservice-api-c",
|
||||
"client-api",
|
||||
"client-api-s",
|
||||
"compat-upload-signatures",
|
||||
"federation-api",
|
||||
"identifiers-validation",
|
||||
"markdown",
|
||||
"push-gateway-api-c",
|
||||
"unstable-exhaustive-types",
|
||||
"rand",
|
||||
"ring-compat",
|
||||
"compat-upload-signatures",
|
||||
"identifiers-validation",
|
||||
"unstable-unspecified",
|
||||
"unstable-msc2448",
|
||||
"unstable-msc2666",
|
||||
"unstable-msc2867",
|
||||
@@ -332,24 +328,23 @@ features = [
|
||||
"unstable-msc3026",
|
||||
"unstable-msc3061",
|
||||
"unstable-msc3245",
|
||||
"unstable-msc3266",
|
||||
"unstable-msc3381", # polls
|
||||
"unstable-msc3489", # beacon / live location
|
||||
"unstable-msc3575",
|
||||
"unstable-msc3930", # polls push rules
|
||||
"unstable-msc4075",
|
||||
"unstable-msc4095",
|
||||
"unstable-msc4121",
|
||||
"unstable-msc4125",
|
||||
"unstable-msc4133",
|
||||
"unstable-msc4186",
|
||||
"unstable-msc4203", # sending to-device events to appservices
|
||||
"unstable-msc4210", # remove legacy mentions
|
||||
"unstable-msc4311",
|
||||
"unstable-extensible-events",
|
||||
"unstable-pdu",
|
||||
"unstable-hydra",
|
||||
]
|
||||
|
||||
[workspace.dependencies.rustls]
|
||||
version = "0.23.27"
|
||||
version = "0.23"
|
||||
default-features = false
|
||||
features = ["aws_lc_rs"]
|
||||
|
||||
@@ -370,14 +365,14 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.sanitize-filename]
|
||||
version = "0.6.0"
|
||||
version = "0.6"
|
||||
|
||||
[workspace.dependencies.sd-notify]
|
||||
version = "0.4.5"
|
||||
version = "0.4"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.sentry]
|
||||
version = "0.38.1"
|
||||
version = "0.42"
|
||||
default-features = false
|
||||
features = [
|
||||
"backtrace",
|
||||
@@ -393,48 +388,52 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.sentry-tower]
|
||||
version = "0.38.1"
|
||||
version = "0.42"
|
||||
|
||||
[workspace.dependencies.sentry-tracing]
|
||||
version = "0.38.1"
|
||||
version = "0.42"
|
||||
|
||||
[workspace.dependencies.serde]
|
||||
version = "1.0.219"
|
||||
version = "1.0"
|
||||
default-features = false
|
||||
features = ["rc"]
|
||||
|
||||
[workspace.dependencies.serde_html_form]
|
||||
version = "0.2.6"
|
||||
version = "0.2"
|
||||
|
||||
[workspace.dependencies.serde_json]
|
||||
version = "1.0.140"
|
||||
version = "1.0"
|
||||
default-features = false
|
||||
features = ["raw_value"]
|
||||
|
||||
[workspace.dependencies.serde_regex]
|
||||
version = "1.1.0"
|
||||
version = "1.1"
|
||||
|
||||
[workspace.dependencies.serde_yaml]
|
||||
version = "0.9.34"
|
||||
version = "0.9"
|
||||
|
||||
[workspace.dependencies.sha1]
|
||||
version = "0.10.6"
|
||||
version = "0.10"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.sha2]
|
||||
version = "0.10.9"
|
||||
version = "0.10"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.similar]
|
||||
version = "2.7"
|
||||
|
||||
[workspace.dependencies.smallstr]
|
||||
version = "0.3"
|
||||
features = [
|
||||
"ffi",
|
||||
"serde",
|
||||
"std",
|
||||
"union",
|
||||
]
|
||||
|
||||
[workspace.dependencies.smallvec]
|
||||
version = "1.15.1"
|
||||
version = "1.15"
|
||||
features = [
|
||||
"const_generics",
|
||||
"const_new",
|
||||
@@ -444,7 +443,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.syn]
|
||||
version = "2.0.101"
|
||||
version = "2.0"
|
||||
default-features = false
|
||||
features = [
|
||||
"extra-traits",
|
||||
@@ -455,11 +454,11 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.termimad]
|
||||
version = "0.33.0"
|
||||
version = "0.33"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.thiserror]
|
||||
version = "2.0.12"
|
||||
version = "2.0"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.tikv-jemallocator]
|
||||
@@ -487,7 +486,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.tokio]
|
||||
version = "1.46.0"
|
||||
version = "1.47"
|
||||
default-features = false
|
||||
features = [
|
||||
"fs",
|
||||
@@ -502,20 +501,20 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.tokio-metrics]
|
||||
version = "0.4.2"
|
||||
version = "0.4"
|
||||
|
||||
[workspace.dependencies.toml]
|
||||
version = "0.8.23"
|
||||
version = "0.9"
|
||||
default-features = false
|
||||
features = ["parse"]
|
||||
|
||||
[workspace.dependencies.tower]
|
||||
version = "0.5.2"
|
||||
version = "0.5"
|
||||
default-features = false
|
||||
features = ["util"]
|
||||
|
||||
[workspace.dependencies.tower-http]
|
||||
version = "0.6.6"
|
||||
version = "0.6"
|
||||
default-features = false
|
||||
features = [
|
||||
"add-extension",
|
||||
@@ -529,21 +528,21 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.tracing]
|
||||
version = "0.1.41"
|
||||
version = "0.1"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.tracing-core]
|
||||
version = "0.1.33"
|
||||
version = "0.1"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.tracing-flame]
|
||||
version = "0.2.0"
|
||||
version = "0.2"
|
||||
|
||||
[workspace.dependencies.tracing-opentelemetry]
|
||||
version = "0.31.0"
|
||||
version = "0.31"
|
||||
|
||||
[workspace.dependencies.tracing-subscriber]
|
||||
version = "0.3.19"
|
||||
version = "0.3"
|
||||
default-features = false
|
||||
features = [
|
||||
"ansi",
|
||||
@@ -555,35 +554,18 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.url]
|
||||
version = "2.5.4"
|
||||
version = "2.5"
|
||||
default-features = false
|
||||
features = ["serde"]
|
||||
|
||||
[workspace.dependencies.webpage]
|
||||
version = "2.0.1"
|
||||
version = "2.0"
|
||||
default-features = false
|
||||
|
||||
#
|
||||
# Patches
|
||||
#
|
||||
|
||||
# backport of [https://github.com/tokio-rs/tracing/pull/2956] to the 0.1.x
|
||||
# branch of tracing. we can switch back to upstream if #2956 is merged and
|
||||
# backported in the upstream repo.
|
||||
# https://github.com/girlbossceo/tracing/commit/b348dca742af641c47bc390261f60711c2af573c
|
||||
[patch.crates-io.tracing-subscriber]
|
||||
git = "https://github.com/matrix-construct/tracing"
|
||||
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
|
||||
[patch.crates-io.tracing]
|
||||
git = "https://github.com/matrix-construct/tracing"
|
||||
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
|
||||
[patch.crates-io.tracing-core]
|
||||
git = "https://github.com/matrix-construct/tracing"
|
||||
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
|
||||
[patch.crates-io.tracing-log]
|
||||
git = "https://github.com/matrix-construct/tracing"
|
||||
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
|
||||
|
||||
# adds a tab completion callback:
|
||||
# https://github.com/girlbossceo/rustyline-async/commit/de26100b0db03e419a3d8e1dd26895d170d1fe50
|
||||
# adds event for CTRL+\:
|
||||
@@ -915,6 +897,7 @@ pedantic = { level = "warn", priority = -1 }
|
||||
|
||||
## some sadness
|
||||
too_long_first_doc_paragraph = { level = "allow", priority = 1 }
|
||||
default_trait_access = { level = "allow", priority = 1 }
|
||||
doc_markdown = { level = "allow", priority = 1 }
|
||||
enum_glob_use = { level = "allow", priority = 1 }
|
||||
if_not_else = { level = "allow", priority = 1 }
|
||||
@@ -972,7 +955,7 @@ semicolon_outside_block = "warn"
|
||||
str_to_string = "warn"
|
||||
string_lit_chars_any = "warn"
|
||||
string_slice = "warn"
|
||||
string_to_string = "warn"
|
||||
implicit_clone = "warn"
|
||||
suspicious_xor_used_as_pow = "warn"
|
||||
tests_outside_test_module = "warn"
|
||||
try_err = "warn"
|
||||
|
||||
@@ -21,7 +21,7 @@ ## High Performance Matrix Homeserver in Rust!
|
||||
[bot](https://matrix.org/ecosystem/integrations/). It is written entirely in Rust to be a scalable,
|
||||
lightweight, low-cost, community-driven alternative covering all but the most niche uses.
|
||||
|
||||
This project is the official successor to [conduwuit](https://github.com/girlbossceo/conduwuit), which
|
||||
This project is the official successor to conduwuit, which
|
||||
was a featureful and high-performance fork of [Conduit](https://gitlab.com/famedly/conduit), all
|
||||
community-lead homeservers implementing the compatible
|
||||
[Matrix Specification](https://spec.matrix.org/latest/).
|
||||
@@ -57,12 +57,12 @@ ### Getting Started
|
||||
`/etc/caddy/Caddyfile` configuration with [Element](https://github.com/element-hq/element-web/releases)
|
||||
unzipped to `/var/www/element`:
|
||||
```
|
||||
https://tuwunel.me:8448 {
|
||||
reverse_proxy http://127.0.0.1:8008
|
||||
tuwunel.me, tuwunel.me:8448 {
|
||||
reverse_proxy localhost:8008
|
||||
}
|
||||
https://tuwunel.me:443 {
|
||||
root * /var/www/element/
|
||||
file_server
|
||||
web.tuwunel.me {
|
||||
root * /var/www/element/
|
||||
file_server
|
||||
}
|
||||
```
|
||||
`caddy reload --config /etc/caddy/Caddyfile`
|
||||
@@ -75,7 +75,7 @@ ### Getting Started
|
||||
|
||||
🤗 Did you find this and other documentation helpful? We would love to hear feedback about setting
|
||||
up Tuwunel.
|
||||
|
||||
|
||||
|
||||
### Migrating to Tuwunel
|
||||
|
||||
@@ -110,22 +110,11 @@ ### Upgrading & Downgrading Tuwunel
|
||||
safe but often prevented by a guard. An error will indicate the downgrade is not possible and a
|
||||
newer version which does not error must be sought.
|
||||
|
||||
#### Versioning
|
||||
|
||||
Tuwunel uses a semantic version tag in the format of `v<major>.<minor>.<patch>`. The `patch` value will
|
||||
always correspond to the number of commits from the last `minor` change with significance to developers.
|
||||
The `minor` version is changed for fixes or features significant to users. The `major` value corresponds
|
||||
to significant feature evolutions, and does not indicate any "breaking change" nor connote stability.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Users should attempt to keep up to date with the latest minor version.**
|
||||
|
||||
#### Branches
|
||||
|
||||
The main branch is always _reasonably safe_ to run. We understand the propensity for users to simply clone
|
||||
the main branch to get up and running, and we're obliged to ensure it's always viable. Nevertheless, only
|
||||
tagged releases are true releases. If you don't care to update often, find the latest `minor` version
|
||||
change rather than `patch`. We don't recommend simply following `major` version changes at this time.
|
||||
tagged releases are true releases.
|
||||
|
||||
#### Container Tracking
|
||||
|
||||
|
||||
+28
-30
@@ -1,59 +1,57 @@
|
||||
# Tuwunel 1.3.0
|
||||
# Tuwunel 1.4.2
|
||||
|
||||
August 4, 2025
|
||||
|
||||
Preparations for [Project Hydra](https://matrix.org/blog/2025/07/security-predisclosure/) have taken place. Users should be prepared to upgrade to `v1.4.0-rc` next week. Due to the comprehensive low-level changes which have taken place, and the inability to develop them in public with your feedback, the release will undergo an extended `-rc` period. Due to the time difference and scheduling conflicts our release may be published between 12 to 36 hours after the suggested time; though well before the written disclosure of the issues.
|
||||
|
||||
Some foundational work for `v1.4.0` was backported into this release after the announcement was made mid-July to further extend Hydra. An unexpected performance improvement drastically reduced CPU usage. As a result, integration tests began to flake, uncovering latent concurrency bugs which were addressed. These fixes primarily impact the legacy and sliding-sync systems, and further fixes improve performance and compliance, though mostly in the legacy system as sliding-sync lacks adequate test coverage.
|
||||
|
||||
This release fixes many bugs and improves performance but at the cost of planned features further rescheduled to either `v1.4.1` or `v1.5.0`.
|
||||
September 12, 2025
|
||||
|
||||
Users running maubot, neochat, or any client or bridge not excluded below should update to this patch as soon as possible to reduce unnecessary resource consumption. (see: Bug Fixes)
|
||||
|
||||
### New Features
|
||||
|
||||
- Refresh tokens (MSC2918) have been implemented. Supporting clients can now timeout their access tokens with a soft-logout after a configured `access_token_ttl`. This feature was commissioned and made public by an enterprise sponsor.
|
||||
- Requested by @alaviss an alternative DNS resolver has been implemented for use with appservices and other configured targets intended for local networks. This passthru performs minimal caching and cannot be used for federation. Enable with `dns_passthru_appservices = true` or specifying hosts in `dns_passthru_domains` (#158)
|
||||
|
||||
- Typing indicators have been added to sliding-sync thanks to @tmayoff. This feature (and the whole of sliding-sync) is still experimental and the indicator may not always appear or disappear as intended, nevertheless the effort will be enhanced by foundational fixes improving sliding-sync requested soon by the project's sponsor.
|
||||
- Contributed by @tototomate123 a nifty experimental feature can disable push notifications when you're active on one device from being sent to others. This can be enabled with `suppress_push_when_active`. Please thank them when your pocket stops vibrating while chatting on your desktop! (#150)
|
||||
|
||||
- Thanks to a report by @DetermineAbsurd the `m.federate` field can be defaulted to false when creating a room using the new `federate_created_rooms` config option. (#151)
|
||||
|
||||
- At the request of @grinapo verbose logging builds are now bundled with this release. These builds are found with the feature-set `-logging-` which is otherwise similar to `-all-`. This contains more messages at all levels optimized away in other release modes; it comes at some performance penalty.
|
||||
|
||||
- JWT tokens can now be used for authentication on any endpoint which supports UIA. For example: an external forgot-password service can send a token to the `client/account/password` endpoint to reset a user's password. This feature was commissioned and made public by an enterprise sponsor.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- @dasha_uwu maintains their streak as a serial contributor by patching the `!admin query raw` command with a base64 option allowing for low-level debugging of database records.
|
||||
- Sliding-sync has been significantly refactored. Performance has massively increased with many bugs and compliance issues also fixed. Please be aware we are tracking an issue related to read-marker behavior in Element X. The 🟢 dot does not unconditionally clear at every touch. Whether this is a feature or a bug, or both, is being investigated for v1.5.
|
||||
|
||||
- @obioma has improved the documentation explaining how to use multiple configuration files with precedence.
|
||||
- Hydra backports are now enabled by default. The change should be completely transparent. If you do notice any increased load try to increase the `cache_capacity_modifier` above default.
|
||||
|
||||
- Upon recommendation of @grin a basic request ID has been added to the tracing logs to uniquely distinguish each request while it's interleaved among others.
|
||||
- Room deletions now also purge synctokens which can be significant to the overall storage consumed by a room. Users who have already deleted rooms please be assured an update planned for v1.5 will deal with cleansing synctokens in general.
|
||||
|
||||
- Requested by @fruzitent this and future releases are tagged by version as multi-arch docker images to be properly archived in the registry rather than simply overwriting `:latest`.
|
||||
|
||||
- Event processing performance has been improved by fetching and processing `prev_events` and `auth_events` concurrently. This reduces the impact of recursing large graphs while the room's mutex is locked.
|
||||
|
||||
- An experimental command `!admin debug resync-database` has been added for developers curious about #35
|
||||
- Room version 1 and 2 support took a step forward, possibly working for some rooms but is not yet considered adequately supported and the ticket remains open. (#12)
|
||||
|
||||
- Thanks to @AreYouLoco for contributing an updated Kubernetes [Helm Chart](https://github.com/AreYouLoco/tuwunel-helm); link added to docs.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Thanks to @tmayoff room avatars are properly calculated and no longer the same for all spaces (https://github.com/matrix-construct/tuwunel/pull/102).
|
||||
- **Special thanks to @frebib for investigating a bug which triggers the uploading of unnecessary encryption one-time-keys.** Running over ten maubot instances it became obvious after observing increased resources and laggy bot response. This update removes any excess keys for a device. Thanks to @duckbuster for confirming neochat is affected. Clients confirmed unaffected include: Element, Element X, Nheko. Fractal, Cinny, matrix-rust-sdk and matrix-js-sdk clients and bots are probably unaffected. Mautrix-based bridges are probably affected. Users of unaffected clients should still upgrade.
|
||||
|
||||
- Courtesy of @coolGi69 our bump to Rust 1.88 was properly updated for Nix. Apologies to the Nix community for getting this wrong the first time.
|
||||
- Thanks @dasha_uwu for refactoring alias resolution logic with fixes to remain compatible with the upcoming element-web release. This was an incredibly valuable contribution which will spare all of us from impending grief; the kind of ahead-of-the-game initiative I don't think a project like this could exist without. (adadafa88f3)
|
||||
|
||||
- Invite rejections have been fixed, this was due to a misinterpretation of the spec in legacy sync.
|
||||
- Room deletions now preserve a small number of records to properly synchronize with local clients and remote servers after the room vanishes. Prior behavior is maintained with a `--force` flag added to the command.
|
||||
|
||||
- Knock rooms might have been buggy from the ambiguous overuse of the word "count" in the codebase. Database records which expected a summation of the users in a room instead received the sequence number of the server, both are called "count."
|
||||
- Thanks @scvalex for once again cleaning up our mess after Nix found the github CI was not running doctests. Thank you for contributing the patch 🙏 (#152).
|
||||
|
||||
- Room knocks failed to wakeup the sync systems; some cases of account_data changes also failed to wakeup the sync systems. These have been addressed.
|
||||
- Thanks @Tronde for reporting a broken link to the CoC in the mdbook documentation. (#155)
|
||||
|
||||
- The main sequence number fundamental to the entire server's operation (the "count" or counter) has been refactored after having exceeded architectural limitations. It has been replaced by a two-phase counter ensuring read-after-write consistency, and quasi-transactions grouping multiple writes.
|
||||
- Specification compliance required the `/joined_rooms` endpoint be restricted to current members rather than including past members. (4b49aaad53a)
|
||||
|
||||
- Sequence issues have been addressed in both legacy and sliding sync. These systems operate using a "snapshot" approach which intentionally ignores new data received by the server after the sync request has started; the snapshot approach replaced the complex of mutexes used by Conduit. The server was not originally designed for this approach and some information "from the future" continued to leak into the snapshot's window; these leaks have been sealed.
|
||||
- Specification compliance required state events be made visible to prior members of a room where `history_visibility=shared`. (86781522b68)
|
||||
|
||||
- Protocol compliance issues in legacy sync have been addressed. Additional compliance tests for device list updates now pass. The `state` and `timeline` on incremental sync provide expected results in more (if not all) cases.
|
||||
- The `limit` parameter to the `/context` endpoint is now divided with de facto compatibility (matrix-org/matrix-spec#2202)
|
||||
|
||||
- Errors requiring M_BAD_ALIAS instead of M_UNKNOWN when sending `m.room.canonical_alias` are now conforming.
|
||||
- The room avatar in sliding sync is now computed with greater compliance to the specification (3deebeab78f). This builds off earlier work done by @tmayoff in (a340e6786db).
|
||||
|
||||
- The canonical alias for a room is considered invalid if the primary alias is missing or removed (7221d466ce8). This is a T&S concern and we encourage reports for any other contexts where this condition should be applied.
|
||||
|
||||
- Presence is no longer updated by the private read-receipt or read-marker paths, only public receipts.
|
||||
|
||||
### Deprecations
|
||||
|
||||
- Unauthenticated media fallbacks are no longer requested by default. This can still be enabled with `request_legacy_media` if desired.
|
||||
|
||||
- Legacy Sliding-Sync has been removed in favor of Simplified Sliding-Sync. Clients which exclusively using Sliding-Sync have already migrated around the start of this year, so this removal should have no impact now.
|
||||
- Hardened Malloc support had to be removed after the build broke. We will gladly add support back upon request or contribution.
|
||||
|
||||
@@ -167,7 +167,7 @@ RUN \
|
||||
EOF
|
||||
|
||||
WORKDIR /usr/src
|
||||
ADD https://github.com/matrix-construct/complement.git#81f3b61e10ec102a36921890c81b79564ebf6f40 complement
|
||||
ADD https://github.com/matrix-construct/complement.git#403840348f6bcc9cc8ed1671dc2f638c2b1ce4ac complement
|
||||
|
||||
WORKDIR /usr/src/complement
|
||||
ENV COMPLEMENT_BASE_IMAGE="complement-testee"
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Docker Builder
|
||||
|
||||
> [!NOTE]
|
||||
> This directory is for building docker images. For [deploying](https://github.com/matrix-construct/tuwunel/tree/main/docs/deploying)
|
||||
> [docker](https://github.com/matrix-construct/tuwunel/blob/main/docs/deploying/docker.md) visit
|
||||
> the appropriate documentation instead.
|
||||
|
||||
All Docker images for the project are built here. All images are
|
||||
[Docker Bake](https://docs.docker.com/build/bake/) targets. All targets are leaves and
|
||||
branches of a unified tree leading to a single root. It is a combinatorial matrix
|
||||
|
||||
+30
-7
@@ -29,8 +29,12 @@ variable "git_ref_name" {
|
||||
|
||||
cargo_feat_sets = {
|
||||
none = ""
|
||||
# Default features
|
||||
default = "brotli_compression,element_hacks,gzip_compression,io_uring,jemalloc,jemalloc_conf,media_thumbnail,release_max_log_level,systemd,url_preview,zstd_compression"
|
||||
all = "blurhashing,brotli_compression,bzip2_compression,tuwunel_mods,console,default,direct_tls,element_hacks,gzip_compression,hardened_malloc,io_uring,jemalloc,jemalloc_conf,jemalloc_prof,jemalloc_stats,ldap,lz4_compression,media_thumbnail,perf_measurements,release_max_log_level,sentry_telemetry,systemd,tokio_console,url_preview,zstd_compression"
|
||||
# All features sans release_max_log_level
|
||||
logging = "blurhashing,brotli_compression,bzip2_compression,console,direct_tls,element_hacks,gzip_compression,io_uring,jemalloc,jemalloc_conf,jemalloc_prof,jemalloc_stats,ldap,lz4_compression,media_thumbnail,perf_measurements,sentry_telemetry,systemd,tokio_console,tuwunel_mods,url_preview,zstd_compression"
|
||||
# All features
|
||||
all = "blurhashing,brotli_compression,bzip2_compression,console,direct_tls,element_hacks,gzip_compression,io_uring,jemalloc,jemalloc_conf,jemalloc_prof,jemalloc_stats,ldap,lz4_compression,media_thumbnail,perf_measurements,release_max_log_level,sentry_telemetry,systemd,tokio_console,tuwunel_mods,url_preview,zstd_compression"
|
||||
}
|
||||
variable "cargo_features_always" {
|
||||
default = "direct_tls"
|
||||
@@ -233,6 +237,7 @@ group "lints" {
|
||||
|
||||
group "tests" {
|
||||
targets = [
|
||||
"docs",
|
||||
"unit",
|
||||
"smoke",
|
||||
"complement",
|
||||
@@ -809,6 +814,25 @@ target "unit" {
|
||||
}
|
||||
}
|
||||
|
||||
target "docs" {
|
||||
name = elem("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
target = "cargo"
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
]
|
||||
contexts = {
|
||||
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = {
|
||||
cargo_cmd = "test"
|
||||
cargo_args = "--doc --no-fail-fast"
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Workspace builds
|
||||
#
|
||||
@@ -835,10 +859,10 @@ target "book" {
|
||||
EOF
|
||||
}
|
||||
|
||||
target "docs" {
|
||||
name = elem("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
target "build-docs" {
|
||||
name = elem("build-docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
elem_tag("build-docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
@@ -1035,7 +1059,7 @@ target "fmt" {
|
||||
input = elem("target:ingredients", [rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = {
|
||||
fmt_args = "-- --color always"
|
||||
fmt_args = "-- --color=always"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1504,7 +1528,7 @@ cargo_installs = [
|
||||
"cargo-deb",
|
||||
#"cargo-arch",
|
||||
"cargo-generate-rpm",
|
||||
"lychee",
|
||||
#"lychee",
|
||||
"mdbook",
|
||||
"typos-cli",
|
||||
]
|
||||
@@ -1613,7 +1637,6 @@ target "kitchen" {
|
||||
args = {
|
||||
packages = join(" ", [
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")? "libbz2-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "hardened_malloc")? "g++": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")? "liburing-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "jemalloc")? "libjemalloc-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")? "liblz4-dev": "",
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ toolchain_toml="$docker_dir/../rust-toolchain.toml"
|
||||
rust_msrv=$(grep "channel = " "$toolchain_toml" | cut -d'=' -f2 | sed 's/\s"\|"$//g')
|
||||
rocksdb_opt_level=3
|
||||
rocksdb_portable=1
|
||||
git_checkout="HEAD"
|
||||
git_checkout="${git_checkout:-HEAD}"
|
||||
use_chef="true"
|
||||
set +a
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ skip="${skip}|TestRoomCreate/Parallel/POST_/createRoom_makes_a_room_with_a_topic
|
||||
skip="${skip}|TestLogin/parallel/POST_/"
|
||||
skip="${skip}|TestUnbanViaInvite"
|
||||
skip="${skip}|TestRoomState/Parallel/GET_/publicRooms_lists_newly-created_room"
|
||||
# flakes due to timeout in debug-mode
|
||||
skip="${skip}|TestMSC4297StateResolutionV2_1_starts_from_empty_set"
|
||||
skip="${skip}|TestMSC4297StateResolutionV2_1_includes_conflicted_subgraph"
|
||||
|
||||
set -a
|
||||
cargo_profile="${cargo_profile:-$default_cargo_profile}"
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../CODE_OF_CONDUCT.md
|
||||
+1
-1
@@ -20,4 +20,4 @@ # Summary
|
||||
- [Contributing](contributing.md)
|
||||
- [Testing](development/testing.md)
|
||||
- [Hot Reloading ("Live" Development)](development/hot_reload.md)
|
||||
- [Tuwunel Community Code of Conduct](../CODE_OF_CONDUCT.md)
|
||||
- [Tuwunel Community Code of Conduct](CODE_OF_CONDUCT.md)
|
||||
|
||||
@@ -153,7 +153,7 @@ ### Caddy
|
||||
```caddyfile
|
||||
your.server.name, your.server.name:8448 {
|
||||
# TCP reverse_proxy
|
||||
reverse_proxy 127.0.0.1:6167
|
||||
reverse_proxy localhost:8008
|
||||
# UNIX socket
|
||||
#reverse_proxy unix//run/tuwunel/tuwunel.sock
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ # Tuwunel for Kubernetes
|
||||
|
||||
Tuwunel doesn't support horizontal scalability or distributed loading
|
||||
natively, however a community maintained Helm Chart is available here to run
|
||||
Tuwunel on Kubernetes: <https://gitlab.cronce.io/charts/conduwuit>
|
||||
Tuwunel on Kubernetes: <https://github.com/AreYouLoco/tuwunel-helm> and the
|
||||
legacy conduwuit version: <https://gitlab.cronce.io/charts/conduwuit>.
|
||||
|
||||
Should changes need to be made, please reach out to the maintainer in our
|
||||
Matrix room as this is not maintained/controlled by the Tuwunel maintainers.
|
||||
|
||||
+100
-46
@@ -1,17 +1,11 @@
|
||||
# Troubleshooting Tuwunel
|
||||
|
||||
> ## Docker users ⚠️
|
||||
>
|
||||
> Docker is extremely UX unfriendly. Because of this, a ton of issues or support
|
||||
> is actually Docker support, not tuwunel support. We also cannot document the
|
||||
> ever-growing list of Docker issues here.
|
||||
>
|
||||
> [!IMPORTANT]
|
||||
> If you intend on asking for support and you are using Docker, **PLEASE**
|
||||
> triple validate your issues are **NOT** because you have a misconfiguration in
|
||||
> your Docker setup.
|
||||
>
|
||||
> If there are things like Compose file issues or Dockerhub image issues, those
|
||||
> can still be mentioned as long as they're something we can fix.
|
||||
> your Docker setup. We must remain focused on supporting Tuwunel issues and
|
||||
> cannot budget our time for generic Docker support. Compose file issues or
|
||||
> Dockerhub image issues are okay if they are something we can fix.
|
||||
|
||||
## Tuwunel and Matrix issues
|
||||
|
||||
@@ -93,48 +87,107 @@ ## RocksDB / database issues
|
||||
|
||||
#### Database corruption
|
||||
|
||||
If your database is corrupted *and* is failing to start (e.g. checksum
|
||||
mismatch), it may be recoverable but careful steps must be taken, and there is
|
||||
no guarantee it may be recoverable.
|
||||
There are many causes and varieties of database corruption. There are several
|
||||
methods for mitigation, each with outcomes ranging from a recovered state down
|
||||
to a savage state. This guide has been simplified into a set of universal steps
|
||||
which everyone can follow from the top until they have recovered or reach the
|
||||
end. The details and implications will be explained within each step.
|
||||
|
||||
The first thing that can be done is launching Tuwunel with the
|
||||
`rocksdb_repair` config option set to true. This will tell RocksDB to attempt to
|
||||
repair itself at launch. If this does not work, disable the option and continue
|
||||
reading.
|
||||
> [!TIP]
|
||||
> All command-line `-O` options can be expressed as environment variables or in
|
||||
> the config file based on your deployment's requirements. Note that
|
||||
> `--maintenance` is equivalent to configuring `startup_netburst = false` and
|
||||
> `listening = false`.
|
||||
|
||||
RocksDB has the following recovery modes:
|
||||
> [!IMPORTANT]
|
||||
> Always create a backup of the database before running any operation. This is
|
||||
> critical for steps 3 and above.
|
||||
|
||||
- `TolerateCorruptedTailRecords`
|
||||
- `AbsoluteConsistency`
|
||||
- `PointInTime`
|
||||
- `SkipAnyCorruptedRecord`
|
||||
**0. Start the server with the following options:**
|
||||
|
||||
By default, Tuwunel uses `TolerateCorruptedTailRecords` as generally these may
|
||||
be due to bad federation and we can re-fetch the correct data over federation.
|
||||
The RocksDB default is `PointInTime` which will attempt to restore a "snapshot"
|
||||
of the data when it was last known to be good. This data can be either a few
|
||||
seconds old, or multiple minutes prior. `PointInTime` may not be suitable for
|
||||
default usage due to clients and servers possibly not being able to handle
|
||||
sudden "backwards time travels", and `AbsoluteConsistency` may be too strict.
|
||||
`tuwunel --maintenance -O rocksdb_recovery_mode=0`
|
||||
|
||||
`AbsoluteConsistency` will fail to start the database if any sign of corruption
|
||||
is detected. `SkipAnyCorruptedRecord` will skip all forms of corruption unless
|
||||
it forbids the database from opening (e.g. too severe). Usage of
|
||||
`SkipAnyCorruptedRecord` voids any support as this may cause more damage and/or
|
||||
leave your database in a permanently inconsistent state, but it may do something
|
||||
if `PointInTime` does not work as a last ditch effort.
|
||||
This is actually a "control" and not a method of recovery. If the server starts
|
||||
you either do not have corruption or have deep corruption indicated by very
|
||||
specific errors from rocksdb citing corruption during runtime. If you are
|
||||
certain there is deep corruption skip to step 4, otherwise you are finished
|
||||
without any modifications.
|
||||
|
||||
With this in mind:
|
||||
**1. Start the server in Tolerate-Corrupted-Tail-Records mode:**
|
||||
|
||||
- First start Tuwunel with the `PointInTime` recovery method. See the [example
|
||||
config](configuration/examples.md) for how to do this using
|
||||
`rocksdb_recovery_mode`
|
||||
- If your database successfully opens, clients are recommended to clear their
|
||||
client cache to account for the rollback
|
||||
- Leave your Tuwunel running in `PointInTime` for at least 30-60 minutes so as
|
||||
much possible corruption is restored
|
||||
- If all goes will, you should be able to restore back to using
|
||||
`TolerateCorruptedTailRecords` and you have successfully recovered your database
|
||||
`tuwunel --maintenance -O rocksdb_recovery_mode=1`
|
||||
|
||||
The most common corruption scenario is from a loss of power to the hardware
|
||||
(not an application crash, though it is still possible). This is remediated
|
||||
by dropping the most recently written record. It is highly unlikely there will
|
||||
be any impact on the application from this loss. In the best-case the same data
|
||||
is often re-requested over the federation or replaced by a client. In the
|
||||
worst-case clients may need to clear-cache & reload to guarantee correctness.
|
||||
If the server starts you are finished.
|
||||
|
||||
**2. Start the server in Point-In-Time mode:**
|
||||
|
||||
`tuwunel --maintenance -O rocksdb_recovery_mode=2`
|
||||
|
||||
Similar to the corruption scenario above but for more severe cases. The most
|
||||
recent records are discarded back to the point where there is no corruption.
|
||||
It is highly unlikely there will be any impact on the application from this
|
||||
loss, but it is more likely than above that clients may need to clear-cache
|
||||
& reload to correctly resynchronize with the server.
|
||||
|
||||
**3. Start the server in Skip-Any-Corrupted-Record mode:**
|
||||
|
||||
> [!WARNING]
|
||||
> Salvage mode potentially impacting the application's ability to function.
|
||||
> We cannot provide support for users who have entered this mode.
|
||||
|
||||
`tuwunel --maintenance -O rocksdb_recovery_mode=3`
|
||||
|
||||
Similar to the prior corruption scenarios but for the most severe cases.
|
||||
The database will be inconsistent. It is theoretically possible for the
|
||||
server to continue functioning without notable issue in the best case, but
|
||||
it is completely uncertain what the effect of this operation will be. If
|
||||
the server starts you should immediately export your messages, encryption
|
||||
keys, etc, in a salvage effort and prepare to reinstall.
|
||||
|
||||
**4. Start the server in repair mode.**
|
||||
|
||||
> [!WARNING]
|
||||
> Salvage mode potentially impacting the application's ability to function.
|
||||
> We cannot provide support for users who have entered this mode.
|
||||
|
||||
> [!CAUTION]
|
||||
> Always create a backup of the database before entering this mode. The repair
|
||||
> is not configurable and not interactive. It may automatically remove more
|
||||
> data than anticipated, preventing further salvage efforts.
|
||||
|
||||
`tuwunel --maintenance -O rocksdb_repair=true`
|
||||
|
||||
For corruption affecting the bulk database tables not covered by any journal.
|
||||
This will leave the database in an inconsistent and unpredictable state. It
|
||||
is theoretically possible to continue operating the server depending on which
|
||||
records were dropped, such as some historical records which are no longer
|
||||
essential. Nevertheless the impact of this operation is impossible to assess
|
||||
and a successful recovery should be used to salvage data prior to reinstall.
|
||||
|
||||
Once finished, restart the server without `rocksdb_repair`. If no errors
|
||||
persist, restart the server again without maintenance mode.
|
||||
|
||||
**5. Utilize an external repair tool.**
|
||||
|
||||
> [!WARNING]
|
||||
> Salvage mode potentially impacting the application's ability to function.
|
||||
> We cannot provide support for users who have entered this mode.
|
||||
|
||||
```
|
||||
git clone https://github.com/facebook/rocksdb
|
||||
cd rocksdb
|
||||
make -j$(nproc) ldb
|
||||
./ldb repair --db=/var/lib/tuwunel/ 2>./repair-log.txt
|
||||
```
|
||||
|
||||
For situations when the repair mode in step 4 failed or produced unexpected
|
||||
results.
|
||||
|
||||
## Debugging
|
||||
|
||||
@@ -147,7 +200,8 @@ #### Debug/Trace log level
|
||||
Tuwunel builds without debug or trace log levels at compile time by default
|
||||
for substantial performance gains in CPU usage and improved compile times. If
|
||||
you need to access debug/trace log levels, you will need to build without the
|
||||
`release_max_log_level` feature or use our provided static debug binaries.
|
||||
`release_max_log_level` feature or use our provided release-logging binaries
|
||||
and images.
|
||||
|
||||
#### Changing log level dynamically
|
||||
|
||||
|
||||
@@ -204,8 +204,6 @@
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
@@ -218,8 +216,6 @@
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
};
|
||||
@@ -231,8 +227,6 @@
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
};
|
||||
@@ -246,8 +240,6 @@
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
};
|
||||
@@ -260,12 +252,9 @@
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
};
|
||||
hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
|
||||
|
||||
oci-image = scopeHost.oci-image;
|
||||
oci-image-all-features = scopeHost.oci-image.override {
|
||||
@@ -278,8 +267,6 @@
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
@@ -294,18 +281,11 @@
|
||||
disable_features = [
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
};
|
||||
};
|
||||
oci-image-hmalloc = scopeHost.oci-image.override {
|
||||
main = scopeHost.main.override {
|
||||
features = ["hardened_malloc"];
|
||||
};
|
||||
};
|
||||
|
||||
book = scopeHost.book;
|
||||
|
||||
@@ -359,8 +339,6 @@
|
||||
disable_features = [
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
@@ -379,8 +357,6 @@
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
@@ -400,8 +376,6 @@
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
@@ -420,22 +394,12 @@
|
||||
disable_features = [
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
# An output for a statically-linked binary with hardened_malloc
|
||||
{
|
||||
name = "${binaryName}-hmalloc";
|
||||
value = scopeCrossStatic.main.override {
|
||||
features = ["hardened_malloc"];
|
||||
};
|
||||
}
|
||||
|
||||
# An output for an OCI image based on that binary
|
||||
{
|
||||
name = "oci-image-${crossSystem}";
|
||||
@@ -478,8 +442,6 @@
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
@@ -501,8 +463,6 @@
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
@@ -523,8 +483,6 @@
|
||||
disable_features = [
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
@@ -532,16 +490,6 @@
|
||||
};
|
||||
}
|
||||
|
||||
# An output for an OCI image based on that binary with hardened_malloc
|
||||
{
|
||||
name = "oci-image-${crossSystem}-hmalloc";
|
||||
value = scopeCrossStatic.oci-image.override {
|
||||
main = scopeCrossStatic.main.override {
|
||||
features = ["hardened_malloc"];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
# An output for a complement OCI image for the specified platform
|
||||
{
|
||||
name = "complement-${crossSystem}";
|
||||
@@ -571,8 +519,6 @@
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
];
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# tuwunel_mods is a development-only hot reload feature
|
||||
"tuwunel_mods"
|
||||
]
|
||||
|
||||
@@ -18,56 +18,46 @@ crate-type = [
|
||||
|
||||
[features]
|
||||
brotli_compression = [
|
||||
"tuwunel-api/brotli_compression",
|
||||
"tuwunel-core/brotli_compression",
|
||||
"tuwunel-service/brotli_compression",
|
||||
]
|
||||
bzip2_compression = [
|
||||
"tuwunel-api/bzip2_compression",
|
||||
"tuwunel-database/bzip2_compression",
|
||||
"tuwunel-service/bzip2_compression",
|
||||
]
|
||||
gzip_compression = [
|
||||
"tuwunel-api/gzip_compression",
|
||||
"tuwunel-core/gzip_compression",
|
||||
"tuwunel-service/gzip_compression",
|
||||
]
|
||||
io_uring = [
|
||||
"tuwunel-api/io_uring",
|
||||
"tuwunel-database/io_uring",
|
||||
"tuwunel-service/io_uring",
|
||||
]
|
||||
jemalloc = [
|
||||
"tuwunel-api/jemalloc",
|
||||
"tuwunel-core/jemalloc",
|
||||
"tuwunel-database/jemalloc",
|
||||
"tuwunel-service/jemalloc",
|
||||
]
|
||||
jemalloc_conf = [
|
||||
"tuwunel-api/jemalloc_conf",
|
||||
"tuwunel-core/jemalloc_conf",
|
||||
"tuwunel-database/jemalloc_conf",
|
||||
"tuwunel-service/jemalloc_conf",
|
||||
]
|
||||
jemalloc_prof = [
|
||||
"tuwunel-api/jemalloc_prof",
|
||||
"tuwunel-core/jemalloc_prof",
|
||||
"tuwunel-database/jemalloc_prof",
|
||||
"tuwunel-service/jemalloc_prof",
|
||||
]
|
||||
jemalloc_stats = [
|
||||
"tuwunel-api/jemalloc_stats",
|
||||
"tuwunel-core/jemalloc_stats",
|
||||
"tuwunel-database/jemalloc_stats",
|
||||
"tuwunel-service/jemalloc_stats",
|
||||
]
|
||||
lz4_compression = [
|
||||
"tuwunel-api/lz4_compression",
|
||||
"tuwunel-database/lz4_compression",
|
||||
"tuwunel-service/lz4_compression",
|
||||
]
|
||||
release_max_log_level = [
|
||||
"tuwunel-api/release_max_log_level",
|
||||
"tuwunel-core/release_max_log_level",
|
||||
"tuwunel-database/release_max_log_level",
|
||||
"tuwunel-service/release_max_log_level",
|
||||
@@ -77,7 +67,6 @@ release_max_log_level = [
|
||||
"log/release_max_level_info",
|
||||
]
|
||||
zstd_compression = [
|
||||
"tuwunel-api/zstd_compression",
|
||||
"tuwunel-core/zstd_compression",
|
||||
"tuwunel-database/zstd_compression",
|
||||
"tuwunel-service/zstd_compression",
|
||||
@@ -86,7 +75,6 @@ zstd_compression = [
|
||||
[dependencies]
|
||||
base64.workspace = true
|
||||
clap.workspace = true
|
||||
tuwunel-api.workspace = true
|
||||
tuwunel-core.workspace = true
|
||||
tuwunel-database.workspace = true
|
||||
tuwunel-macros.workspace = true
|
||||
|
||||
+32
-53
@@ -45,7 +45,6 @@ pub(super) async fn echo(&self, message: Vec<String>) -> Result {
|
||||
pub(super) async fn get_auth_chain(&self, event_id: OwnedEventId) -> Result {
|
||||
let Ok(event) = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json(&event_id)
|
||||
.await
|
||||
@@ -64,7 +63,6 @@ pub(super) async fn get_auth_chain(&self, event_id: OwnedEventId) -> Result {
|
||||
let start = Instant::now();
|
||||
let count = self
|
||||
.services
|
||||
.rooms
|
||||
.auth_chain
|
||||
.event_ids_iter(room_id, once(event_id.as_ref()))
|
||||
.ready_filter_map(Result::ok)
|
||||
@@ -87,9 +85,12 @@ pub(super) async fn parse_pdu(&self) -> Result {
|
||||
}
|
||||
|
||||
let string = self.body[1..self.body.len().saturating_sub(1)].join("\n");
|
||||
let rules = RoomVersionId::V6
|
||||
.rules()
|
||||
.expect("rules for V6 rooms");
|
||||
match serde_json::from_str(&string) {
|
||||
| Err(e) => return Err!("Invalid json in command body: {e}"),
|
||||
| Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
|
||||
| Ok(value) => match ruma::signatures::reference_hash(&value, &rules) {
|
||||
| Err(e) => return Err!("Could not parse PDU JSON: {e:?}"),
|
||||
| Ok(hash) => {
|
||||
let event_id = OwnedEventId::parse(format!("${hash}"));
|
||||
@@ -108,7 +109,6 @@ pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
|
||||
let mut outlier = false;
|
||||
let mut pdu_json = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_non_outlier_pdu_json(&event_id)
|
||||
.await;
|
||||
@@ -117,7 +117,6 @@ pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
|
||||
outlier = true;
|
||||
pdu_json = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json(&event_id)
|
||||
.await;
|
||||
@@ -152,7 +151,6 @@ pub(super) async fn get_short_pdu(
|
||||
|
||||
let pdu_json = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json_from_id(&pdu_id)
|
||||
.await;
|
||||
@@ -252,7 +250,6 @@ pub(super) async fn get_remote_pdu(
|
||||
.sending
|
||||
.send_federation_request(&server, ruma::api::federation::event::get_event::v1::Request {
|
||||
event_id: event_id.clone(),
|
||||
include_unredacted_content: None,
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -273,32 +270,28 @@ pub(super) async fn get_remote_pdu(
|
||||
})?;
|
||||
|
||||
trace!("Attempting to parse PDU: {:?}", &response.pdu);
|
||||
let _parsed_pdu = {
|
||||
let (room_id, ..) = {
|
||||
let parsed_result = self
|
||||
.services
|
||||
.rooms
|
||||
.event_handler
|
||||
.parse_incoming_pdu(&response.pdu)
|
||||
.boxed()
|
||||
.await;
|
||||
|
||||
let (event_id, value, room_id) = match parsed_result {
|
||||
match parsed_result {
|
||||
| Ok(t) => t,
|
||||
| Err(e) => {
|
||||
warn!("Failed to parse PDU: {e}");
|
||||
info!("Full PDU: {:?}", &response.pdu);
|
||||
return Err!("Failed to parse PDU remote server {server} sent us: {e}");
|
||||
},
|
||||
};
|
||||
|
||||
vec![(event_id, value, room_id)]
|
||||
}
|
||||
};
|
||||
|
||||
info!("Attempting to handle event ID {event_id} as backfilled PDU");
|
||||
self.services
|
||||
.rooms
|
||||
.timeline
|
||||
.backfill_pdu(&server, response.pdu)
|
||||
.backfill_pdu(&room_id, &server, response.pdu)
|
||||
.await?;
|
||||
|
||||
let text = serde_json::to_string_pretty(&json)?;
|
||||
@@ -311,10 +304,9 @@ pub(super) async fn get_remote_pdu(
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
let room_id = self.services.rooms.alias.resolve(&room).await?;
|
||||
let room_id = self.services.alias.resolve(&room).await?;
|
||||
let room_state: Vec<Raw<AnyStateEvent>> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_full_pdus(&room_id)
|
||||
.map_ok(Event::into_format)
|
||||
@@ -492,7 +484,6 @@ pub(super) async fn verify_pdu(&self, event_id: OwnedEventId) -> Result {
|
||||
|
||||
let mut event = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json(&event_id)
|
||||
.await?;
|
||||
@@ -517,7 +508,6 @@ pub(super) async fn verify_pdu(&self, event_id: OwnedEventId) -> Result {
|
||||
pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&self.services.server.name, &room_id)
|
||||
.await
|
||||
@@ -527,7 +517,6 @@ pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
|
||||
let first_pdu = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.first_pdu_in_room(&room_id)
|
||||
.await
|
||||
@@ -542,7 +531,6 @@ pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&self.services.server.name, &room_id)
|
||||
.await
|
||||
@@ -552,7 +540,6 @@ pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
|
||||
let latest_pdu = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.latest_pdu_in_room(&room_id)
|
||||
.await
|
||||
@@ -571,7 +558,6 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
) -> Result {
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&self.services.server.name, &room_id)
|
||||
.await
|
||||
@@ -581,7 +567,6 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
|
||||
let first_pdu = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.latest_pdu_in_room(&room_id)
|
||||
.await
|
||||
@@ -589,7 +574,6 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
|
||||
let room_version = self
|
||||
.services
|
||||
.rooms
|
||||
.state
|
||||
.get_room_version(&room_id)
|
||||
.await?;
|
||||
@@ -608,7 +592,6 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
for pdu in remote_state_response.pdus.clone() {
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.event_handler
|
||||
.parse_incoming_pdu(&pdu)
|
||||
.await
|
||||
@@ -627,24 +610,33 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
.server_keys
|
||||
.validate_and_add_event_id(pdu, &room_version)
|
||||
}) {
|
||||
let Ok((event_id, value)) = result.await else {
|
||||
let Ok((event_id, mut value)) = result.await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
|
||||
let invalid_pdu_err = |e| {
|
||||
debug_error!("Invalid PDU in fetching remote room state PDUs response: {value:#?}");
|
||||
err!(BadServerResponse(debug_error!("Invalid PDU in send_join response: {e:?}")))
|
||||
})?;
|
||||
};
|
||||
|
||||
let pdu = if value["type"] == "m.room.create" {
|
||||
PduEvent::from_rid_val(&room_id, &event_id, value.clone()).map_err(invalid_pdu_err)?
|
||||
} else {
|
||||
PduEvent::from_id_val(&event_id, value.clone()).map_err(invalid_pdu_err)?
|
||||
};
|
||||
|
||||
if !value.contains_key("room_id") {
|
||||
let room_id = CanonicalJsonValue::String(room_id.as_str().into());
|
||||
value.insert("room_id".into(), room_id);
|
||||
}
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.timeline
|
||||
.add_pdu_outlier(&event_id, &value);
|
||||
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
let shortstatekey = self
|
||||
.services
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)
|
||||
.await;
|
||||
@@ -667,14 +659,12 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
};
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.timeline
|
||||
.add_pdu_outlier(&event_id, &value);
|
||||
}
|
||||
|
||||
let new_room_state = self
|
||||
.services
|
||||
.rooms
|
||||
.event_handler
|
||||
.resolve_state(&room_id, &room_version, state)
|
||||
.await?;
|
||||
@@ -686,21 +676,13 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
removed,
|
||||
} = self
|
||||
.services
|
||||
.rooms
|
||||
.state_compressor
|
||||
.save_state(room_id.clone().as_ref(), new_room_state)
|
||||
.await?;
|
||||
|
||||
let state_lock = self
|
||||
.services
|
||||
.rooms
|
||||
.state
|
||||
.mutex
|
||||
.lock(&*room_id)
|
||||
.await;
|
||||
let state_lock = self.services.state.mutex.lock(&*room_id).await;
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.state
|
||||
.force_state(room_id.clone().as_ref(), short_state_hash, added, removed, &state_lock)
|
||||
.await?;
|
||||
@@ -710,7 +692,6 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
the room's m.room.member state"
|
||||
);
|
||||
self.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.update_joined_count(&room_id)
|
||||
.await;
|
||||
@@ -993,10 +974,10 @@ pub(super) async fn create_jwt(
|
||||
#[derive(Serialize)]
|
||||
struct Claim {
|
||||
sub: String,
|
||||
iss: String,
|
||||
aud: String,
|
||||
exp: usize,
|
||||
nbf: usize,
|
||||
iss: Option<String>,
|
||||
aud: Option<String>,
|
||||
exp: Option<usize>,
|
||||
nbf: Option<usize>,
|
||||
}
|
||||
|
||||
let config = &self.services.config.jwt;
|
||||
@@ -1013,21 +994,19 @@ struct Claim {
|
||||
let claim = Claim {
|
||||
sub: user,
|
||||
|
||||
iss: issuer.unwrap_or_default(),
|
||||
iss: issuer,
|
||||
|
||||
aud: audience.unwrap_or_default(),
|
||||
aud: audience,
|
||||
|
||||
exp: exp_from_now
|
||||
.and_then(|val| now_secs().checked_add(val))
|
||||
.map(TryInto::try_into)
|
||||
.and_then(Result::ok)
|
||||
.unwrap_or(usize::MAX),
|
||||
.and_then(Result::ok),
|
||||
|
||||
nbf: nbf_from_now
|
||||
.and_then(|val| now_secs().checked_add(val))
|
||||
.map(TryInto::try_into)
|
||||
.and_then(Result::ok)
|
||||
.unwrap_or(0),
|
||||
.and_then(Result::ok),
|
||||
};
|
||||
|
||||
encode(&header, &claim, &key)
|
||||
|
||||
+2
-11
@@ -72,15 +72,6 @@ pub(super) enum DebugCommand {
|
||||
},
|
||||
|
||||
/// - Gets all the room state events for the specified room.
|
||||
///
|
||||
/// This is functionally equivalent to `GET
|
||||
/// /_matrix/client/v3/rooms/{roomid}/state`, except the admin command does
|
||||
/// *not* check if the sender user is allowed to see state events. This is
|
||||
/// done because it's implied that server admins here have database access
|
||||
/// and can see/get room info themselves anyways if they were malicious
|
||||
/// admins.
|
||||
///
|
||||
/// Of course the check is still done on the actual client API.
|
||||
GetRoomState {
|
||||
/// Room ID
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
@@ -125,13 +116,13 @@ pub(super) enum DebugCommand {
|
||||
reset: bool,
|
||||
},
|
||||
|
||||
/// - Verify json signatures
|
||||
/// - Sign JSON blob
|
||||
///
|
||||
/// This command needs a JSON blob provided in a Markdown code block below
|
||||
/// the command.
|
||||
SignJson,
|
||||
|
||||
/// - Verify json signatures
|
||||
/// - Verify JSON signatures
|
||||
///
|
||||
/// This command needs a JSON blob provided in a Markdown code block below
|
||||
/// the command.
|
||||
|
||||
@@ -6,19 +6,13 @@
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn disable_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
self.services
|
||||
.rooms
|
||||
.metadata
|
||||
.disable_room(&room_id, true);
|
||||
self.services.metadata.disable_room(&room_id);
|
||||
self.write_str("Room disabled.").await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn enable_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
self.services
|
||||
.rooms
|
||||
.metadata
|
||||
.disable_room(&room_id, false);
|
||||
self.services.metadata.enable_room(&room_id);
|
||||
self.write_str("Room enabled.").await
|
||||
}
|
||||
|
||||
@@ -82,7 +76,6 @@ pub(super) async fn remote_user_in_rooms(&self, user_id: OwnedUserId) -> Result
|
||||
|
||||
let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&user_id)
|
||||
.then(|room_id| get_room_info(self.services, room_id))
|
||||
|
||||
@@ -37,7 +37,6 @@ pub(super) async fn delete(
|
||||
// parsing the PDU for any MXC URLs begins here
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json(&event_id)
|
||||
.await
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![recursion_limit = "192"]
|
||||
#![allow(clippy::wildcard_imports)]
|
||||
#![allow(clippy::enum_glob_use)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
@@ -30,11 +30,7 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Context<'_>)
|
||||
match subcommand {
|
||||
| RoomAliasCommand::ResolveLocalAlias { alias } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&alias)
|
||||
.await;
|
||||
let results = services.alias.resolve_local_alias(&alias).await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
@@ -42,7 +38,6 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Context<'_>)
|
||||
| RoomAliasCommand::LocalAliasesForRoom { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let aliases: Vec<_> = services
|
||||
.rooms
|
||||
.alias
|
||||
.local_aliases_for_room(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -55,7 +50,6 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Context<'_>)
|
||||
| RoomAliasCommand::AllLocalAliases => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let aliases = services
|
||||
.rooms
|
||||
.alias
|
||||
.all_local_aliases()
|
||||
.map(|(room_id, alias)| (room_id.to_owned(), alias.to_owned()))
|
||||
|
||||
@@ -83,7 +83,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::ServerInRoom { server, room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&server, &room_id)
|
||||
.await;
|
||||
@@ -98,7 +97,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::RoomServers { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_servers(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -115,7 +113,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::ServerRooms { server } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_rooms(&server)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -132,7 +129,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::RoomMembers { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -149,7 +145,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::LocalUsersInRoom { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.local_users_in_room(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -166,7 +161,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::ActiveLocalUsersInRoom { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.active_local_users_in_room(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -183,7 +177,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::RoomJoinedCount { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_joined_count(&room_id)
|
||||
.await;
|
||||
@@ -198,7 +191,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::RoomInvitedCount { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_invited_count(&room_id)
|
||||
.await;
|
||||
@@ -213,7 +205,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::RoomUserOnceJoined { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_useroncejoined(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -230,7 +221,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::RoomMembersInvited { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members_invited(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -247,7 +237,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::GetInviteCount { room_id, user_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_invite_count(&room_id, &user_id)
|
||||
.await;
|
||||
@@ -262,7 +251,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::GetLeftCount { room_id, user_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_left_count(&room_id, &user_id)
|
||||
.await;
|
||||
@@ -277,7 +265,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::RoomsJoined { user_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&user_id)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -294,7 +281,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::RoomsInvited { user_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_invited(&user_id)
|
||||
.collect()
|
||||
@@ -310,7 +296,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::RoomsLeft { user_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_left(&user_id)
|
||||
.collect()
|
||||
@@ -326,7 +311,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
| RoomStateCacheCommand::InviteState { user_id, room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.invite_state(&user_id, &room_id)
|
||||
.await;
|
||||
|
||||
@@ -25,18 +25,12 @@ pub(crate) enum RoomTimelineCommand {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn last(&self, room_id: OwnedRoomOrAliasId) -> Result {
|
||||
let room_id = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve(&room_id)
|
||||
.await?;
|
||||
let room_id = self.services.alias.resolve(&room_id).await?;
|
||||
|
||||
let result = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.last_timeline_count(None, &room_id)
|
||||
.last_timeline_count(None, &room_id, None)
|
||||
.await?;
|
||||
|
||||
self.write_str(&format!("{result:#?}")).await
|
||||
@@ -49,18 +43,12 @@ pub(super) async fn pdus(
|
||||
from: Option<String>,
|
||||
limit: Option<usize>,
|
||||
) -> Result {
|
||||
let room_id = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve(&room_id)
|
||||
.await?;
|
||||
let room_id = self.services.alias.resolve(&room_id).await?;
|
||||
|
||||
let from: Option<PduCount> = from.as_deref().map(str::parse).transpose()?;
|
||||
|
||||
let result: Vec<_> = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(None, &room_id, from)
|
||||
.try_take(limit.unwrap_or(3))
|
||||
|
||||
@@ -21,7 +21,6 @@ pub(crate) enum ShortCommand {
|
||||
pub(super) async fn short_event_id(&self, event_id: OwnedEventId) -> Result {
|
||||
let shortid = self
|
||||
.services
|
||||
.rooms
|
||||
.short
|
||||
.get_shorteventid(&event_id)
|
||||
.await?;
|
||||
@@ -31,16 +30,10 @@ pub(super) async fn short_event_id(&self, event_id: OwnedEventId) -> Result {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn short_room_id(&self, room_id: OwnedRoomOrAliasId) -> Result {
|
||||
let room_id = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve(&room_id)
|
||||
.await?;
|
||||
let room_id = self.services.alias.resolve(&room_id).await?;
|
||||
|
||||
let shortid = self
|
||||
.services
|
||||
.rooms
|
||||
.short
|
||||
.get_shortroomid(&room_id)
|
||||
.await?;
|
||||
|
||||
@@ -134,7 +134,6 @@ async fn get_shared_rooms(&self, user_a: OwnedUserId, user_b: OwnedUserId) -> Re
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result: Vec<_> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_shared_rooms(&user_a, &user_b)
|
||||
.map(ToOwned::to_owned)
|
||||
|
||||
+8
-16
@@ -63,17 +63,15 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
|
||||
match (
|
||||
force,
|
||||
services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&room_alias)
|
||||
.await,
|
||||
) {
|
||||
| (true, Ok(id)) => {
|
||||
match services.rooms.alias.set_alias(
|
||||
&room_alias,
|
||||
&room_id,
|
||||
server_user,
|
||||
) {
|
||||
match services
|
||||
.alias
|
||||
.set_alias(&room_alias, &room_id, server_user)
|
||||
{
|
||||
| Err(err) => Err!("Failed to remove alias: {err}"),
|
||||
| Ok(()) =>
|
||||
context
|
||||
@@ -88,11 +86,10 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
|
||||
overwrite"
|
||||
),
|
||||
| (_, Err(_)) => {
|
||||
match services.rooms.alias.set_alias(
|
||||
&room_alias,
|
||||
&room_id,
|
||||
server_user,
|
||||
) {
|
||||
match services
|
||||
.alias
|
||||
.set_alias(&room_alias, &room_id, server_user)
|
||||
{
|
||||
| Err(err) => Err!("Failed to remove alias: {err}"),
|
||||
| Ok(()) => context.write_str("Successfully set alias").await,
|
||||
}
|
||||
@@ -101,14 +98,12 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
|
||||
},
|
||||
| RoomAliasCommand::Remove { .. } => {
|
||||
match services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&room_alias)
|
||||
.await
|
||||
{
|
||||
| Err(_) => Err!("Alias isn't in use."),
|
||||
| Ok(id) => match services
|
||||
.rooms
|
||||
.alias
|
||||
.remove_alias(&room_alias, server_user)
|
||||
.await
|
||||
@@ -123,7 +118,6 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
|
||||
},
|
||||
| RoomAliasCommand::Which { .. } => {
|
||||
match services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&room_alias)
|
||||
.await
|
||||
@@ -141,7 +135,6 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
|
||||
| RoomAliasCommand::List { room_id } =>
|
||||
if let Some(room_id) = room_id {
|
||||
let aliases: Vec<OwnedRoomAliasId> = services
|
||||
.rooms
|
||||
.alias
|
||||
.local_aliases_for_room(&room_id)
|
||||
.map(Into::into)
|
||||
@@ -160,7 +153,6 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
|
||||
context.write_str(&plain).await
|
||||
} else {
|
||||
let aliases = services
|
||||
.rooms
|
||||
.alias
|
||||
.all_local_aliases()
|
||||
.map(|(room_id, localpart)| (room_id.into(), localpart.into()))
|
||||
|
||||
+22
-21
@@ -16,27 +16,14 @@ pub(super) async fn list_rooms(
|
||||
let page = page.unwrap_or(1);
|
||||
let mut rooms = self
|
||||
.services
|
||||
.rooms
|
||||
.metadata
|
||||
.iter_ids()
|
||||
.filter_map(async |room_id| {
|
||||
(!exclude_disabled
|
||||
|| !self
|
||||
.services
|
||||
.rooms
|
||||
.metadata
|
||||
.is_disabled(room_id)
|
||||
.await)
|
||||
(!exclude_disabled || !self.services.metadata.is_disabled(room_id).await)
|
||||
.then_some(room_id)
|
||||
})
|
||||
.filter_map(async |room_id| {
|
||||
(!exclude_banned
|
||||
|| !self
|
||||
.services
|
||||
.rooms
|
||||
.metadata
|
||||
.is_banned(room_id)
|
||||
.await)
|
||||
(!exclude_banned || !self.services.metadata.is_banned(room_id).await)
|
||||
.then_some(room_id)
|
||||
})
|
||||
.then(|room_id| get_room_info(self.services, room_id))
|
||||
@@ -74,12 +61,26 @@ pub(super) async fn list_rooms(
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn exists(&self, room_id: OwnedRoomId) -> Result {
|
||||
let result = self
|
||||
.services
|
||||
.rooms
|
||||
.metadata
|
||||
.exists(&room_id)
|
||||
.await;
|
||||
let result = self.services.metadata.exists(&room_id).await;
|
||||
|
||||
self.write_str(&format!("{result}")).await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_room(&self, room_id: OwnedRoomId, force: bool) -> Result {
|
||||
if self.services.admin.is_admin_room(&room_id).await {
|
||||
return Err!("Cannot delete admin room");
|
||||
}
|
||||
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
|
||||
self.services
|
||||
.delete
|
||||
.delete_room(&room_id, force, state_lock)
|
||||
.await?;
|
||||
|
||||
self.write_str("Successfully deleted the room from our database.")
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -29,18 +29,17 @@ pub(super) async fn process(command: RoomDirectoryCommand, context: &Context<'_>
|
||||
let services = context.services;
|
||||
match command {
|
||||
| RoomDirectoryCommand::Publish { room_id } => {
|
||||
services.rooms.directory.set_public(&room_id);
|
||||
services.directory.set_public(&room_id);
|
||||
context.write_str("Room published").await
|
||||
},
|
||||
| RoomDirectoryCommand::Unpublish { room_id } => {
|
||||
services.rooms.directory.set_not_public(&room_id);
|
||||
services.directory.set_not_public(&room_id);
|
||||
context.write_str("Room unpublished").await
|
||||
},
|
||||
| RoomDirectoryCommand::List { page } => {
|
||||
// TODO: i know there's a way to do this with clap, but i can't seem to find it
|
||||
let page = page.unwrap_or(1);
|
||||
let mut rooms: Vec<_> = services
|
||||
.rooms
|
||||
.directory
|
||||
.public_rooms()
|
||||
.then(|room_id| get_room_info(services, room_id))
|
||||
|
||||
@@ -30,7 +30,6 @@ pub(crate) enum RoomInfoCommand {
|
||||
async fn list_joined_members(&self, room_id: OwnedRoomId, local_only: bool) -> Result {
|
||||
let room_name = self
|
||||
.services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_name(&room_id)
|
||||
.await
|
||||
@@ -38,7 +37,6 @@ async fn list_joined_members(&self, room_id: OwnedRoomId, local_only: bool) -> R
|
||||
|
||||
let member_info: Vec<_> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.ready_filter(|user_id| {
|
||||
@@ -75,7 +73,6 @@ async fn list_joined_members(&self, room_id: OwnedRoomId, local_only: bool) -> R
|
||||
async fn view_room_topic(&self, room_id: OwnedRoomId) -> Result {
|
||||
let Ok(room_topic) = self
|
||||
.services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_room_topic(&room_id)
|
||||
.await
|
||||
|
||||
@@ -56,4 +56,12 @@ pub(super) enum RoomCommand {
|
||||
Exists {
|
||||
room_id: OwnedRoomId,
|
||||
},
|
||||
|
||||
/// - Delete room
|
||||
DeleteRoom {
|
||||
room_id: OwnedRoomId,
|
||||
|
||||
#[arg(short, long)]
|
||||
force: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use clap::Subcommand;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{OwnedRoomId, OwnedRoomOrAliasId, RoomAliasId, RoomId, RoomOrAliasId};
|
||||
use tuwunel_api::client::leave_room;
|
||||
use tuwunel_core::{
|
||||
Err, Result, debug,
|
||||
utils::{IterStream, ReadyExt},
|
||||
@@ -70,10 +69,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
};
|
||||
|
||||
debug!("Room specified is a room ID, banning room ID");
|
||||
self.services
|
||||
.rooms
|
||||
.metadata
|
||||
.ban_room(room_id, true);
|
||||
self.services.metadata.ban_room(room_id);
|
||||
|
||||
room_id.to_owned()
|
||||
} else if room.is_room_alias_id() {
|
||||
@@ -95,7 +91,6 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
|
||||
let room_id = match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(room_alias)
|
||||
.await
|
||||
@@ -109,7 +104,6 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(room_alias, None)
|
||||
.await
|
||||
@@ -131,10 +125,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
},
|
||||
};
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.metadata
|
||||
.ban_room(&room_id, true);
|
||||
self.services.metadata.ban_room(&room_id);
|
||||
|
||||
room_id
|
||||
} else {
|
||||
@@ -148,7 +139,6 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
debug!("Making all users leave the room {room_id} and forgetting it");
|
||||
let mut users = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -161,27 +151,31 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
evicting admins too)",
|
||||
);
|
||||
|
||||
if let Err(e) = leave_room(self.services, user_id, &room_id, None)
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
|
||||
if let Err(e) = self
|
||||
.services
|
||||
.membership
|
||||
.leave(user_id, &room_id, None, false, &state_lock)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
warn!("Failed to leave room: {e}");
|
||||
}
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.forget(&room_id, user_id);
|
||||
}
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.alias
|
||||
.local_aliases_for_room(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.for_each(async |local_alias| {
|
||||
self.services
|
||||
.rooms
|
||||
.alias
|
||||
.remove_alias(&local_alias, &self.services.globals.server_user)
|
||||
.await
|
||||
@@ -190,15 +184,9 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
.await;
|
||||
|
||||
// unpublish from room directory
|
||||
self.services
|
||||
.rooms
|
||||
.directory
|
||||
.set_not_public(&room_id);
|
||||
self.services.directory.set_not_public(&room_id);
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.metadata
|
||||
.disable_room(&room_id, true);
|
||||
self.services.metadata.disable_room(&room_id);
|
||||
|
||||
self.write_str(
|
||||
"Room banned, removed all our local users, and disabled incoming federation with room.",
|
||||
@@ -258,7 +246,6 @@ async fn ban_list_of_rooms(&self) -> Result {
|
||||
| Ok(room_alias) => {
|
||||
let room_id = match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(room_alias)
|
||||
.await
|
||||
@@ -272,7 +259,6 @@ async fn ban_list_of_rooms(&self) -> Result {
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(room_alias, None)
|
||||
.await
|
||||
@@ -320,10 +306,7 @@ async fn ban_list_of_rooms(&self) -> Result {
|
||||
}
|
||||
|
||||
for room_id in room_ids {
|
||||
self.services
|
||||
.rooms
|
||||
.metadata
|
||||
.ban_room(&room_id, true);
|
||||
self.services.metadata.ban_room(&room_id);
|
||||
|
||||
debug!("Banned {room_id} successfully");
|
||||
room_ban_count = room_ban_count.saturating_add(1);
|
||||
@@ -331,7 +314,6 @@ async fn ban_list_of_rooms(&self) -> Result {
|
||||
debug!("Making all users leave the room {room_id} and forgetting it");
|
||||
let mut users = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -344,28 +326,32 @@ async fn ban_list_of_rooms(&self) -> Result {
|
||||
evicting admins too)",
|
||||
);
|
||||
|
||||
if let Err(e) = leave_room(self.services, user_id, &room_id, None)
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
|
||||
if let Err(e) = self
|
||||
.services
|
||||
.membership
|
||||
.leave(user_id, &room_id, None, false, &state_lock)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
warn!("Failed to leave room: {e}");
|
||||
}
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.forget(&room_id, user_id);
|
||||
}
|
||||
|
||||
// remove any local aliases, ignore errors
|
||||
self.services
|
||||
.rooms
|
||||
.alias
|
||||
.local_aliases_for_room(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.for_each(async |local_alias| {
|
||||
self.services
|
||||
.rooms
|
||||
.alias
|
||||
.remove_alias(&local_alias, &self.services.globals.server_user)
|
||||
.await
|
||||
@@ -374,15 +360,9 @@ async fn ban_list_of_rooms(&self) -> Result {
|
||||
.await;
|
||||
|
||||
// unpublish from room directory, ignore errors
|
||||
self.services
|
||||
.rooms
|
||||
.directory
|
||||
.set_not_public(&room_id);
|
||||
self.services.directory.set_not_public(&room_id);
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.metadata
|
||||
.disable_room(&room_id, true);
|
||||
self.services.metadata.disable_room(&room_id);
|
||||
}
|
||||
|
||||
self.write_str(&format!(
|
||||
@@ -407,10 +387,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
};
|
||||
|
||||
debug!("Room specified is a room ID, unbanning room ID");
|
||||
self.services
|
||||
.rooms
|
||||
.metadata
|
||||
.ban_room(room_id, false);
|
||||
self.services.metadata.unban_room(room_id);
|
||||
|
||||
room_id.to_owned()
|
||||
} else if room.is_room_alias_id() {
|
||||
@@ -432,7 +409,6 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
|
||||
let room_id = match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(room_alias)
|
||||
.await
|
||||
@@ -446,7 +422,6 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(room_alias, None)
|
||||
.await
|
||||
@@ -466,10 +441,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
},
|
||||
};
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.metadata
|
||||
.ban_room(&room_id, false);
|
||||
self.services.metadata.unban_room(&room_id);
|
||||
|
||||
room_id
|
||||
} else {
|
||||
@@ -480,10 +452,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
);
|
||||
};
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.metadata
|
||||
.disable_room(&room_id, false);
|
||||
self.services.metadata.enable_room(&room_id);
|
||||
self.write_str("Room unbanned and federation re-enabled.")
|
||||
.await
|
||||
}
|
||||
@@ -492,7 +461,6 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
async fn list_banned_rooms(&self, no_details: bool) -> Result {
|
||||
let room_ids: Vec<OwnedRoomId> = self
|
||||
.services
|
||||
.rooms
|
||||
.metadata
|
||||
.list_banned_rooms()
|
||||
.map(Into::into)
|
||||
|
||||
+194
-152
@@ -2,30 +2,27 @@
|
||||
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{
|
||||
OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId,
|
||||
Int, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId,
|
||||
events::{
|
||||
RoomAccountDataEventType, StateEventType,
|
||||
room::{
|
||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent, UserPowerLevel},
|
||||
redaction::RoomRedactionEventContent,
|
||||
},
|
||||
tag::{TagEvent, TagEventContent, TagInfo},
|
||||
},
|
||||
};
|
||||
use tuwunel_api::client::{
|
||||
full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, update_avatar_url,
|
||||
update_displayname,
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Result, debug, debug_warn, error, info, is_equal_to,
|
||||
matrix::{Event, pdu::PduBuilder},
|
||||
utils::{self, ReadyExt},
|
||||
warn,
|
||||
};
|
||||
use tuwunel_service::Services;
|
||||
|
||||
use crate::{
|
||||
admin_command, get_room_info,
|
||||
utils::{parse_active_local_user_id, parse_local_user_id},
|
||||
utils::{parse_active_local_user_id, parse_local_user_id, parse_user_id},
|
||||
};
|
||||
|
||||
const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
|
||||
@@ -122,7 +119,7 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
.is_empty()
|
||||
{
|
||||
for room in &self.services.server.config.auto_join_rooms {
|
||||
let Ok(room_id) = self.services.rooms.alias.resolve(room).await else {
|
||||
let Ok(room_id) = self.services.alias.resolve(room).await else {
|
||||
error!(
|
||||
%user_id,
|
||||
"Failed to resolve room alias to room ID when attempting to auto join {room}, skipping"
|
||||
@@ -132,7 +129,6 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(self.services.globals.server_name(), &room_id)
|
||||
.await
|
||||
@@ -143,20 +139,24 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
continue;
|
||||
}
|
||||
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
|
||||
if let Some(room_server_name) = room.server_name() {
|
||||
match join_room_by_id_helper(
|
||||
self.services,
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[
|
||||
self.services.globals.server_name().to_owned(),
|
||||
room_server_name.to_owned(),
|
||||
],
|
||||
None,
|
||||
&None,
|
||||
)
|
||||
.await
|
||||
match self
|
||||
.services
|
||||
.membership
|
||||
.join(
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[
|
||||
self.services.globals.server_name().to_owned(),
|
||||
room_server_name.to_owned(),
|
||||
],
|
||||
&None,
|
||||
&state_lock,
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Ok(_response) => {
|
||||
info!("Automatically joined room {room} for user {user_id}");
|
||||
@@ -175,6 +175,8 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
.await;
|
||||
},
|
||||
}
|
||||
|
||||
drop(state_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,7 +188,6 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
if let Ok(admin_room) = self.services.admin.get_admin_room().await {
|
||||
if self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_joined_count(&admin_room)
|
||||
.await
|
||||
@@ -195,6 +196,7 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
self.services
|
||||
.admin
|
||||
.make_user_admin(&user_id)
|
||||
.boxed()
|
||||
.await?;
|
||||
warn!("Granting {user_id} admin privileges as the first user");
|
||||
}
|
||||
@@ -216,34 +218,7 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
|
||||
return Err!("Not allowed to deactivate the server service account.",);
|
||||
}
|
||||
|
||||
self.services
|
||||
.users
|
||||
.deactivate_account(&user_id)
|
||||
.await?;
|
||||
|
||||
if !no_leave_rooms {
|
||||
self.services
|
||||
.admin
|
||||
.send_text(&format!("Making {user_id} leave all rooms after deactivation..."))
|
||||
.await;
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&user_id)
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
|
||||
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms).await;
|
||||
leave_all_rooms(self.services, &user_id).await;
|
||||
}
|
||||
deactivate_user(self.services, &user_id, no_leave_rooms).await?;
|
||||
|
||||
self.write_str(&format!("User {user_id} has been deactivated"))
|
||||
.await
|
||||
@@ -336,41 +311,16 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
|
||||
let mut deactivation_count: usize = 0;
|
||||
|
||||
for user_id in user_ids {
|
||||
match self
|
||||
.services
|
||||
.users
|
||||
.deactivate_account(&user_id)
|
||||
.await
|
||||
{
|
||||
match deactivate_user(self.services, &user_id, no_leave_rooms).await {
|
||||
| Ok(()) => {
|
||||
deactivation_count = deactivation_count.saturating_add(1);
|
||||
},
|
||||
| Err(e) => {
|
||||
self.services
|
||||
.admin
|
||||
.send_text(&format!("Failed deactivating user: {e}"))
|
||||
.await;
|
||||
},
|
||||
| Ok(()) => {
|
||||
deactivation_count = deactivation_count.saturating_add(1);
|
||||
if !no_leave_rooms {
|
||||
info!("Forcing user {user_id} to leave all rooms apart of deactivate-all");
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&user_id)
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
|
||||
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms)
|
||||
.await;
|
||||
leave_all_rooms(self.services, &user_id).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,6 +337,20 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
|
||||
.await
|
||||
}
|
||||
|
||||
async fn deactivate_user(services: &Services, user_id: &UserId, no_leave_rooms: bool) -> Result {
|
||||
if !no_leave_rooms {
|
||||
services
|
||||
.deactivate
|
||||
.full_deactivate(user_id)
|
||||
.boxed()
|
||||
.await?;
|
||||
} else {
|
||||
services.users.deactivate_account(user_id).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result {
|
||||
// Validate user id
|
||||
@@ -394,7 +358,6 @@ pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result {
|
||||
|
||||
let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&user_id)
|
||||
.then(|room_id| get_room_info(self.services, room_id))
|
||||
@@ -444,14 +407,12 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
|
||||
let (room_id, servers) = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_with_servers(&room_id, None)
|
||||
.await?;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(self.services.globals.server_name(), &room_id)
|
||||
.await
|
||||
@@ -461,7 +422,6 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
|
||||
let server_admins: Vec<_> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.active_local_users_in_room(&admin_room)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -470,7 +430,6 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.ready_any(|user_id| server_admins.contains(&user_id.to_owned()))
|
||||
@@ -518,17 +477,21 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
let mut failed_joins: usize = 0;
|
||||
let mut successful_joins: usize = 0;
|
||||
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
|
||||
for user_id in user_ids {
|
||||
match join_room_by_id_helper(
|
||||
self.services,
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some(String::from(BULK_JOIN_REASON)),
|
||||
&servers,
|
||||
None,
|
||||
&None,
|
||||
)
|
||||
.await
|
||||
match self
|
||||
.services
|
||||
.membership
|
||||
.join(
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some(String::from(BULK_JOIN_REASON)),
|
||||
&servers,
|
||||
&None,
|
||||
&state_lock,
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Ok(_res) => {
|
||||
successful_joins = successful_joins.saturating_add(1);
|
||||
@@ -540,6 +503,8 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
}
|
||||
}
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
self.write_str(&format!(
|
||||
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins \
|
||||
failed.",
|
||||
@@ -566,14 +531,12 @@ pub(super) async fn force_join_all_local_users(
|
||||
|
||||
let (room_id, servers) = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_with_servers(&room_id, None)
|
||||
.await?;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(self.services.globals.server_name(), &room_id)
|
||||
.await
|
||||
@@ -583,7 +546,6 @@ pub(super) async fn force_join_all_local_users(
|
||||
|
||||
let server_admins: Vec<_> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.active_local_users_in_room(&admin_room)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -592,7 +554,6 @@ pub(super) async fn force_join_all_local_users(
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.ready_any(|user_id| server_admins.contains(&user_id.to_owned()))
|
||||
@@ -604,6 +565,8 @@ pub(super) async fn force_join_all_local_users(
|
||||
let mut failed_joins: usize = 0;
|
||||
let mut successful_joins: usize = 0;
|
||||
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
|
||||
for user_id in &self
|
||||
.services
|
||||
.users
|
||||
@@ -612,16 +575,18 @@ pub(super) async fn force_join_all_local_users(
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
{
|
||||
match join_room_by_id_helper(
|
||||
self.services,
|
||||
user_id,
|
||||
&room_id,
|
||||
Some(String::from(BULK_JOIN_REASON)),
|
||||
&servers,
|
||||
None,
|
||||
&None,
|
||||
)
|
||||
.await
|
||||
match self
|
||||
.services
|
||||
.membership
|
||||
.join(
|
||||
user_id,
|
||||
&room_id,
|
||||
Some(String::from(BULK_JOIN_REASON)),
|
||||
&servers,
|
||||
&None,
|
||||
&state_lock,
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Ok(_res) => {
|
||||
successful_joins = successful_joins.saturating_add(1);
|
||||
@@ -633,6 +598,8 @@ pub(super) async fn force_join_all_local_users(
|
||||
}
|
||||
}
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
self.write_str(&format!(
|
||||
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins \
|
||||
failed.",
|
||||
@@ -649,7 +616,6 @@ pub(super) async fn force_join_room(
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let (room_id, servers) = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_with_servers(&room_id, None)
|
||||
.await?;
|
||||
@@ -658,9 +624,16 @@ pub(super) async fn force_join_room(
|
||||
self.services.globals.user_is_local(&user_id),
|
||||
"Parsed user_id must be a local user"
|
||||
);
|
||||
join_room_by_id_helper(self.services, &user_id, &room_id, None, &servers, None, &None)
|
||||
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
|
||||
self.services
|
||||
.membership
|
||||
.join(&user_id, &room_id, None, &servers, &None, &state_lock)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
self.write_str(&format!("{user_id} has been joined to {room_id}.",))
|
||||
.await
|
||||
}
|
||||
@@ -672,12 +645,7 @@ pub(super) async fn force_leave_room(
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
) -> Result {
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let room_id = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve(&room_id)
|
||||
.await?;
|
||||
let room_id = self.services.alias.resolve(&room_id).await?;
|
||||
|
||||
assert!(
|
||||
self.services.globals.user_is_local(&user_id),
|
||||
@@ -686,7 +654,6 @@ pub(super) async fn force_leave_room(
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(&user_id, &room_id)
|
||||
.await
|
||||
@@ -694,10 +661,16 @@ pub(super) async fn force_leave_room(
|
||||
return Err!("{user_id} is not joined in the room");
|
||||
}
|
||||
|
||||
leave_room(self.services, &user_id, &room_id, None)
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
|
||||
self.services
|
||||
.membership
|
||||
.leave(&user_id, &room_id, None, false, &state_lock)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
self.write_str(&format!("{user_id} has left {room_id}.",))
|
||||
.await
|
||||
}
|
||||
@@ -705,57 +678,49 @@ pub(super) async fn force_leave_room(
|
||||
#[admin_command]
|
||||
pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAliasId) -> Result {
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let room_id = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve(&room_id)
|
||||
.await?;
|
||||
let room_id = self.services.alias.resolve(&room_id).await?;
|
||||
|
||||
assert!(
|
||||
self.services.globals.user_is_local(&user_id),
|
||||
"Parsed user_id must be a local user"
|
||||
);
|
||||
|
||||
let state_lock = self
|
||||
.services
|
||||
.rooms
|
||||
.state
|
||||
.mutex
|
||||
.lock(&room_id)
|
||||
.await;
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
|
||||
let room_power_levels: Option<RoomPowerLevelsEventContent> = self
|
||||
let room_power_levels: Option<RoomPowerLevels> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content(&room_id, &StateEventType::RoomPowerLevels, "")
|
||||
.get_power_levels(&room_id)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let user_can_demote_self = room_power_levels
|
||||
let user_can_change_self = room_power_levels
|
||||
.as_ref()
|
||||
.is_some_and(|power_levels_content| {
|
||||
RoomPowerLevels::from(power_levels_content.clone())
|
||||
.user_can_change_user_power_level(&user_id, &user_id)
|
||||
}) || self
|
||||
.services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
.is_ok_and(|event| event.sender() == user_id);
|
||||
.is_some_and(|power_levels| {
|
||||
power_levels.user_can_change_user_power_level(&user_id, &user_id)
|
||||
});
|
||||
|
||||
let user_can_demote_self = user_can_change_self
|
||||
|| self
|
||||
.services
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
.is_ok_and(|event| event.sender() == user_id);
|
||||
|
||||
if !user_can_demote_self {
|
||||
return Err!("User is not allowed to modify their own power levels in the room.",);
|
||||
return Err!("User is not allowed to modify their own power levels in the room.");
|
||||
}
|
||||
|
||||
let mut power_levels_content = room_power_levels.unwrap_or_default();
|
||||
let mut power_levels_content: RoomPowerLevelsEventContent = room_power_levels
|
||||
.map(TryInto::try_into)
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
power_levels_content.users.remove(&user_id);
|
||||
|
||||
let event_id = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &power_levels_content),
|
||||
@@ -772,6 +737,85 @@ pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAli
|
||||
.await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn force_promote(
|
||||
&self,
|
||||
target_id: String,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
) -> Result {
|
||||
let target_id = parse_user_id(self.services, &target_id)?;
|
||||
let room_id = self.services.alias.resolve(&room_id).await?;
|
||||
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
|
||||
let room_power_levels = self
|
||||
.services
|
||||
.state_accessor
|
||||
.get_power_levels(&room_id)
|
||||
.await?;
|
||||
|
||||
let privileged_member = self
|
||||
.services
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.ready_filter(|member_id| {
|
||||
self.services.globals.user_is_local(member_id)
|
||||
&& room_power_levels.user_can_change_user_power_level(member_id, &target_id)
|
||||
})
|
||||
.map(ToOwned::to_owned)
|
||||
.ready_fold_default(|selected_user, member_id| match selected_user {
|
||||
| None => Some(member_id),
|
||||
| Some(selected_user) => Some(
|
||||
if room_power_levels.for_user(&selected_user)
|
||||
> room_power_levels.for_user(&member_id)
|
||||
{
|
||||
selected_user
|
||||
} else {
|
||||
member_id
|
||||
},
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
let Some(privileged_member) = privileged_member else {
|
||||
return Err!("No privileged user exists in room, cannot promote.");
|
||||
};
|
||||
|
||||
info!("Selected privileged member {privileged_member}");
|
||||
|
||||
let power_level: Int = match room_power_levels.for_user(&privileged_member) {
|
||||
| UserPowerLevel::Infinite => Int::MAX,
|
||||
| UserPowerLevel::Int(x) => x,
|
||||
};
|
||||
|
||||
let mut power_levels_content: RoomPowerLevelsEventContent = room_power_levels.try_into()?;
|
||||
|
||||
power_levels_content
|
||||
.users
|
||||
.insert(target_id.clone(), power_level);
|
||||
|
||||
let event_id = self
|
||||
.services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &power_levels_content),
|
||||
&privileged_member,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
self.write_str(&format!(
|
||||
"User {privileged_member} promoted {target_id} to {power_level} power level in \
|
||||
{room_id} - {event_id}"
|
||||
))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn make_user_admin(&self, user_id: String) -> Result {
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
@@ -783,6 +827,7 @@ pub(super) async fn make_user_admin(&self, user_id: String) -> Result {
|
||||
self.services
|
||||
.admin
|
||||
.make_user_admin(&user_id)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
self.write_str(&format!("{user_id} has been granted admin privileges.",))
|
||||
@@ -889,7 +934,6 @@ pub(super) async fn get_room_tags(&self, user_id: String, room_id: OwnedRoomId)
|
||||
pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
|
||||
let Ok(event) = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_non_outlier_pdu(&event_id)
|
||||
.await
|
||||
@@ -917,14 +961,12 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
|
||||
let redaction_event_id = {
|
||||
let state_lock = self
|
||||
.services
|
||||
.rooms
|
||||
.state
|
||||
.mutex
|
||||
.lock(event.room_id())
|
||||
.await;
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
|
||||
@@ -88,6 +88,12 @@ pub(super) enum UserCommand {
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
},
|
||||
|
||||
/// - Force promote
|
||||
ForcePromote {
|
||||
user_id: String,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
},
|
||||
|
||||
/// - Grant server-admin privileges to a user.
|
||||
MakeUserAdmin {
|
||||
user_id: String,
|
||||
|
||||
@@ -17,13 +17,11 @@ pub(crate) async fn get_room_info(
|
||||
(
|
||||
room_id.into(),
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_joined_count(room_id)
|
||||
.await
|
||||
.unwrap_or(0),
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_name(room_id)
|
||||
.await
|
||||
|
||||
+11
-236
@@ -1,32 +1,13 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{
|
||||
OwnedRoomId, UserId,
|
||||
api::client::{
|
||||
account::{
|
||||
ThirdPartyIdRemovalStatus, change_password, deactivate, get_3pids,
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
||||
whoami,
|
||||
},
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
},
|
||||
events::{
|
||||
StateEventType,
|
||||
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
},
|
||||
use ruma::api::client::account::{
|
||||
ThirdPartyIdRemovalStatus, change_password, deactivate, get_3pids,
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn, whoami,
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Error, Result, err, info,
|
||||
matrix::{Event, pdu::PduBuilder},
|
||||
utils,
|
||||
utils::{ReadyExt, stream::BroadbandExt},
|
||||
warn,
|
||||
};
|
||||
use tuwunel_service::Services;
|
||||
use tuwunel_core::{Err, Result, info, utils::ReadyExt};
|
||||
|
||||
use super::SESSION_ID_LENGTH;
|
||||
use crate::Ruma;
|
||||
use crate::{Ruma, router::auth_uiaa};
|
||||
|
||||
/// # `POST /_matrix/client/r0/account/password`
|
||||
///
|
||||
@@ -51,48 +32,7 @@ pub(crate) async fn change_password_route(
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<change_password::v3::Request>,
|
||||
) -> Result<change_password::v3::Response> {
|
||||
// Authentication for this endpoint was made optional, but we need
|
||||
// authentication currently
|
||||
let sender_user = body
|
||||
.sender_user
|
||||
.as_ref()
|
||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
||||
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
|
||||
match &body.auth {
|
||||
| Some(auth) => {
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
|
||||
// Success!
|
||||
},
|
||||
| _ => match body.json_body {
|
||||
| Some(ref json) => {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services
|
||||
.uiaa
|
||||
.create(sender_user, body.sender_device(), &uiaainfo, json);
|
||||
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("JSON body is not valid")));
|
||||
},
|
||||
},
|
||||
}
|
||||
let ref sender_user = auth_uiaa(&services, &body).await?;
|
||||
|
||||
services
|
||||
.users
|
||||
@@ -104,32 +44,9 @@ pub(crate) async fn change_password_route(
|
||||
services
|
||||
.users
|
||||
.all_device_ids(sender_user)
|
||||
.ready_filter(|id| *id != body.sender_device())
|
||||
.ready_filter(|&id| Some(id) != body.sender_device.as_deref())
|
||||
.for_each(|id| services.users.remove_device(sender_user, id))
|
||||
.await;
|
||||
|
||||
// Remove all pushers except the ones associated with this session
|
||||
services
|
||||
.pusher
|
||||
.get_pushkeys(sender_user)
|
||||
.map(ToOwned::to_owned)
|
||||
.broad_filter_map(async |pushkey| {
|
||||
services
|
||||
.pusher
|
||||
.get_pusher_device(&pushkey)
|
||||
.await
|
||||
.ok()
|
||||
.filter(|pusher_device| pusher_device != body.sender_device())
|
||||
.is_some()
|
||||
.then_some(pushkey)
|
||||
})
|
||||
.for_each(async |pushkey| {
|
||||
services
|
||||
.pusher
|
||||
.delete_pusher(sender_user, &pushkey)
|
||||
.await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
info!("User {sender_user} changed their password.");
|
||||
@@ -180,66 +97,15 @@ pub(crate) async fn deactivate_route(
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<deactivate::v3::Request>,
|
||||
) -> Result<deactivate::v3::Response> {
|
||||
// Authentication for this endpoint was made optional, but we need
|
||||
// authentication currently
|
||||
let sender_user = body
|
||||
.sender_user
|
||||
.as_ref()
|
||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
||||
let ref sender_user = auth_uiaa(&services, &body).await?;
|
||||
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
|
||||
match &body.auth {
|
||||
| Some(auth) => {
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
| _ => match body.json_body {
|
||||
| Some(ref json) => {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services
|
||||
.uiaa
|
||||
.create(sender_user, body.sender_device(), &uiaainfo, json);
|
||||
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("JSON body is not valid")));
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Remove profile pictures and display name
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(sender_user)
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
super::update_displayname(&services, sender_user, None, &all_joined_rooms).await;
|
||||
super::update_avatar_url(&services, sender_user, None, None, &all_joined_rooms).await;
|
||||
|
||||
full_user_deactivate(&services, sender_user, &all_joined_rooms)
|
||||
services
|
||||
.deactivate
|
||||
.full_deactivate(sender_user)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
info!("User {sender_user} deactivated their account.");
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
@@ -293,94 +159,3 @@ pub(crate) async fn request_3pid_management_token_via_msisdn_route(
|
||||
) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
|
||||
Err!(Request(ThreepidDenied("Third party identifiers are not implemented")))
|
||||
}
|
||||
|
||||
/// Runs through all the deactivation steps:
|
||||
///
|
||||
/// - Mark as deactivated
|
||||
/// - Removing display name
|
||||
/// - Removing avatar URL and blurhash
|
||||
/// - Removing all profile data
|
||||
/// - Leaving all rooms (and forgets all of them)
|
||||
pub async fn full_user_deactivate(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
all_joined_rooms: &[OwnedRoomId],
|
||||
) -> Result {
|
||||
services
|
||||
.users
|
||||
.deactivate_account(user_id)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
super::update_displayname(services, user_id, None, all_joined_rooms).await;
|
||||
super::update_avatar_url(services, user_id, None, None, all_joined_rooms).await;
|
||||
|
||||
services
|
||||
.users
|
||||
.all_profile_keys(user_id)
|
||||
.ready_for_each(|(profile_key, _)| {
|
||||
services
|
||||
.users
|
||||
.set_profile_key(user_id, &profile_key, None);
|
||||
})
|
||||
.await;
|
||||
|
||||
for room_id in all_joined_rooms {
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
|
||||
let room_power_levels = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content::<RoomPowerLevelsEventContent>(
|
||||
room_id,
|
||||
&StateEventType::RoomPowerLevels,
|
||||
"",
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let user_can_demote_self =
|
||||
room_power_levels
|
||||
.as_ref()
|
||||
.is_some_and(|power_levels_content| {
|
||||
RoomPowerLevels::from(power_levels_content.clone())
|
||||
.user_can_change_user_power_level(user_id, user_id)
|
||||
}) || services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
.is_ok_and(|event| event.sender() == user_id);
|
||||
|
||||
if user_can_demote_self {
|
||||
let mut power_levels_content = room_power_levels.unwrap_or_default();
|
||||
power_levels_content.users.remove(user_id);
|
||||
|
||||
// ignore errors so deactivation doesn't fail
|
||||
match services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &power_levels_content),
|
||||
user_id,
|
||||
room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Err(e) => {
|
||||
warn!(%room_id, %user_id, "Failed to demote user's own power level: {e}");
|
||||
},
|
||||
| _ => {
|
||||
info!("Demoted {user_id} in {room_id} as part of account deactivation");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super::leave_all_rooms(services, user_id)
|
||||
.boxed()
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ pub(crate) async fn create_alias_route(
|
||||
) -> Result<create_alias::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
services
|
||||
.rooms
|
||||
.alias
|
||||
.appservice_checks(&body.room_alias, &body.appservice_info)
|
||||
.await?;
|
||||
@@ -35,7 +34,6 @@ pub(crate) async fn create_alias_route(
|
||||
}
|
||||
|
||||
if services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&body.room_alias)
|
||||
.await
|
||||
@@ -45,7 +43,6 @@ pub(crate) async fn create_alias_route(
|
||||
}
|
||||
|
||||
services
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(&body.room_alias, &body.room_id, sender_user)?;
|
||||
|
||||
@@ -63,13 +60,11 @@ pub(crate) async fn delete_alias_route(
|
||||
) -> Result<delete_alias::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
services
|
||||
.rooms
|
||||
.alias
|
||||
.appservice_checks(&body.room_alias, &body.appservice_info)
|
||||
.await?;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.alias
|
||||
.remove_alias(&body.room_alias, sender_user)
|
||||
.await?;
|
||||
@@ -89,7 +84,6 @@ pub(crate) async fn get_alias_route(
|
||||
let room_alias = body.body.room_alias;
|
||||
|
||||
let Ok((room_id, servers)) = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&room_alias, None)
|
||||
.await
|
||||
@@ -111,7 +105,6 @@ async fn room_available_servers(
|
||||
) -> Vec<OwnedServerName> {
|
||||
// find active servers in room state cache to suggest
|
||||
let mut servers: Vec<OwnedServerName> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_servers(room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
use axum::extract::State;
|
||||
use ruma::{
|
||||
RoomVersionId,
|
||||
api::client::discovery::get_capabilities::{
|
||||
self, Capabilities, GetLoginTokenCapability, RoomVersionStability,
|
||||
RoomVersionsCapability, ThirdPartyIdChangesCapability,
|
||||
api::client::discovery::{
|
||||
get_capabilities,
|
||||
get_capabilities::v3::{
|
||||
Capabilities, GetLoginTokenCapability, ProfileFieldsCapability, RoomVersionStability,
|
||||
RoomVersionsCapability, ThirdPartyIdChangesCapability,
|
||||
},
|
||||
},
|
||||
};
|
||||
use serde_json::json;
|
||||
@@ -41,8 +44,7 @@ pub(crate) async fn get_capabilities_route(
|
||||
enabled: services.server.config.login_via_existing_session,
|
||||
};
|
||||
|
||||
// MSC4133 capability
|
||||
capabilities.set("uk.tcpip.msc4133.profile_fields", json!({"enabled": true}))?;
|
||||
capabilities.profile_fields = ProfileFieldsCapability::new(true).into();
|
||||
|
||||
capabilities.set(
|
||||
"org.matrix.msc4267.forget_forced_upon_leave",
|
||||
|
||||
@@ -38,7 +38,7 @@ pub(crate) async fn get_context_route(
|
||||
let event_id = &body.event_id;
|
||||
let filter = &body.filter;
|
||||
|
||||
if !services.rooms.metadata.exists(room_id).await {
|
||||
if !services.metadata.exists(room_id).await {
|
||||
return Err!(Request(Forbidden("Room does not exist to this server")));
|
||||
}
|
||||
|
||||
@@ -50,19 +50,16 @@ pub(crate) async fn get_context_route(
|
||||
.min(LIMIT_MAX);
|
||||
|
||||
let base_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_id(event_id)
|
||||
.map_err(|_| err!(Request(NotFound("Event not found."))));
|
||||
|
||||
let base_pdu = services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu(event_id)
|
||||
.map_err(|_| err!(Request(NotFound("Base event not found."))));
|
||||
|
||||
let visible = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, room_id, event_id)
|
||||
.map(Ok);
|
||||
@@ -74,7 +71,11 @@ pub(crate) async fn get_context_route(
|
||||
}
|
||||
|
||||
if !visible {
|
||||
debug_warn!(req_evt = ?event_id, ?base_id, ?room_id, "Event requested by {sender_user} but is not allowed to see it, returning 404");
|
||||
debug_warn!(
|
||||
req_evt = ?event_id, ?base_id, ?room_id,
|
||||
"Event requested by {sender_user} but is not allowed to see it."
|
||||
);
|
||||
|
||||
return Err!(Request(NotFound("Event not found.")));
|
||||
}
|
||||
|
||||
@@ -83,7 +84,6 @@ pub(crate) async fn get_context_route(
|
||||
let base_event = ignored_filter(&services, (base_count, base_pdu), sender_user);
|
||||
|
||||
let events_before = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(Some(sender_user), room_id, Some(base_count))
|
||||
.ignore_err()
|
||||
@@ -94,14 +94,13 @@ pub(crate) async fn get_context_route(
|
||||
.collect();
|
||||
|
||||
let events_after = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus(Some(sender_user), room_id, Some(base_count))
|
||||
.ignore_err()
|
||||
.ready_filter_map(|item| event_filter(item, filter))
|
||||
.wide_filter_map(|item| ignored_filter(&services, item, sender_user))
|
||||
.wide_filter_map(|item| visibility_filter(&services, item, sender_user))
|
||||
.take(limit / 2)
|
||||
.take(limit.div_ceil(2))
|
||||
.collect();
|
||||
|
||||
let (base_event, events_before, events_after): (_, Vec<_>, Vec<_>) =
|
||||
@@ -135,18 +134,11 @@ pub(crate) async fn get_context_route(
|
||||
.map_or_else(|| body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
|
||||
|
||||
let state_ids = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.pdu_shortstatehash(state_at)
|
||||
.or_else(|_| {
|
||||
services
|
||||
.rooms
|
||||
.state
|
||||
.get_room_shortstatehash(room_id)
|
||||
})
|
||||
.or_else(|_| services.state.get_room_shortstatehash(room_id))
|
||||
.map_ok(|shortstatehash| {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_full_ids(shortstatehash)
|
||||
.map(Ok)
|
||||
@@ -163,7 +155,6 @@ pub(crate) async fn get_context_route(
|
||||
let shorteventids = state_ids.iter().map(ref_at!(1)).stream();
|
||||
let lazy_loading_witnessed = lazy_loading_witnessed.unwrap_or_default();
|
||||
let state: Vec<_> = services
|
||||
.rooms
|
||||
.short
|
||||
.multi_get_statekey_from_short(shortstatekeys)
|
||||
.zip(shorteventids)
|
||||
@@ -182,11 +173,7 @@ pub(crate) async fn get_context_route(
|
||||
Some(event_id)
|
||||
})
|
||||
.broad_filter_map(|event_id: &OwnedEventId| {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu(event_id.as_ref())
|
||||
.ok()
|
||||
services.timeline.get_pdu(event_id.as_ref()).ok()
|
||||
})
|
||||
.map(Event::into_format)
|
||||
.collect()
|
||||
|
||||
@@ -3,16 +3,13 @@
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
MilliSecondsSinceUnixEpoch, OwnedDeviceId,
|
||||
api::client::{
|
||||
device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
|
||||
error::ErrorKind,
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
api::client::device::{
|
||||
self, delete_device, delete_devices, get_device, get_devices, update_device,
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{Err, Error, Result, debug, err, utils};
|
||||
use tuwunel_core::{Err, Result, debug, err, utils};
|
||||
|
||||
use super::SESSION_ID_LENGTH;
|
||||
use crate::{Ruma, client::DEVICE_ID_LENGTH};
|
||||
use crate::{Ruma, client::DEVICE_ID_LENGTH, router::auth_uiaa};
|
||||
|
||||
/// # `GET /_matrix/client/r0/devices`
|
||||
///
|
||||
@@ -126,10 +123,10 @@ pub(crate) async fn delete_device_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_device::v3::Request>,
|
||||
) -> Result<delete_device::v3::Response> {
|
||||
let (sender_user, sender_device) = body.sender();
|
||||
let appservice = body.appservice_info.as_ref();
|
||||
|
||||
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
||||
let sender_user = body.sender_user();
|
||||
debug!(
|
||||
"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
|
||||
enabled"
|
||||
@@ -142,41 +139,7 @@ pub(crate) async fn delete_device_route(
|
||||
return Ok(delete_device::v3::Response {});
|
||||
}
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
|
||||
match &body.auth {
|
||||
| Some(auth) => {
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(sender_user, sender_device, auth, &uiaainfo)
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
| _ => match body.json_body {
|
||||
| Some(ref json) => {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services
|
||||
.uiaa
|
||||
.create(sender_user, sender_device, &uiaainfo, json);
|
||||
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("Not json.")));
|
||||
},
|
||||
},
|
||||
}
|
||||
let ref sender_user = auth_uiaa(&services, &body).await?;
|
||||
|
||||
services
|
||||
.users
|
||||
@@ -203,10 +166,10 @@ pub(crate) async fn delete_devices_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_devices::v3::Request>,
|
||||
) -> Result<delete_devices::v3::Response> {
|
||||
let (sender_user, sender_device) = body.sender();
|
||||
let appservice = body.appservice_info.as_ref();
|
||||
|
||||
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
||||
let sender_user = body.sender_user();
|
||||
debug!(
|
||||
"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
|
||||
enabled"
|
||||
@@ -221,41 +184,7 @@ pub(crate) async fn delete_devices_route(
|
||||
return Ok(delete_devices::v3::Response {});
|
||||
}
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
|
||||
match &body.auth {
|
||||
| Some(auth) => {
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(sender_user, sender_device, auth, &uiaainfo)
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
| _ => match body.json_body {
|
||||
| Some(ref json) => {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services
|
||||
.uiaa
|
||||
.create(sender_user, sender_device, &uiaainfo, json);
|
||||
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
||||
},
|
||||
},
|
||||
}
|
||||
let ref sender_user = auth_uiaa(&services, &body).await?;
|
||||
|
||||
for device_id in &body.devices {
|
||||
services
|
||||
|
||||
+50
-73
@@ -16,18 +16,15 @@
|
||||
},
|
||||
federation,
|
||||
},
|
||||
directory::{Filter, PublicRoomJoinRule, PublicRoomsChunk, RoomNetwork, RoomTypeFilter},
|
||||
directory::{Filter, PublicRoomsChunk, RoomNetwork, RoomTypeFilter},
|
||||
events::{
|
||||
StateEventType,
|
||||
room::{
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
},
|
||||
room::join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
},
|
||||
uint,
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Result, err, info,
|
||||
Err, Result, err, info, is_true,
|
||||
matrix::Event,
|
||||
utils::{
|
||||
TryFutureExtExt,
|
||||
@@ -51,7 +48,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_public_rooms_filtered::v3::Request>,
|
||||
) -> Result<get_public_rooms_filtered::v3::Response> {
|
||||
check_banned(&services, body.server.as_deref())?;
|
||||
check_server_banned(&services, body.server.as_deref())?;
|
||||
|
||||
let response = get_public_rooms_filtered_helper(
|
||||
&services,
|
||||
@@ -80,7 +77,7 @@ pub(crate) async fn get_public_rooms_route(
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_public_rooms::v3::Request>,
|
||||
) -> Result<get_public_rooms::v3::Response> {
|
||||
check_banned(&services, body.server.as_deref())?;
|
||||
check_server_banned(&services, body.server.as_deref())?;
|
||||
|
||||
let response = get_public_rooms_filtered_helper(
|
||||
&services,
|
||||
@@ -114,12 +111,7 @@ pub(crate) async fn set_room_visibility_route(
|
||||
) -> Result<set_room_visibility::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.metadata
|
||||
.exists(&body.room_id)
|
||||
.await
|
||||
{
|
||||
if !services.metadata.exists(&body.room_id).await {
|
||||
// Return 404 if the room doesn't exist
|
||||
return Err!(Request(NotFound("Room not found")));
|
||||
}
|
||||
@@ -169,7 +161,7 @@ pub(crate) async fn set_room_visibility_route(
|
||||
)));
|
||||
}
|
||||
|
||||
services.rooms.directory.set_public(&body.room_id);
|
||||
services.directory.set_public(&body.room_id);
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
@@ -182,10 +174,7 @@ pub(crate) async fn set_room_visibility_route(
|
||||
}
|
||||
info!("{sender_user} made {0} public to the room directory", body.room_id);
|
||||
},
|
||||
| room::Visibility::Private => services
|
||||
.rooms
|
||||
.directory
|
||||
.set_not_public(&body.room_id),
|
||||
| room::Visibility::Private => services.directory.set_not_public(&body.room_id),
|
||||
| _ => {
|
||||
return Err!(Request(InvalidParam("Room visibility type is not supported.",)));
|
||||
},
|
||||
@@ -201,19 +190,13 @@ pub(crate) async fn get_room_visibility_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_room_visibility::v3::Request>,
|
||||
) -> Result<get_room_visibility::v3::Response> {
|
||||
if !services
|
||||
.rooms
|
||||
.metadata
|
||||
.exists(&body.room_id)
|
||||
.await
|
||||
{
|
||||
if !services.metadata.exists(&body.room_id).await {
|
||||
// Return 404 if the room doesn't exist
|
||||
return Err!(Request(NotFound("Room not found")));
|
||||
}
|
||||
|
||||
Ok(get_room_visibility::v3::Response {
|
||||
visibility: if services
|
||||
.rooms
|
||||
.directory
|
||||
.is_public_room(&body.room_id)
|
||||
.await
|
||||
@@ -298,13 +281,12 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||
|
||||
let meta_public_rooms = search_room_id
|
||||
.filter(|_| services.config.allow_unlisted_room_search_by_id)
|
||||
.map(|prefix| services.rooms.metadata.public_ids_prefix(prefix))
|
||||
.map(|prefix| services.metadata.public_ids_prefix(prefix))
|
||||
.into_iter()
|
||||
.stream()
|
||||
.flatten();
|
||||
|
||||
let mut all_rooms: Vec<PublicRoomsChunk> = services
|
||||
.rooms
|
||||
.directory
|
||||
.public_rooms()
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -391,20 +373,14 @@ async fn user_can_publish_room(
|
||||
room_id: &RoomId,
|
||||
) -> Result<bool> {
|
||||
match services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")
|
||||
.get_power_levels(room_id)
|
||||
.await
|
||||
{
|
||||
| Ok(event) => serde_json::from_str(event.content().get())
|
||||
.map_err(|_| err!(Database("Invalid event content for m.room.power_levels")))
|
||||
.map(|content: RoomPowerLevelsEventContent| {
|
||||
RoomPowerLevels::from(content)
|
||||
.user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)
|
||||
}),
|
||||
| Ok(power_levels) =>
|
||||
Ok(power_levels.user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)),
|
||||
| _ => {
|
||||
match services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
@@ -417,57 +393,57 @@ async fn user_can_publish_room(
|
||||
}
|
||||
|
||||
async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> PublicRoomsChunk {
|
||||
let name = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_name(&room_id)
|
||||
.ok();
|
||||
let name = services.state_accessor.get_name(&room_id).ok();
|
||||
|
||||
let room_type = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_room_type(&room_id)
|
||||
.ok();
|
||||
|
||||
let canonical_alias = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_canonical_alias(&room_id)
|
||||
.ok()
|
||||
.then(async |alias| {
|
||||
if let Some(alias) = alias
|
||||
&& services.globals.alias_is_local(&alias)
|
||||
&& let Ok(alias_room_id) = services.alias.resolve_local_alias(&alias).await
|
||||
&& alias_room_id == room_id
|
||||
{
|
||||
Some(alias)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let avatar_url = services
|
||||
.state_accessor
|
||||
.get_avatar(&room_id)
|
||||
.map_ok(|content| content.url)
|
||||
.ok();
|
||||
|
||||
let avatar_url = services.rooms.state_accessor.get_avatar(&room_id);
|
||||
|
||||
let topic = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_room_topic(&room_id)
|
||||
.ok();
|
||||
|
||||
let world_readable = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.is_world_readable(&room_id);
|
||||
|
||||
let join_rule = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content(&room_id, &StateEventType::RoomJoinRules, "")
|
||||
.map_ok(|c: RoomJoinRulesEventContent| match c.join_rule {
|
||||
| JoinRule::Public => PublicRoomJoinRule::Public,
|
||||
| JoinRule::Public => "public".into(),
|
||||
| JoinRule::Knock => "knock".into(),
|
||||
| JoinRule::KnockRestricted(_) => "knock_restricted".into(),
|
||||
| _ => "invite".into(),
|
||||
});
|
||||
|
||||
let guest_can_join = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.guest_can_join(&room_id);
|
||||
let guest_can_join = services.state_accessor.guest_can_join(&room_id);
|
||||
|
||||
let num_joined_members = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_joined_count(&room_id);
|
||||
let num_joined_members = services.state_cache.room_joined_count(&room_id);
|
||||
|
||||
let (
|
||||
(avatar_url, canonical_alias, guest_can_join, join_rule, name),
|
||||
@@ -480,7 +456,7 @@ async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> Public
|
||||
.await;
|
||||
|
||||
PublicRoomsChunk {
|
||||
avatar_url: avatar_url.into_option().unwrap_or_default().url,
|
||||
avatar_url: avatar_url.flatten(),
|
||||
canonical_alias,
|
||||
guest_can_join,
|
||||
join_rule: join_rule.unwrap_or_default(),
|
||||
@@ -497,24 +473,25 @@ async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> Public
|
||||
}
|
||||
}
|
||||
|
||||
fn check_banned(services: &Services, server: Option<&ServerName>) -> Result {
|
||||
fn check_server_banned(services: &Services, server: Option<&ServerName>) -> Result {
|
||||
let Some(server) = server else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let forbidden_remote_directory = services
|
||||
.config
|
||||
.forbidden_remote_room_directory_server_names
|
||||
.is_match(server.host());
|
||||
let conditions = [
|
||||
services
|
||||
.config
|
||||
.forbidden_remote_room_directory_server_names
|
||||
.is_match(server.host()),
|
||||
services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(server.host()),
|
||||
];
|
||||
|
||||
let forbidden_remote_server = services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(server.host());
|
||||
|
||||
if forbidden_remote_directory || forbidden_remote_server {
|
||||
Err!(Request(Forbidden("Server is banned on this homeserver.")))
|
||||
} else {
|
||||
Ok(())
|
||||
if conditions.iter().any(is_true!()) {
|
||||
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+19
-19
@@ -3,7 +3,8 @@
|
||||
use axum::extract::State;
|
||||
use futures::{StreamExt, stream::FuturesUnordered};
|
||||
use ruma::{
|
||||
OneTimeKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
|
||||
CanonicalJsonObject, CanonicalJsonValue, OneTimeKeyAlgorithm, OwnedDeviceId, OwnedUserId,
|
||||
UserId,
|
||||
api::{
|
||||
client::{
|
||||
error::ErrorKind,
|
||||
@@ -39,7 +40,11 @@ pub(crate) async fn upload_keys_route(
|
||||
) -> Result<upload_keys::v3::Response> {
|
||||
let (sender_user, sender_device) = body.sender();
|
||||
|
||||
for (key_id, one_time_key) in &body.one_time_keys {
|
||||
for (key_id, one_time_key) in body
|
||||
.one_time_keys
|
||||
.iter()
|
||||
.take(services.config.one_time_key_limit)
|
||||
{
|
||||
if one_time_key
|
||||
.deserialize()
|
||||
.inspect_err(|e| {
|
||||
@@ -162,10 +167,7 @@ pub(crate) async fn upload_signing_keys_route(
|
||||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match check_for_new_keys(
|
||||
@@ -396,7 +398,6 @@ pub(crate) async fn get_key_changes_route(
|
||||
);
|
||||
|
||||
let mut rooms_joined = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(sender_user)
|
||||
.boxed();
|
||||
@@ -599,20 +600,19 @@ fn add_unsigned_device_display_name(
|
||||
include_display_names: bool,
|
||||
) -> Result {
|
||||
if let Some(display_name) = metadata.display_name {
|
||||
let mut object = keys.deserialize_as::<serde_json::Map<String, serde_json::Value>>()?;
|
||||
let mut object = keys.deserialize_as_unchecked::<CanonicalJsonObject>()?;
|
||||
|
||||
let unsigned = object
|
||||
.entry("unsigned")
|
||||
.or_insert_with(|| json!({}));
|
||||
if let serde_json::Value::Object(unsigned_object) = unsigned {
|
||||
if include_display_names {
|
||||
unsigned_object.insert("device_display_name".to_owned(), display_name.into());
|
||||
if let CanonicalJsonValue::Object(unsigned) = object
|
||||
.entry("unsigned".into())
|
||||
.or_insert_with(|| CanonicalJsonObject::default().into())
|
||||
{
|
||||
let display_name = if include_display_names {
|
||||
CanonicalJsonValue::String(display_name)
|
||||
} else {
|
||||
unsigned_object.insert(
|
||||
"device_display_name".to_owned(),
|
||||
Some(metadata.device_id.as_str().to_owned()).into(),
|
||||
);
|
||||
}
|
||||
CanonicalJsonValue::String(metadata.device_id.into())
|
||||
};
|
||||
|
||||
unsigned.insert("device_display_name".into(), display_name);
|
||||
}
|
||||
|
||||
*keys = Raw::from_json(serde_json::value::to_raw_value(&object)?);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use axum::extract::State;
|
||||
use ruma::{
|
||||
api::client::membership::ban_user,
|
||||
events::room::member::{MembershipState, RoomMemberEventContent},
|
||||
};
|
||||
use tuwunel_core::{Err, Result, matrix::pdu::PduBuilder};
|
||||
use futures::FutureExt;
|
||||
use ruma::api::client::membership::ban_user;
|
||||
use tuwunel_core::{Err, Result};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -20,38 +18,12 @@ pub(crate) async fn ban_user_route(
|
||||
return Err!(Request(Forbidden("You cannot ban yourself.")));
|
||||
}
|
||||
|
||||
let state_lock = services
|
||||
.rooms
|
||||
.state
|
||||
.mutex
|
||||
.lock(&body.room_id)
|
||||
.await;
|
||||
|
||||
let current_member_content = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(&body.room_id, &body.user_id)
|
||||
.await
|
||||
.unwrap_or_else(|_| RoomMemberEventContent::new(MembershipState::Ban));
|
||||
let state_lock = services.state.mutex.lock(&body.room_id).await;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
|
||||
membership: MembershipState::Ban,
|
||||
reason: body.reason.clone(),
|
||||
displayname: None, // display name may be offensive
|
||||
avatar_url: None, // avatar may be offensive
|
||||
is_direct: None,
|
||||
join_authorized_via_users_server: None,
|
||||
third_party_invite: None,
|
||||
..current_member_content
|
||||
}),
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.membership
|
||||
.ban(&body.room_id, &body.user_id, body.reason.as_ref(), sender_user, &state_lock)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
@@ -21,18 +21,9 @@ pub(crate) async fn forget_room_route(
|
||||
let user_id = body.sender_user();
|
||||
let room_id = &body.room_id;
|
||||
|
||||
let joined = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(user_id, room_id);
|
||||
let knocked = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_knocked(user_id, room_id);
|
||||
let invited = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_invited(user_id, room_id);
|
||||
let joined = services.state_cache.is_joined(user_id, room_id);
|
||||
let knocked = services.state_cache.is_knocked(user_id, room_id);
|
||||
let invited = services.state_cache.is_invited(user_id, room_id);
|
||||
|
||||
pin_mut!(joined, knocked, invited);
|
||||
if joined.or(knocked).or(invited).await {
|
||||
@@ -40,7 +31,6 @@ pub(crate) async fn forget_room_route(
|
||||
}
|
||||
|
||||
let membership = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(room_id, user_id)
|
||||
.await;
|
||||
@@ -55,15 +45,11 @@ pub(crate) async fn forget_room_route(
|
||||
|
||||
if non_membership
|
||||
|| services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_left(user_id, room_id)
|
||||
.await
|
||||
{
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.forget(room_id, user_id);
|
||||
services.state_cache.forget(room_id, user_id);
|
||||
}
|
||||
|
||||
Ok(forget_room::v3::Response::new())
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::{FutureExt, join};
|
||||
use ruma::{
|
||||
OwnedServerName, RoomId, UserId,
|
||||
api::{client::membership::invite_user, federation::membership::create_invite},
|
||||
events::room::member::{MembershipState, RoomMemberEventContent},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Result, debug_error, err, info,
|
||||
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
|
||||
};
|
||||
use tuwunel_service::Services;
|
||||
use ruma::{api::client::membership::invite_user, events::room::member::MembershipState};
|
||||
use tuwunel_core::{Err, Result};
|
||||
|
||||
use super::banned_room_check;
|
||||
use crate::Ruma;
|
||||
use crate::{Ruma, client::utils::invite_check};
|
||||
|
||||
/// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
|
||||
///
|
||||
@@ -26,220 +18,56 @@ pub(crate) async fn invite_user_route(
|
||||
) -> Result<invite_user::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
|
||||
debug_error!(
|
||||
"User {sender_user} is not an admin and attempted to send an invite to room {}",
|
||||
&body.room_id
|
||||
);
|
||||
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
|
||||
}
|
||||
let room_id = &body.room_id;
|
||||
|
||||
banned_room_check(
|
||||
&services,
|
||||
sender_user,
|
||||
Some(&body.room_id),
|
||||
body.room_id.server_name(),
|
||||
client,
|
||||
)
|
||||
.await?;
|
||||
invite_check(&services, sender_user, room_id).await?;
|
||||
|
||||
match &body.recipient {
|
||||
| invite_user::v3::InvitationRecipient::UserId { user_id } => {
|
||||
let sender_ignored_recipient = services
|
||||
.users
|
||||
.user_is_ignored(sender_user, user_id);
|
||||
let recipient_ignored_by_sender = services
|
||||
.users
|
||||
.user_is_ignored(user_id, sender_user);
|
||||
|
||||
let (sender_ignored_recipient, recipient_ignored_by_sender) =
|
||||
join!(sender_ignored_recipient, recipient_ignored_by_sender);
|
||||
|
||||
if sender_ignored_recipient {
|
||||
return Ok(invite_user::v3::Response {});
|
||||
}
|
||||
|
||||
if let Ok(target_user_membership) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(&body.room_id, user_id)
|
||||
.await
|
||||
{
|
||||
if target_user_membership.membership == MembershipState::Ban {
|
||||
return Err!(Request(Forbidden("User is banned from this room.")));
|
||||
}
|
||||
}
|
||||
|
||||
if recipient_ignored_by_sender {
|
||||
// silently drop the invite to the recipient if they've been ignored by the
|
||||
// sender, pretend it worked
|
||||
return Ok(invite_user::v3::Response {});
|
||||
}
|
||||
|
||||
invite_helper(
|
||||
&services,
|
||||
sender_user,
|
||||
user_id,
|
||||
&body.room_id,
|
||||
body.reason.clone(),
|
||||
false,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
Ok(invite_user::v3::Response {})
|
||||
},
|
||||
| _ => {
|
||||
Err!(Request(NotFound("User not found.")))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn invite_helper(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
reason: Option<String>,
|
||||
is_direct: bool,
|
||||
) -> Result {
|
||||
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
|
||||
info!(
|
||||
"User {sender_user} is not an admin and attempted to send an invite to room \
|
||||
{room_id}"
|
||||
);
|
||||
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
|
||||
}
|
||||
|
||||
if !services.globals.user_is_local(user_id) {
|
||||
let (pdu, pdu_json, invite_room_state) = {
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
|
||||
let content = RoomMemberEventContent {
|
||||
avatar_url: services.users.avatar_url(user_id).await.ok(),
|
||||
is_direct: Some(is_direct),
|
||||
reason,
|
||||
..RoomMemberEventContent::new(MembershipState::Invite)
|
||||
};
|
||||
|
||||
let (pdu, pdu_json) = services
|
||||
.rooms
|
||||
.timeline
|
||||
.create_hash_and_sign_event(
|
||||
PduBuilder::state(user_id.to_string(), &content),
|
||||
sender_user,
|
||||
room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let invite_room_state = services.rooms.state.summary_stripped(&pdu).await;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
(pdu, pdu_json, invite_room_state)
|
||||
};
|
||||
|
||||
let room_version_id = services
|
||||
.rooms
|
||||
.state
|
||||
.get_room_version(room_id)
|
||||
.await?;
|
||||
|
||||
let response = services
|
||||
.sending
|
||||
.send_federation_request(user_id.server_name(), create_invite::v2::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id: (*pdu.event_id).to_owned(),
|
||||
room_version: room_version_id.clone(),
|
||||
event: services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(pdu_json.clone())
|
||||
.await,
|
||||
invite_room_state,
|
||||
via: services
|
||||
.rooms
|
||||
.state_cache
|
||||
.servers_route_via(room_id)
|
||||
.await
|
||||
.ok(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
// We do not add the event_id field to the pdu here because of signature and
|
||||
// hashes checks
|
||||
let (event_id, value) = gen_event_id_canonical_json(&response.event, &room_version_id)
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
|
||||
})?;
|
||||
|
||||
if pdu.event_id != event_id {
|
||||
return Err!(Request(BadJson(warn!(
|
||||
%pdu.event_id, %event_id,
|
||||
"Server {} sent event with wrong event ID",
|
||||
user_id.server_name()
|
||||
))));
|
||||
}
|
||||
|
||||
let origin: OwnedServerName = serde_json::from_value(serde_json::to_value(
|
||||
value
|
||||
.get("origin")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing origin field."))))?,
|
||||
)?)
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(warn!("Origin field in event is not a valid server name: {e}"))))
|
||||
})?;
|
||||
|
||||
let pdu_id = services
|
||||
.rooms
|
||||
.event_handler
|
||||
.handle_incoming_pdu(&origin, room_id, &event_id, value, true)
|
||||
.boxed()
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
err!(Request(InvalidParam("Could not accept incoming PDU as timeline event.")))
|
||||
})?;
|
||||
|
||||
return services
|
||||
.sending
|
||||
.send_pdu_room(room_id, &pdu_id)
|
||||
.await;
|
||||
}
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(sender_user, room_id)
|
||||
.await
|
||||
{
|
||||
return Err!(Request(Forbidden(
|
||||
"You must be joined in the room you are trying to invite from."
|
||||
)));
|
||||
}
|
||||
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
|
||||
let content = RoomMemberEventContent {
|
||||
displayname: services.users.displayname(user_id).await.ok(),
|
||||
avatar_url: services.users.avatar_url(user_id).await.ok(),
|
||||
blurhash: services.users.blurhash(user_id).await.ok(),
|
||||
is_direct: Some(is_direct),
|
||||
reason,
|
||||
..RoomMemberEventContent::new(MembershipState::Invite)
|
||||
};
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(user_id.to_string(), &content),
|
||||
sender_user,
|
||||
room_id,
|
||||
&state_lock,
|
||||
)
|
||||
banned_room_check(&services, sender_user, Some(room_id), room_id.server_name(), client)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
let invite_user::v3::InvitationRecipient::UserId { user_id } = &body.recipient else {
|
||||
return Err!(Request(ThreepidDenied("Third party identifiers are not implemented")));
|
||||
};
|
||||
|
||||
Ok(())
|
||||
let sender_ignored_recipient = services
|
||||
.users
|
||||
.user_is_ignored(sender_user, user_id);
|
||||
|
||||
let recipient_ignored_by_sender = services
|
||||
.users
|
||||
.user_is_ignored(user_id, sender_user);
|
||||
|
||||
let (sender_ignored_recipient, recipient_ignored_by_sender) =
|
||||
join!(sender_ignored_recipient, recipient_ignored_by_sender);
|
||||
|
||||
if sender_ignored_recipient {
|
||||
return Ok(invite_user::v3::Response {});
|
||||
}
|
||||
|
||||
// TODO: this should be in the service, but moving it from here would
|
||||
// trigger the recipient_ignored_by_sender check before the banned check,
|
||||
// revealing the ignore state to the sending user if the recipient is banned
|
||||
if let Ok(target_user_membership) = services
|
||||
.state_accessor
|
||||
.get_member(room_id, user_id)
|
||||
.await
|
||||
{
|
||||
if target_user_membership.membership == MembershipState::Ban {
|
||||
return Err!(Request(Forbidden("User is banned from this room.")));
|
||||
}
|
||||
}
|
||||
|
||||
if recipient_ignored_by_sender {
|
||||
// silently drop the invite to the recipient if they've been ignored by the
|
||||
// sender, pretend it worked
|
||||
return Ok(invite_user::v3::Response {});
|
||||
}
|
||||
|
||||
services
|
||||
.membership
|
||||
.invite(sender_user, user_id, room_id, body.reason.as_ref(), false)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
Ok(invite_user::v3::Response {})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,7 @@
|
||||
use axum::extract::State;
|
||||
use ruma::{
|
||||
api::client::membership::kick_user,
|
||||
events::room::member::{MembershipState, RoomMemberEventContent},
|
||||
};
|
||||
use tuwunel_core::{Err, Result, matrix::pdu::PduBuilder};
|
||||
use futures::FutureExt;
|
||||
use ruma::api::client::membership::kick_user;
|
||||
use tuwunel_core::{Err, Result};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -14,50 +12,18 @@ pub(crate) async fn kick_user_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<kick_user::v3::Request>,
|
||||
) -> Result<kick_user::v3::Response> {
|
||||
let state_lock = services
|
||||
.rooms
|
||||
.state
|
||||
.mutex
|
||||
.lock(&body.room_id)
|
||||
.await;
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
let Ok(event) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(&body.room_id, &body.user_id)
|
||||
.await
|
||||
else {
|
||||
// copy synapse's behaviour of returning 200 without any change to the state
|
||||
// instead of erroring on left users
|
||||
return Ok(kick_user::v3::Response::new());
|
||||
};
|
||||
|
||||
if !matches!(
|
||||
event.membership,
|
||||
MembershipState::Invite | MembershipState::Knock | MembershipState::Join,
|
||||
) {
|
||||
return Err!(Request(Forbidden(
|
||||
"Cannot kick a user who is not apart of the room (current membership: {})",
|
||||
event.membership
|
||||
)));
|
||||
if sender_user == body.user_id {
|
||||
return Err!(Request(Forbidden("You cannot kick yourself.")));
|
||||
}
|
||||
|
||||
let state_lock = services.state.mutex.lock(&body.room_id).await;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
|
||||
membership: MembershipState::Leave,
|
||||
reason: body.reason.clone(),
|
||||
is_direct: None,
|
||||
join_authorized_via_users_server: None,
|
||||
third_party_invite: None,
|
||||
..event
|
||||
}),
|
||||
body.sender_user(),
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.membership
|
||||
.kick(&body.room_id, &body.user_id, body.reason.as_ref(), sender_user, &state_lock)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, RoomId,
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedServerName, RoomId,
|
||||
RoomVersionId, UserId,
|
||||
api::{
|
||||
client::knock::knock_room,
|
||||
federation::{self},
|
||||
federation::{
|
||||
membership::RawStrippedState,
|
||||
{self},
|
||||
},
|
||||
},
|
||||
canonical_json::to_canonical_value,
|
||||
events::{
|
||||
@@ -17,14 +20,13 @@
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Result, debug, debug_info, debug_warn, err, info,
|
||||
Err, Result, debug, debug_info, debug_warn, err, extract_variant, info,
|
||||
matrix::{
|
||||
event::{Event, gen_event_id},
|
||||
pdu::{PduBuilder, PduEvent},
|
||||
},
|
||||
result::FlatOk,
|
||||
trace,
|
||||
utils::{self, shuffle, stream::IterStream},
|
||||
utils::{self},
|
||||
warn,
|
||||
};
|
||||
use tuwunel_service::{
|
||||
@@ -36,7 +38,7 @@
|
||||
};
|
||||
|
||||
use super::banned_room_check;
|
||||
use crate::Ruma;
|
||||
use crate::{Ruma, client::membership::get_join_params};
|
||||
|
||||
/// # `POST /_matrix/client/*/knock/{roomIdOrAlias}`
|
||||
///
|
||||
@@ -50,102 +52,13 @@ pub(crate) async fn knock_room_route(
|
||||
let sender_user = body.sender_user();
|
||||
let body = &body.body;
|
||||
|
||||
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias.clone()) {
|
||||
| Ok(room_id) => {
|
||||
banned_room_check(
|
||||
&services,
|
||||
sender_user,
|
||||
Some(&room_id),
|
||||
room_id.server_name(),
|
||||
client,
|
||||
)
|
||||
.await?;
|
||||
let (room_id, servers) =
|
||||
get_join_params(&services, sender_user, &body.room_id_or_alias, &body.via).await?;
|
||||
|
||||
let mut servers = body.via.clone();
|
||||
servers.extend(
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.servers_invite_via(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
.await,
|
||||
);
|
||||
banned_room_check(&services, sender_user, Some(&room_id), room_id.server_name(), client)
|
||||
.await?;
|
||||
|
||||
servers.extend(
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.invite_state(sender_user, &room_id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter_map(|event| event.get_field("sender").ok().flatten())
|
||||
.filter_map(|sender: &str| UserId::parse(sender).ok())
|
||||
.map(|user| user.server_name().to_owned()),
|
||||
);
|
||||
|
||||
if let Some(server) = room_id.server_name() {
|
||||
servers.push(server.to_owned());
|
||||
}
|
||||
|
||||
servers.sort_unstable();
|
||||
servers.dedup();
|
||||
shuffle(&mut servers);
|
||||
|
||||
(servers, room_id)
|
||||
},
|
||||
| Err(room_alias) => {
|
||||
let (room_id, mut servers) = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&room_alias, Some(body.via.clone()))
|
||||
.await?;
|
||||
|
||||
banned_room_check(
|
||||
&services,
|
||||
sender_user,
|
||||
Some(&room_id),
|
||||
Some(room_alias.server_name()),
|
||||
client,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let addl_via_servers = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.servers_invite_via(&room_id)
|
||||
.map(ToOwned::to_owned);
|
||||
|
||||
let addl_state_servers = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.invite_state(sender_user, &room_id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut addl_servers: Vec<_> = addl_state_servers
|
||||
.iter()
|
||||
.map(|event| event.get_field("sender"))
|
||||
.filter_map(FlatOk::flat_ok)
|
||||
.map(|user: &UserId| user.server_name().to_owned())
|
||||
.stream()
|
||||
.chain(addl_via_servers)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
addl_servers.sort_unstable();
|
||||
addl_servers.dedup();
|
||||
shuffle(&mut addl_servers);
|
||||
servers.append(&mut addl_servers);
|
||||
|
||||
(servers, room_id)
|
||||
},
|
||||
};
|
||||
|
||||
knock_room_by_id_helper(&services, sender_user, &room_id, body.reason.clone(), &servers)
|
||||
.boxed()
|
||||
.await
|
||||
knock_room_by_id_helper(&services, sender_user, &room_id, body.reason.clone(), &servers).await
|
||||
}
|
||||
|
||||
async fn knock_room_by_id_helper(
|
||||
@@ -155,10 +68,9 @@ async fn knock_room_by_id_helper(
|
||||
reason: Option<String>,
|
||||
servers: &[OwnedServerName],
|
||||
) -> Result<knock_room::v3::Response> {
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
let state_lock = services.state.mutex.lock(room_id).await;
|
||||
|
||||
if services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_invited(sender_user, room_id)
|
||||
.await
|
||||
@@ -170,7 +82,6 @@ async fn knock_room_by_id_helper(
|
||||
}
|
||||
|
||||
if services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(sender_user, room_id)
|
||||
.await
|
||||
@@ -180,7 +91,6 @@ async fn knock_room_by_id_helper(
|
||||
}
|
||||
|
||||
if services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_knocked(sender_user, room_id)
|
||||
.await
|
||||
@@ -190,7 +100,6 @@ async fn knock_room_by_id_helper(
|
||||
}
|
||||
|
||||
if let Ok(membership) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(room_id, sender_user)
|
||||
.await
|
||||
@@ -202,7 +111,6 @@ async fn knock_room_by_id_helper(
|
||||
}
|
||||
|
||||
let server_in_room = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(services.globals.server_name(), room_id)
|
||||
.await;
|
||||
@@ -234,11 +142,7 @@ async fn knock_room_helper_local(
|
||||
) -> Result {
|
||||
debug_info!("We can knock locally");
|
||||
|
||||
let room_version_id = services
|
||||
.rooms
|
||||
.state
|
||||
.get_room_version(room_id)
|
||||
.await?;
|
||||
let room_version_id = services.state.get_room_version(room_id).await?;
|
||||
|
||||
if matches!(
|
||||
room_version_id,
|
||||
@@ -262,7 +166,6 @@ async fn knock_room_helper_local(
|
||||
|
||||
// Try normal knock first
|
||||
let Err(error) = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(sender_user.to_string(), &content),
|
||||
@@ -346,12 +249,12 @@ async fn knock_room_helper_local(
|
||||
let knock_event = knock_event_stub;
|
||||
|
||||
info!("Asking {remote_server} for send_knock in room {room_id}");
|
||||
let send_knock_request = federation::knock::send_knock::v1::Request {
|
||||
let send_knock_request = federation::membership::create_knock_event::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id: event_id.clone(),
|
||||
pdu: services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(knock_event.clone())
|
||||
.federation
|
||||
.format_pdu_into(knock_event.clone(), Some(&room_version_id))
|
||||
.await,
|
||||
};
|
||||
|
||||
@@ -363,7 +266,6 @@ async fn knock_room_helper_local(
|
||||
info!("send_knock finished");
|
||||
|
||||
services
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortroomid(room_id)
|
||||
.await;
|
||||
@@ -375,7 +277,6 @@ async fn knock_room_helper_local(
|
||||
|
||||
info!("Updating membership locally to knock state with provided stripped state events");
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.update_membership(
|
||||
room_id,
|
||||
@@ -384,7 +285,13 @@ async fn knock_room_helper_local(
|
||||
.get_content::<RoomMemberEventContent>()
|
||||
.expect("we just created this"),
|
||||
sender_user,
|
||||
Some(send_knock_response.knock_room_state),
|
||||
Some(
|
||||
send_knock_response
|
||||
.knock_room_state
|
||||
.into_iter()
|
||||
.filter_map(|s| extract_variant!(s, RawStrippedState::Stripped))
|
||||
.collect(),
|
||||
),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
@@ -392,7 +299,6 @@ async fn knock_room_helper_local(
|
||||
|
||||
info!("Appending room knock event locally");
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.append_pdu(
|
||||
&parsed_knock_pdu,
|
||||
@@ -477,12 +383,12 @@ async fn knock_room_helper_remote(
|
||||
let knock_event = knock_event_stub;
|
||||
|
||||
info!("Asking {remote_server} for send_knock in room {room_id}");
|
||||
let send_knock_request = federation::knock::send_knock::v1::Request {
|
||||
let send_knock_request = federation::membership::create_knock_event::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id: event_id.clone(),
|
||||
pdu: services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(knock_event.clone())
|
||||
.federation
|
||||
.format_pdu_into(knock_event.clone(), Some(&room_version_id))
|
||||
.await,
|
||||
};
|
||||
|
||||
@@ -494,7 +400,6 @@ async fn knock_room_helper_remote(
|
||||
info!("send_knock finished");
|
||||
|
||||
services
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortroomid(room_id)
|
||||
.await;
|
||||
@@ -507,7 +412,14 @@ async fn knock_room_helper_remote(
|
||||
let state = send_knock_response
|
||||
.knock_room_state
|
||||
.iter()
|
||||
.map(|event| serde_json::from_str::<CanonicalJsonObject>(event.clone().into_json().get()))
|
||||
.map(|event| {
|
||||
serde_json::from_str::<CanonicalJsonObject>(
|
||||
extract_variant!(event.clone(), RawStrippedState::Stripped)
|
||||
.expect("Raw<AnyStrippedStateEvent>")
|
||||
.json()
|
||||
.get(),
|
||||
)
|
||||
})
|
||||
.filter_map(Result::ok);
|
||||
|
||||
let mut state_map: HashMap<u64, OwnedEventId> = HashMap::new();
|
||||
@@ -534,13 +446,11 @@ async fn knock_room_helper_remote(
|
||||
|
||||
let event_id = gen_event_id(&event, &room_version_id)?;
|
||||
let shortstatekey = services
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortstatekey(&event_type, &state_key)
|
||||
.await;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.add_pdu_outlier(&event_id, &event);
|
||||
|
||||
@@ -549,7 +459,6 @@ async fn knock_room_helper_remote(
|
||||
|
||||
info!("Compressing state from send_knock");
|
||||
let compressed: CompressedState = services
|
||||
.rooms
|
||||
.state_compressor
|
||||
.compress_state_events(
|
||||
state_map
|
||||
@@ -565,27 +474,23 @@ async fn knock_room_helper_remote(
|
||||
added,
|
||||
removed,
|
||||
} = services
|
||||
.rooms
|
||||
.state_compressor
|
||||
.save_state(room_id, Arc::new(compressed))
|
||||
.await?;
|
||||
|
||||
debug!("Forcing state for new room");
|
||||
services
|
||||
.rooms
|
||||
.state
|
||||
.force_state(room_id, statehash_before_knock, added, removed, &state_lock)
|
||||
.await?;
|
||||
|
||||
let statehash_after_knock = services
|
||||
.rooms
|
||||
.state
|
||||
.append_to_state(&parsed_knock_pdu)
|
||||
.await?;
|
||||
|
||||
info!("Updating membership locally to knock state with provided stripped state events");
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.update_membership(
|
||||
room_id,
|
||||
@@ -594,7 +499,13 @@ async fn knock_room_helper_remote(
|
||||
.get_content::<RoomMemberEventContent>()
|
||||
.expect("we just created this"),
|
||||
sender_user,
|
||||
Some(send_knock_response.knock_room_state),
|
||||
Some(
|
||||
send_knock_response
|
||||
.knock_room_state
|
||||
.into_iter()
|
||||
.filter_map(|s| extract_variant!(s, RawStrippedState::Stripped))
|
||||
.collect(),
|
||||
),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
@@ -602,7 +513,6 @@ async fn knock_room_helper_remote(
|
||||
|
||||
info!("Appending room knock event locally");
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.append_pdu(
|
||||
&parsed_knock_pdu,
|
||||
@@ -616,7 +526,6 @@ async fn knock_room_helper_remote(
|
||||
// We set the room state after inserting the pdu, so that we never have a moment
|
||||
// in time where events in the current room state do not exist
|
||||
services
|
||||
.rooms
|
||||
.state
|
||||
.set_room_state(room_id, statehash_after_knock, &state_lock);
|
||||
|
||||
@@ -628,7 +537,7 @@ async fn make_knock_request(
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
servers: &[OwnedServerName],
|
||||
) -> Result<(federation::knock::create_knock_event_template::v1::Response, OwnedServerName)> {
|
||||
) -> Result<(federation::membership::prepare_knock_event::v1::Response, OwnedServerName)> {
|
||||
let mut make_knock_response_and_server =
|
||||
Err!(BadServerResponse("No server available to assist in knocking."));
|
||||
|
||||
@@ -645,7 +554,7 @@ async fn make_knock_request(
|
||||
.sending
|
||||
.send_federation_request(
|
||||
remote_server,
|
||||
federation::knock::create_knock_event_template::v1::Request {
|
||||
federation::membership::prepare_knock_event::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
user_id: sender_user.to_owned(),
|
||||
ver: services
|
||||
|
||||
@@ -1,25 +1,7 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use axum::extract::State;
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt, pin_mut};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, RoomVersionId, UserId,
|
||||
api::{
|
||||
client::membership::leave_room,
|
||||
federation::{self},
|
||||
},
|
||||
events::{
|
||||
StateEventType,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Result, debug_info, debug_warn, err,
|
||||
matrix::{event::gen_event_id, pdu::PduBuilder},
|
||||
utils::{self, FutureBoolExt, future::ReadyEqExt},
|
||||
warn,
|
||||
};
|
||||
use tuwunel_service::Services;
|
||||
use futures::FutureExt;
|
||||
use ruma::api::client::membership::leave_room;
|
||||
use tuwunel_core::Result;
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -32,362 +14,21 @@ pub(crate) async fn leave_room_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<leave_room::v3::Request>,
|
||||
) -> Result<leave_room::v3::Response> {
|
||||
leave_room(&services, body.sender_user(), &body.room_id, body.reason.clone())
|
||||
let state_lock = services.state.mutex.lock(&body.room_id).await;
|
||||
|
||||
services
|
||||
.membership
|
||||
.leave(body.sender_user(), &body.room_id, body.reason.clone(), false, &state_lock)
|
||||
.boxed()
|
||||
.await
|
||||
.map(|()| leave_room::v3::Response::new())
|
||||
}
|
||||
|
||||
// Make a user leave all their joined rooms, rescinds knocks, forgets all rooms,
|
||||
// and ignores errors
|
||||
pub async fn leave_all_rooms(services: &Services, user_id: &UserId) {
|
||||
let rooms_joined = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(user_id)
|
||||
.map(ToOwned::to_owned);
|
||||
|
||||
let rooms_invited = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_invited(user_id)
|
||||
.map(|(r, _)| r);
|
||||
|
||||
let rooms_knocked = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_knocked(user_id)
|
||||
.map(|(r, _)| r);
|
||||
|
||||
let all_rooms: Vec<_> = rooms_joined
|
||||
.chain(rooms_invited)
|
||||
.chain(rooms_knocked)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
for room_id in all_rooms {
|
||||
// ignore errors
|
||||
if let Err(e) = leave_room(services, user_id, &room_id, None)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
warn!(%user_id, "Failed to leave {room_id} remotely: {e}");
|
||||
}
|
||||
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.forget(&room_id, user_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn leave_room(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
reason: Option<String>,
|
||||
) -> Result {
|
||||
let default_member_content = RoomMemberEventContent {
|
||||
membership: MembershipState::Leave,
|
||||
reason: reason.clone(),
|
||||
join_authorized_via_users_server: None,
|
||||
is_direct: None,
|
||||
avatar_url: None,
|
||||
displayname: None,
|
||||
third_party_invite: None,
|
||||
blurhash: None,
|
||||
};
|
||||
|
||||
let is_banned = services.rooms.metadata.is_banned(room_id);
|
||||
let is_disabled = services.rooms.metadata.is_disabled(room_id);
|
||||
|
||||
pin_mut!(is_banned, is_disabled);
|
||||
if is_banned.or(is_disabled).await {
|
||||
// the room is banned/disabled, the room must be rejected locally since we
|
||||
// cant/dont want to federate with this server
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.update_membership(
|
||||
room_id,
|
||||
user_id,
|
||||
default_member_content,
|
||||
user_id,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let dont_have_room = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(services.globals.server_name(), room_id)
|
||||
.eq(&false);
|
||||
|
||||
let not_knocked = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_knocked(user_id, room_id)
|
||||
.eq(&false);
|
||||
|
||||
// Ask a remote server if we don't have this room and are not knocking on it
|
||||
if dont_have_room.and(not_knocked).await {
|
||||
if let Err(e) = remote_leave_room(services, user_id, room_id)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
warn!(%user_id, "Failed to leave room {room_id} remotely: {e}");
|
||||
// Don't tell the client about this error
|
||||
}
|
||||
|
||||
let last_state = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.invite_state(user_id, room_id)
|
||||
.or_else(|_| {
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.knock_state(user_id, room_id)
|
||||
})
|
||||
.or_else(|_| {
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.left_state(user_id, room_id)
|
||||
})
|
||||
.await
|
||||
.ok();
|
||||
|
||||
// We always drop the invite, we can't rely on other servers
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.update_membership(
|
||||
room_id,
|
||||
user_id,
|
||||
default_member_content,
|
||||
user_id,
|
||||
last_state,
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
|
||||
let Ok(event) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content::<RoomMemberEventContent>(
|
||||
room_id,
|
||||
&StateEventType::RoomMember,
|
||||
user_id.as_str(),
|
||||
)
|
||||
.await
|
||||
else {
|
||||
debug_warn!(
|
||||
"Trying to leave a room you are not a member of, marking room as left locally."
|
||||
);
|
||||
|
||||
return services
|
||||
.rooms
|
||||
.state_cache
|
||||
.update_membership(
|
||||
room_id,
|
||||
user_id,
|
||||
default_member_content,
|
||||
user_id,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
};
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
|
||||
membership: MembershipState::Leave,
|
||||
reason,
|
||||
join_authorized_via_users_server: None,
|
||||
is_direct: None,
|
||||
..event
|
||||
}),
|
||||
user_id,
|
||||
room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remote_leave_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result {
|
||||
let mut make_leave_response_and_server =
|
||||
Err!(BadServerResponse("No remote server available to assist in leaving {room_id}."));
|
||||
|
||||
let mut servers: HashSet<OwnedServerName> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.servers_invite_via(room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
match services
|
||||
.rooms
|
||||
.state_cache
|
||||
.invite_state(user_id, room_id)
|
||||
.await
|
||||
{
|
||||
| Ok(invite_state) => {
|
||||
servers.extend(
|
||||
invite_state
|
||||
.iter()
|
||||
.filter_map(|event| event.get_field("sender").ok().flatten())
|
||||
.filter_map(|sender: &str| UserId::parse(sender).ok())
|
||||
.map(|user| user.server_name().to_owned()),
|
||||
);
|
||||
},
|
||||
| _ => {
|
||||
match services
|
||||
.rooms
|
||||
.state_cache
|
||||
.knock_state(user_id, room_id)
|
||||
.await
|
||||
{
|
||||
| Ok(knock_state) => {
|
||||
servers.extend(
|
||||
knock_state
|
||||
.iter()
|
||||
.filter_map(|event| event.get_field("sender").ok().flatten())
|
||||
.filter_map(|sender: &str| UserId::parse(sender).ok())
|
||||
.filter_map(|sender| {
|
||||
if !services.globals.user_is_local(sender) {
|
||||
Some(sender.server_name().to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
);
|
||||
},
|
||||
| _ => {},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if let Some(room_id_server_name) = room_id.server_name() {
|
||||
servers.insert(room_id_server_name.to_owned());
|
||||
}
|
||||
|
||||
debug_info!("servers in remote_leave_room: {servers:?}");
|
||||
|
||||
for remote_server in servers {
|
||||
let make_leave_response = services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
&remote_server,
|
||||
federation::membership::prepare_leave_event::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
user_id: user_id.to_owned(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
make_leave_response_and_server = make_leave_response.map(|r| (r, remote_server));
|
||||
|
||||
if make_leave_response_and_server.is_ok() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let (make_leave_response, remote_server) = make_leave_response_and_server?;
|
||||
|
||||
let Some(room_version_id) = make_leave_response.room_version else {
|
||||
return Err!(BadServerResponse(warn!(
|
||||
"No room version was returned by {remote_server} for {room_id}, room version is \
|
||||
likely not supported by tuwunel"
|
||||
)));
|
||||
};
|
||||
|
||||
if !services
|
||||
.server
|
||||
.supported_room_version(&room_version_id)
|
||||
{
|
||||
return Err!(BadServerResponse(warn!(
|
||||
"Remote room version {room_version_id} for {room_id} is not supported by conduwuit",
|
||||
)));
|
||||
}
|
||||
|
||||
let mut leave_event_stub = serde_json::from_str::<CanonicalJsonObject>(
|
||||
make_leave_response.event.get(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
err!(BadServerResponse(warn!(
|
||||
"Invalid make_leave event json received from {remote_server} for {room_id}: {e:?}"
|
||||
)))
|
||||
})?;
|
||||
|
||||
// TODO: Is origin needed?
|
||||
leave_event_stub.insert(
|
||||
"origin".to_owned(),
|
||||
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
|
||||
);
|
||||
leave_event_stub.insert(
|
||||
"origin_server_ts".to_owned(),
|
||||
CanonicalJsonValue::Integer(
|
||||
utils::millis_since_unix_epoch()
|
||||
.try_into()
|
||||
.expect("Timestamp is valid js_int value"),
|
||||
),
|
||||
);
|
||||
|
||||
// room v3 and above removed the "event_id" field from remote PDU format
|
||||
match room_version_id {
|
||||
| RoomVersionId::V1 | RoomVersionId::V2 => {},
|
||||
| _ => {
|
||||
leave_event_stub.remove("event_id");
|
||||
},
|
||||
}
|
||||
|
||||
// In order to create a compatible ref hash (EventID) the `hashes` field needs
|
||||
// to be present
|
||||
services
|
||||
.server_keys
|
||||
.hash_and_sign_event(&mut leave_event_stub, &room_version_id)?;
|
||||
|
||||
// Generate event id
|
||||
let event_id = gen_event_id(&leave_event_stub, &room_version_id)?;
|
||||
|
||||
// Add event_id back
|
||||
leave_event_stub
|
||||
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
|
||||
|
||||
// It has enough fields to be called a proper event now
|
||||
let leave_event = leave_event_stub;
|
||||
|
||||
services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
&remote_server,
|
||||
federation::membership::create_leave_event::v2::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id,
|
||||
pdu: services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(leave_event.clone())
|
||||
.await,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
if services.config.delete_rooms_after_leave {
|
||||
services
|
||||
.delete
|
||||
.delete_if_empty_local(&body.room_id, state_lock)
|
||||
.boxed()
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(leave_room::v3::Response {})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use axum::extract::State;
|
||||
use futures::{FutureExt, StreamExt, future::join};
|
||||
use futures::{FutureExt, StreamExt, pin_mut};
|
||||
use ruma::{
|
||||
api::client::membership::{
|
||||
get_member_events::{self, v3::MembershipEventFilter},
|
||||
@@ -7,15 +7,18 @@
|
||||
},
|
||||
events::{
|
||||
StateEventType,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
room::{
|
||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Result, at,
|
||||
matrix::Event,
|
||||
utils::{
|
||||
future::TryExtExt,
|
||||
stream::{BroadbandExt, ReadyExt},
|
||||
future::{BoolExt, TryExtExt},
|
||||
stream::ReadyExt,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -31,22 +34,20 @@ pub(crate) async fn get_member_events_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_member_events::v3::Request>,
|
||||
) -> Result<get_member_events::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
let membership = body.membership.as_ref();
|
||||
let not_membership = body.not_membership.as_ref();
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(sender_user, &body.room_id)
|
||||
.user_can_see_state_events(body.sender_user(), &body.room_id)
|
||||
.await
|
||||
{
|
||||
return Err!(Request(Forbidden("You don't have permission to view this room.")));
|
||||
return Err!(Request(Forbidden(
|
||||
"You aren't a member of the room and weren't previously a member of the room."
|
||||
)));
|
||||
}
|
||||
|
||||
let membership = body.membership.as_ref();
|
||||
let not_membership = body.not_membership.as_ref();
|
||||
Ok(get_member_events::v3::Response {
|
||||
chunk: services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_full(&body.room_id)
|
||||
.ready_filter_map(Result::ok)
|
||||
@@ -70,31 +71,44 @@ pub(crate) async fn joined_members_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<joined_members::v3::Request>,
|
||||
) -> Result<joined_members::v3::Response> {
|
||||
if !services
|
||||
.rooms
|
||||
let is_joined = services
|
||||
.state_cache
|
||||
.is_joined(body.sender_user(), &body.room_id);
|
||||
|
||||
let is_world_readable = services
|
||||
.state_accessor
|
||||
.user_can_see_state_events(body.sender_user(), &body.room_id)
|
||||
.await
|
||||
{
|
||||
return Err!(Request(Forbidden("You don't have permission to view this room.")));
|
||||
.room_state_get_content(&body.room_id, &StateEventType::RoomHistoryVisibility, "")
|
||||
.map_ok_or(false, |c: RoomHistoryVisibilityEventContent| {
|
||||
c.history_visibility == HistoryVisibility::WorldReadable
|
||||
});
|
||||
|
||||
pin_mut!(is_joined, is_world_readable);
|
||||
if !is_joined.or(is_world_readable).await {
|
||||
return Err!(Request(Forbidden("You aren't a member of the room.")));
|
||||
}
|
||||
|
||||
Ok(joined_members::v3::Response {
|
||||
joined: services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&body.room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.broad_then(async |user_id| {
|
||||
let (display_name, avatar_url) = join(
|
||||
services.users.displayname(&user_id).ok(),
|
||||
services.users.avatar_url(&user_id).ok(),
|
||||
)
|
||||
.await;
|
||||
.state_accessor
|
||||
.room_state_full(&body.room_id)
|
||||
.ready_filter_map(Result::ok)
|
||||
.ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember)
|
||||
.map(at!(1))
|
||||
.ready_filter_map(|pdu| {
|
||||
membership_filter(pdu, Some(&MembershipEventFilter::Join), None)
|
||||
})
|
||||
.ready_filter_map(|pdu| {
|
||||
let content = pdu.get_content::<RoomMemberEventContent>().ok()?;
|
||||
let sender = pdu.sender().to_owned();
|
||||
let member = RoomMember {
|
||||
display_name: content.displayname,
|
||||
avatar_url: content.avatar_url,
|
||||
};
|
||||
|
||||
(user_id, RoomMember { display_name, avatar_url })
|
||||
Some((sender, member))
|
||||
})
|
||||
.collect()
|
||||
.boxed()
|
||||
.await,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,18 +8,21 @@
|
||||
mod members;
|
||||
mod unban;
|
||||
|
||||
use std::net::IpAddr;
|
||||
use std::{cmp::Ordering, net::IpAddr};
|
||||
|
||||
use axum::extract::State;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{OwnedRoomId, RoomId, ServerName, UserId, api::client::membership::joined_rooms};
|
||||
use tuwunel_core::{Err, Result, warn};
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedServerName, RoomId, RoomOrAliasId, ServerName, UserId,
|
||||
api::client::membership::joined_rooms,
|
||||
};
|
||||
use tuwunel_core::{Err, Result, result::LogErr, utils::shuffle, warn};
|
||||
use tuwunel_service::Services;
|
||||
|
||||
pub(crate) use self::{
|
||||
ban::ban_user_route,
|
||||
forget::forget_room_route,
|
||||
invite::{invite_helper, invite_user_route},
|
||||
invite::invite_user_route,
|
||||
join::{join_room_by_id_or_alias_route, join_room_by_id_route},
|
||||
kick::kick_user_route,
|
||||
knock::knock_room_route,
|
||||
@@ -27,11 +30,7 @@
|
||||
members::{get_member_events_route, joined_members_route},
|
||||
unban::unban_user_route,
|
||||
};
|
||||
pub use self::{
|
||||
join::join_room_by_id_helper,
|
||||
leave::{leave_all_rooms, leave_room},
|
||||
};
|
||||
use crate::{Ruma, client::full_user_deactivate};
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `POST /_matrix/client/r0/joined_rooms`
|
||||
///
|
||||
@@ -42,7 +41,6 @@ pub(crate) async fn joined_rooms_route(
|
||||
) -> Result<joined_rooms::v3::Response> {
|
||||
Ok(joined_rooms::v3::Response {
|
||||
joined_rooms: services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(body.sender_user())
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -68,53 +66,28 @@ pub(crate) async fn banned_room_check(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// TODO: weird condition
|
||||
if let Some(room_id) = room_id {
|
||||
if services.rooms.metadata.is_banned(room_id).await
|
||||
|| services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(
|
||||
room_id
|
||||
.server_name()
|
||||
.expect("legacy room mxid")
|
||||
.host(),
|
||||
) {
|
||||
if services.metadata.is_banned(room_id).await
|
||||
|| (room_id.server_name().is_some()
|
||||
&& services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(
|
||||
room_id
|
||||
.server_name()
|
||||
.expect("legacy room mxid")
|
||||
.host(),
|
||||
)) {
|
||||
warn!(
|
||||
"User {user_id} who is not an admin attempted to send an invite for or \
|
||||
attempted to join a banned room or banned room server name: {room_id}"
|
||||
);
|
||||
|
||||
if services
|
||||
.server
|
||||
.config
|
||||
.auto_deactivate_banned_room_attempts
|
||||
{
|
||||
warn!(
|
||||
"Automatically deactivating user {user_id} due to attempted banned room join"
|
||||
);
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_text(&format!(
|
||||
"Automatically deactivating user {user_id} due to attempted banned \
|
||||
room join from IP {client_ip}"
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(user_id)
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
full_user_deactivate(services, user_id, &all_joined_rooms)
|
||||
.boxed()
|
||||
.await?;
|
||||
}
|
||||
maybe_deactivate(services, user_id, client_ip)
|
||||
.await
|
||||
.log_err()
|
||||
.ok();
|
||||
|
||||
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
||||
}
|
||||
@@ -129,37 +102,10 @@ pub(crate) async fn banned_room_check(
|
||||
name {server_name} that is globally forbidden. Rejecting.",
|
||||
);
|
||||
|
||||
if services
|
||||
.server
|
||||
.config
|
||||
.auto_deactivate_banned_room_attempts
|
||||
{
|
||||
warn!(
|
||||
"Automatically deactivating user {user_id} due to attempted banned room join"
|
||||
);
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_text(&format!(
|
||||
"Automatically deactivating user {user_id} due to attempted banned \
|
||||
room join from IP {client_ip}"
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(user_id)
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
full_user_deactivate(services, user_id, &all_joined_rooms)
|
||||
.boxed()
|
||||
.await?;
|
||||
}
|
||||
maybe_deactivate(services, user_id, client_ip)
|
||||
.await
|
||||
.log_err()
|
||||
.ok();
|
||||
|
||||
return Err!(Request(Forbidden("This remote server is banned on this homeserver.")));
|
||||
}
|
||||
@@ -167,3 +113,119 @@ pub(crate) async fn banned_room_check(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn maybe_deactivate(services: &Services, user_id: &UserId, client_ip: IpAddr) -> Result {
|
||||
if services
|
||||
.server
|
||||
.config
|
||||
.auto_deactivate_banned_room_attempts
|
||||
{
|
||||
warn!("Automatically deactivating user {user_id} due to attempted banned room join");
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_text(&format!(
|
||||
"Automatically deactivating user {user_id} due to attempted banned room \
|
||||
join from IP {client_ip}"
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
services
|
||||
.deactivate
|
||||
.full_deactivate(user_id)
|
||||
.boxed()
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: should this be in services? banned check would have to resolve again if
|
||||
// room_id is not available at callsite
|
||||
async fn get_join_params(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
room_id_or_alias: &RoomOrAliasId,
|
||||
via: &[OwnedServerName],
|
||||
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
|
||||
// servers tried first, additional_servers shuffled then tried after
|
||||
let (room_id, mut servers, mut additional_servers) =
|
||||
match OwnedRoomId::try_from(room_id_or_alias.to_owned()) {
|
||||
// if room id, shuffle via + room_id server_name ...
|
||||
| Ok(room_id) => {
|
||||
let mut additional_servers = via.to_vec();
|
||||
|
||||
if let Some(server) = room_id.server_name() {
|
||||
additional_servers.push(server.to_owned());
|
||||
}
|
||||
|
||||
(room_id, Vec::new(), additional_servers)
|
||||
},
|
||||
// ... if room alias, resolve and don't shuffle ...
|
||||
| Err(room_alias) => {
|
||||
let (room_id, servers) = services
|
||||
.alias
|
||||
.resolve_alias(&room_alias, Some(via.to_vec()))
|
||||
.await?;
|
||||
|
||||
(room_id, servers, Vec::new())
|
||||
},
|
||||
};
|
||||
|
||||
// either way, add invited vias
|
||||
additional_servers.extend(
|
||||
services
|
||||
.state_cache
|
||||
.servers_invite_via(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
.await,
|
||||
);
|
||||
|
||||
// either way, add invite senders' servers
|
||||
additional_servers.extend(
|
||||
services
|
||||
.state_cache
|
||||
.invite_state(user_id, &room_id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter_map(|event| event.get_field("sender").ok().flatten())
|
||||
.filter_map(|sender: &str| UserId::parse(sender).ok())
|
||||
.map(|user| user.server_name().to_owned()),
|
||||
);
|
||||
|
||||
// shuffle additionals, append to base servers
|
||||
additional_servers.sort_unstable();
|
||||
additional_servers.dedup();
|
||||
shuffle(&mut additional_servers);
|
||||
servers.sort_unstable();
|
||||
servers.dedup();
|
||||
servers.append(&mut additional_servers);
|
||||
|
||||
// sort deprioritized servers last
|
||||
servers.sort_by(|a, b| {
|
||||
let a_matches = services
|
||||
.server
|
||||
.config
|
||||
.deprioritize_joins_through_servers
|
||||
.is_match(a.host());
|
||||
let b_matches = services
|
||||
.server
|
||||
.config
|
||||
.deprioritize_joins_through_servers
|
||||
.is_match(b.host());
|
||||
|
||||
if a_matches && !b_matches {
|
||||
Ordering::Greater
|
||||
} else if !a_matches && b_matches {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
});
|
||||
|
||||
Ok((room_id, servers))
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use axum::extract::State;
|
||||
use ruma::{
|
||||
api::client::membership::unban_user,
|
||||
events::room::member::{MembershipState, RoomMemberEventContent},
|
||||
};
|
||||
use tuwunel_core::{Err, Result, matrix::pdu::PduBuilder};
|
||||
use futures::FutureExt;
|
||||
use ruma::api::client::membership::unban_user;
|
||||
use tuwunel_core::Result;
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -14,43 +12,18 @@ pub(crate) async fn unban_user_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<unban_user::v3::Request>,
|
||||
) -> Result<unban_user::v3::Response> {
|
||||
let state_lock = services
|
||||
.rooms
|
||||
.state
|
||||
.mutex
|
||||
.lock(&body.room_id)
|
||||
.await;
|
||||
|
||||
let current_member_content = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(&body.room_id, &body.user_id)
|
||||
.await
|
||||
.unwrap_or_else(|_| RoomMemberEventContent::new(MembershipState::Leave));
|
||||
|
||||
if current_member_content.membership != MembershipState::Ban {
|
||||
return Err!(Request(Forbidden(
|
||||
"Cannot unban a user who is not banned (current membership: {})",
|
||||
current_member_content.membership
|
||||
)));
|
||||
}
|
||||
let state_lock = services.state.mutex.lock(&body.room_id).await;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
|
||||
membership: MembershipState::Leave,
|
||||
reason: body.reason.clone(),
|
||||
join_authorized_via_users_server: None,
|
||||
third_party_invite: None,
|
||||
is_direct: None,
|
||||
..current_member_content
|
||||
}),
|
||||
body.sender_user(),
|
||||
.membership
|
||||
.unban(
|
||||
&body.room_id,
|
||||
&body.user_id,
|
||||
body.reason.as_ref(),
|
||||
body.sender_user(),
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
@@ -73,7 +73,7 @@ pub(crate) async fn get_message_events_route(
|
||||
let room_id = &body.room_id;
|
||||
let filter = &body.filter;
|
||||
|
||||
if !services.rooms.metadata.exists(room_id).await {
|
||||
if !services.metadata.exists(room_id).await {
|
||||
return Err!(Request(Forbidden("Room does not exist to this server")));
|
||||
}
|
||||
|
||||
@@ -97,7 +97,6 @@ pub(crate) async fn get_message_events_route(
|
||||
|
||||
if matches!(body.dir, Direction::Backward) {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.backfill_if_required(room_id, from)
|
||||
.await
|
||||
@@ -107,14 +106,12 @@ pub(crate) async fn get_message_events_route(
|
||||
|
||||
let it = match body.dir {
|
||||
| Direction::Forward => services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus(Some(sender_user), room_id, Some(from))
|
||||
.ignore_err()
|
||||
.boxed(),
|
||||
|
||||
| Direction::Backward => services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(Some(sender_user), room_id, Some(from))
|
||||
.ignore_err()
|
||||
@@ -192,7 +189,7 @@ pub(crate) async fn lazy_loading_witness<'a, I>(
|
||||
.max()
|
||||
.unwrap_or_else(PduCount::max);
|
||||
|
||||
let receipts = services.rooms.read_receipt.readreceipts_since(
|
||||
let receipts = services.read_receipt.readreceipts_since(
|
||||
lazy_loading_context.room_id,
|
||||
oldest.into_unsigned(),
|
||||
Some(newest.into_unsigned()),
|
||||
@@ -213,7 +210,6 @@ pub(crate) async fn lazy_loading_witness<'a, I>(
|
||||
.await;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.lazy_loading
|
||||
.witness_retain(witness, lazy_loading_context)
|
||||
.await
|
||||
@@ -225,7 +221,6 @@ async fn get_member_event(
|
||||
user_id: &UserId,
|
||||
) -> Option<Raw<AnyStateEvent>> {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())
|
||||
.map_ok(Event::into_format)
|
||||
@@ -293,7 +288,6 @@ pub(crate) async fn visibility_filter(
|
||||
let (_, pdu) = &item;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(user_id, pdu.room_id(), pdu.event_id())
|
||||
.await
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
pub(super) mod voip;
|
||||
pub(super) mod well_known;
|
||||
|
||||
pub use account::full_user_deactivate;
|
||||
mod utils;
|
||||
|
||||
pub(super) use account::*;
|
||||
pub(super) use account_data::*;
|
||||
pub(super) use alias::*;
|
||||
@@ -55,12 +56,10 @@
|
||||
pub(super) use media::*;
|
||||
pub(super) use media_legacy::*;
|
||||
pub(super) use membership::*;
|
||||
pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room};
|
||||
pub(super) use message::*;
|
||||
pub(super) use openid::*;
|
||||
pub(super) use presence::*;
|
||||
pub(super) use profile::*;
|
||||
pub use profile::{update_all_rooms, update_avatar_url, update_displayname};
|
||||
pub(super) use push::*;
|
||||
pub(super) use read_marker::*;
|
||||
pub(super) use redact::*;
|
||||
|
||||
@@ -44,7 +44,6 @@ pub(crate) async fn get_presence_route(
|
||||
|
||||
let mut presence_event = None;
|
||||
let has_shared_rooms = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.user_sees_user(body.sender_user(), &body.user_id)
|
||||
.await;
|
||||
|
||||
+44
-156
@@ -2,27 +2,20 @@
|
||||
|
||||
use axum::extract::State;
|
||||
use futures::{
|
||||
StreamExt, TryStreamExt,
|
||||
future::{join, join3, join4},
|
||||
StreamExt,
|
||||
future::{join, join4},
|
||||
};
|
||||
use ruma::{
|
||||
OwnedMxcUri, OwnedRoomId, UserId,
|
||||
OwnedRoomId,
|
||||
api::{
|
||||
client::profile::{
|
||||
get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name,
|
||||
},
|
||||
federation,
|
||||
},
|
||||
events::room::member::{MembershipState, RoomMemberEventContent},
|
||||
presence::PresenceState,
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Result,
|
||||
matrix::pdu::PduBuilder,
|
||||
utils::{IterStream, future::TryExtExt, stream::TryIgnore},
|
||||
warn,
|
||||
};
|
||||
use tuwunel_service::Services;
|
||||
use tuwunel_core::{Err, Result, utils::future::TryExtExt};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -42,14 +35,15 @@ pub(crate) async fn set_displayname_route(
|
||||
}
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
update_displayname(&services, &body.user_id, body.displayname.clone(), &all_joined_rooms)
|
||||
services
|
||||
.users
|
||||
.update_displayname(&body.user_id, body.displayname.clone(), &all_joined_rooms)
|
||||
.await;
|
||||
|
||||
if services.config.allow_local_presence {
|
||||
@@ -138,21 +132,21 @@ pub(crate) async fn set_avatar_url_route(
|
||||
}
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
update_avatar_url(
|
||||
&services,
|
||||
&body.user_id,
|
||||
body.avatar_url.clone(),
|
||||
body.blurhash.clone(),
|
||||
&all_joined_rooms,
|
||||
)
|
||||
.await;
|
||||
services
|
||||
.users
|
||||
.update_avatar_url(
|
||||
&body.user_id,
|
||||
body.avatar_url.clone(),
|
||||
body.blurhash.clone(),
|
||||
&all_joined_rooms,
|
||||
)
|
||||
.await;
|
||||
|
||||
if services.config.allow_local_presence {
|
||||
// Presence update
|
||||
@@ -279,13 +273,20 @@ pub(crate) async fn get_profile_route(
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(get_profile::v3::Response {
|
||||
displayname: response.displayname,
|
||||
avatar_url: response.avatar_url,
|
||||
blurhash: response.blurhash,
|
||||
tz: response.tz,
|
||||
custom_profile_fields: response.custom_profile_fields,
|
||||
});
|
||||
let canonical_fields = [
|
||||
("avatar_url", response.avatar_url.map(Into::into)),
|
||||
("blurhash", response.blurhash),
|
||||
("displayname", response.displayname),
|
||||
("tz", response.tz),
|
||||
];
|
||||
|
||||
let response = canonical_fields
|
||||
.into_iter()
|
||||
.filter_map(|(key, val)| val.map(|val| (key, val)))
|
||||
.map(|(key, val)| (key.to_owned(), val.into()))
|
||||
.chain(response.custom_profile_fields.into_iter());
|
||||
|
||||
return Ok(response.collect::<get_profile::v3::Response>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,131 +314,18 @@ pub(crate) async fn get_profile_route(
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(get_profile::v3::Response {
|
||||
avatar_url,
|
||||
blurhash,
|
||||
displayname,
|
||||
tz,
|
||||
custom_profile_fields,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn update_displayname(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
displayname: Option<String>,
|
||||
all_joined_rooms: &[OwnedRoomId],
|
||||
) {
|
||||
let (current_avatar_url, current_blurhash, current_displayname) = join3(
|
||||
services.users.avatar_url(user_id).ok(),
|
||||
services.users.blurhash(user_id).ok(),
|
||||
services.users.displayname(user_id).ok(),
|
||||
)
|
||||
.await;
|
||||
|
||||
if displayname == current_displayname {
|
||||
return;
|
||||
}
|
||||
|
||||
services
|
||||
.users
|
||||
.set_displayname(user_id, displayname.clone());
|
||||
|
||||
// Send a new join membership event into all joined rooms
|
||||
let avatar_url = ¤t_avatar_url;
|
||||
let blurhash = ¤t_blurhash;
|
||||
let displayname = &displayname;
|
||||
let all_joined_rooms: Vec<_> = all_joined_rooms
|
||||
.iter()
|
||||
.try_stream()
|
||||
.and_then(async |room_id: &OwnedRoomId| {
|
||||
let pdu = PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
|
||||
displayname: displayname.clone(),
|
||||
membership: MembershipState::Join,
|
||||
avatar_url: avatar_url.clone(),
|
||||
blurhash: blurhash.clone(),
|
||||
join_authorized_via_users_server: None,
|
||||
reason: None,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
});
|
||||
|
||||
Ok((pdu, room_id))
|
||||
})
|
||||
.ignore_err()
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
update_all_rooms(services, all_joined_rooms, user_id).await;
|
||||
}
|
||||
|
||||
pub async fn update_avatar_url(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
avatar_url: Option<OwnedMxcUri>,
|
||||
blurhash: Option<String>,
|
||||
all_joined_rooms: &[OwnedRoomId],
|
||||
) {
|
||||
let (current_avatar_url, current_blurhash, current_displayname) = join3(
|
||||
services.users.avatar_url(user_id).ok(),
|
||||
services.users.blurhash(user_id).ok(),
|
||||
services.users.displayname(user_id).ok(),
|
||||
)
|
||||
.await;
|
||||
|
||||
if current_avatar_url == avatar_url && current_blurhash == blurhash {
|
||||
return;
|
||||
}
|
||||
|
||||
services
|
||||
.users
|
||||
.set_avatar_url(user_id, avatar_url.clone());
|
||||
services
|
||||
.users
|
||||
.set_blurhash(user_id, blurhash.clone());
|
||||
|
||||
// Send a new join membership event into all joined rooms
|
||||
let avatar_url = &avatar_url;
|
||||
let blurhash = &blurhash;
|
||||
let displayname = ¤t_displayname;
|
||||
let all_joined_rooms: Vec<_> = all_joined_rooms
|
||||
.iter()
|
||||
.try_stream()
|
||||
.and_then(async |room_id: &OwnedRoomId| {
|
||||
let pdu = PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
|
||||
avatar_url: avatar_url.clone(),
|
||||
blurhash: blurhash.clone(),
|
||||
membership: MembershipState::Join,
|
||||
displayname: displayname.clone(),
|
||||
join_authorized_via_users_server: None,
|
||||
reason: None,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
});
|
||||
|
||||
Ok((pdu, room_id))
|
||||
})
|
||||
.ignore_err()
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
update_all_rooms(services, all_joined_rooms, user_id).await;
|
||||
}
|
||||
|
||||
pub async fn update_all_rooms(
|
||||
services: &Services,
|
||||
all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>,
|
||||
user_id: &UserId,
|
||||
) {
|
||||
for (pdu_builder, room_id) in all_joined_rooms {
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
if let Err(e) = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(pdu_builder, user_id, room_id, &state_lock)
|
||||
.await
|
||||
{
|
||||
warn!(%user_id, %room_id, "Failed to update/send new profile join membership update in room: {e}");
|
||||
}
|
||||
}
|
||||
let canonical_fields = [
|
||||
("avatar_url", avatar_url.map(Into::into)),
|
||||
("blurhash", blurhash),
|
||||
("displayname", displayname),
|
||||
("tz", tz),
|
||||
];
|
||||
|
||||
let response = canonical_fields
|
||||
.into_iter()
|
||||
.filter_map(|(key, val)| val.map(|val| (key, val)))
|
||||
.map(|(key, val)| (key.to_owned(), val.into()))
|
||||
.chain(custom_profile_fields.into_iter());
|
||||
|
||||
Ok(response.collect::<get_profile::v3::Response>())
|
||||
}
|
||||
|
||||
@@ -44,20 +44,18 @@ pub(crate) async fn set_read_marker_route(
|
||||
|
||||
if body.private_read_receipt.is_some() || body.read_receipt.is_some() {
|
||||
services
|
||||
.rooms
|
||||
.user
|
||||
.reset_notification_counts(sender_user, &body.room_id);
|
||||
}
|
||||
|
||||
// ping presence
|
||||
if services.config.allow_local_presence {
|
||||
services
|
||||
.presence
|
||||
.ping_presence(sender_user, &ruma::presence::PresenceState::Online)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(event) = &body.read_receipt {
|
||||
if services.config.allow_local_presence {
|
||||
services
|
||||
.presence
|
||||
.ping_presence(sender_user, &ruma::presence::PresenceState::Online)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let receipt_content = BTreeMap::from_iter([(
|
||||
event.to_owned(),
|
||||
BTreeMap::from_iter([(
|
||||
@@ -70,7 +68,6 @@ pub(crate) async fn set_read_marker_route(
|
||||
)]);
|
||||
|
||||
services
|
||||
.rooms
|
||||
.read_receipt
|
||||
.readreceipt_update(
|
||||
sender_user,
|
||||
@@ -85,7 +82,6 @@ pub(crate) async fn set_read_marker_route(
|
||||
|
||||
if let Some(event) = &body.private_read_receipt {
|
||||
let count = services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_count(event)
|
||||
.await
|
||||
@@ -98,7 +94,6 @@ pub(crate) async fn set_read_marker_route(
|
||||
};
|
||||
|
||||
services
|
||||
.rooms
|
||||
.read_receipt
|
||||
.private_read_set(&body.room_id, sender_user, count);
|
||||
}
|
||||
@@ -120,19 +115,10 @@ pub(crate) async fn create_receipt_route(
|
||||
create_receipt::v3::ReceiptType::Read | create_receipt::v3::ReceiptType::ReadPrivate
|
||||
) {
|
||||
services
|
||||
.rooms
|
||||
.user
|
||||
.reset_notification_counts(sender_user, &body.room_id);
|
||||
}
|
||||
|
||||
// ping presence
|
||||
if services.config.allow_local_presence {
|
||||
services
|
||||
.presence
|
||||
.ping_presence(sender_user, &ruma::presence::PresenceState::Online)
|
||||
.await?;
|
||||
}
|
||||
|
||||
match body.receipt_type {
|
||||
| create_receipt::v3::ReceiptType::FullyRead => {
|
||||
let fully_read_event = ruma::events::fully_read::FullyReadEvent {
|
||||
@@ -151,6 +137,13 @@ pub(crate) async fn create_receipt_route(
|
||||
.await?;
|
||||
},
|
||||
| create_receipt::v3::ReceiptType::Read => {
|
||||
if services.config.allow_local_presence {
|
||||
services
|
||||
.presence
|
||||
.ping_presence(sender_user, &ruma::presence::PresenceState::Online)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let receipt_content = BTreeMap::from_iter([(
|
||||
body.event_id.clone(),
|
||||
BTreeMap::from_iter([(
|
||||
@@ -166,7 +159,6 @@ pub(crate) async fn create_receipt_route(
|
||||
)]);
|
||||
|
||||
services
|
||||
.rooms
|
||||
.read_receipt
|
||||
.readreceipt_update(
|
||||
sender_user,
|
||||
@@ -180,7 +172,6 @@ pub(crate) async fn create_receipt_route(
|
||||
},
|
||||
| create_receipt::v3::ReceiptType::ReadPrivate => {
|
||||
let count = services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_count(&body.event_id)
|
||||
.await
|
||||
@@ -193,7 +184,6 @@ pub(crate) async fn create_receipt_route(
|
||||
};
|
||||
|
||||
services
|
||||
.rooms
|
||||
.read_receipt
|
||||
.private_read_set(&body.room_id, sender_user, count);
|
||||
},
|
||||
|
||||
@@ -18,15 +18,9 @@ pub(crate) async fn redact_event_route(
|
||||
let sender_user = body.sender_user();
|
||||
let body = &body.body;
|
||||
|
||||
let state_lock = services
|
||||
.rooms
|
||||
.state
|
||||
.mutex
|
||||
.lock(&body.room_id)
|
||||
.await;
|
||||
let state_lock = services.state.mutex.lock(&body.room_id).await;
|
||||
|
||||
let event_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
|
||||
+25
-18
@@ -19,7 +19,7 @@
|
||||
use tuwunel_core::{Err, Error, Result, debug_info, error, info, is_equal_to, utils, warn};
|
||||
use tuwunel_service::users::device::generate_refresh_token;
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, join_room_by_id_helper};
|
||||
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH};
|
||||
use crate::Ruma;
|
||||
|
||||
const RANDOM_USER_ID_LENGTH: usize = 10;
|
||||
@@ -316,7 +316,7 @@ pub(crate) async fn register_route(
|
||||
stages: vec![AuthType::RegistrationToken],
|
||||
}],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
params: Default::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
@@ -326,7 +326,7 @@ pub(crate) async fn register_route(
|
||||
uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
params: Default::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
@@ -517,13 +517,16 @@ pub(crate) async fn register_route(
|
||||
&& services.config.grant_admin_to_first_user
|
||||
&& let Ok(admin_room) = services.admin.get_admin_room().await
|
||||
&& services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_joined_count(&admin_room)
|
||||
.await
|
||||
.is_ok_and(is_equal_to!(1))
|
||||
{
|
||||
services.admin.make_user_admin(&user_id).await?;
|
||||
services
|
||||
.admin
|
||||
.make_user_admin(&user_id)
|
||||
.boxed()
|
||||
.await?;
|
||||
warn!("Granting {user_id} admin privileges as the first user");
|
||||
}
|
||||
|
||||
@@ -532,7 +535,7 @@ pub(crate) async fn register_route(
|
||||
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
|
||||
{
|
||||
for room in &services.server.config.auto_join_rooms {
|
||||
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
|
||||
let Ok(room_id) = services.alias.resolve(room).await else {
|
||||
error!(
|
||||
"Failed to resolve room alias to room ID when attempting to auto join \
|
||||
{room}, skipping"
|
||||
@@ -541,7 +544,6 @@ pub(crate) async fn register_route(
|
||||
};
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(services.globals.server_name(), &room_id)
|
||||
.await
|
||||
@@ -553,17 +555,20 @@ pub(crate) async fn register_route(
|
||||
}
|
||||
|
||||
if let Some(room_server_name) = room.server_name() {
|
||||
match join_room_by_id_helper(
|
||||
&services,
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
||||
None,
|
||||
&body.appservice_info,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
let state_lock = services.state.mutex.lock(&room_id).await;
|
||||
|
||||
match services
|
||||
.membership
|
||||
.join(
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
||||
&body.appservice_info,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
| Err(e) => {
|
||||
// don't return this error so we don't fail registrations
|
||||
@@ -575,6 +580,8 @@ pub(crate) async fn register_route(
|
||||
info!("Automatically joined room {room} for user {user_id}");
|
||||
},
|
||||
}
|
||||
|
||||
drop(state_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,6 @@ async fn paginate_relations_with_filter(
|
||||
let depth: u8 = if recurse { 3 } else { 1 };
|
||||
|
||||
let events: Vec<_> = services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.get_relations(sender_user, room_id, target, start, limit, depth, dir)
|
||||
.await
|
||||
@@ -183,7 +182,6 @@ async fn visibility_filter<Pdu: Event>(
|
||||
let (_, pdu) = &item;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, pdu.room_id(), pdu.event_id())
|
||||
.await
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
const REASON_MAX_LEN: usize = 750;
|
||||
|
||||
/// # `POST /_matrix/client/v3/rooms/{roomId}/report`
|
||||
///
|
||||
/// Reports an abusive room to homeserver admins
|
||||
@@ -29,24 +31,18 @@ pub(crate) async fn report_room_route(
|
||||
|
||||
info!(
|
||||
"Received room report by user {sender_user} for room {} with reason: \"{}\"",
|
||||
body.room_id,
|
||||
body.reason.as_deref().unwrap_or("")
|
||||
body.room_id, body.reason,
|
||||
);
|
||||
|
||||
if body
|
||||
.reason
|
||||
.as_ref()
|
||||
.is_some_and(|s| s.len() > 750)
|
||||
{
|
||||
return Err!(Request(
|
||||
InvalidParam("Reason too long, should be 750 characters or fewer",)
|
||||
));
|
||||
if body.reason.len().gt(&REASON_MAX_LEN) {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Reason too long, should be {REASON_MAX_LEN} characters or fewer"
|
||||
)));
|
||||
}
|
||||
|
||||
delay_response().await;
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&services.server.name, &body.room_id)
|
||||
.await
|
||||
@@ -64,7 +60,7 @@ pub(crate) async fn report_room_route(
|
||||
"@room Room report received from {} -\n\nRoom ID: {}\n\nReport Reason: {}",
|
||||
sender_user.to_owned(),
|
||||
body.room_id,
|
||||
body.reason.as_deref().unwrap_or("")
|
||||
body.reason,
|
||||
)))
|
||||
.await
|
||||
.ok();
|
||||
@@ -95,12 +91,7 @@ pub(crate) async fn report_event_route(
|
||||
delay_response().await;
|
||||
|
||||
// check if we know about the reported event ID or if it's invalid
|
||||
let Ok(pdu) = services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu(&body.event_id)
|
||||
.await
|
||||
else {
|
||||
let Ok(pdu) = services.timeline.get_pdu(&body.event_id).await else {
|
||||
return Err!(Request(NotFound("Event ID is not known to us or Event ID is invalid")));
|
||||
};
|
||||
|
||||
@@ -170,7 +161,6 @@ async fn is_event_report_valid(
|
||||
}
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(room_id)
|
||||
.ready_any(|user_id| user_id == sender_user)
|
||||
|
||||
@@ -18,7 +18,6 @@ pub(crate) async fn get_room_aliases_route(
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(sender_user, &body.room_id)
|
||||
.await
|
||||
@@ -28,7 +27,6 @@ pub(crate) async fn get_room_aliases_route(
|
||||
|
||||
Ok(aliases::v3::Response {
|
||||
aliases: services
|
||||
.rooms
|
||||
.alias
|
||||
.local_aliases_for_room(&body.room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
|
||||
+383
-204
@@ -1,10 +1,13 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use futures::FutureExt;
|
||||
use futures::{FutureExt, future::OptionFuture};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId,
|
||||
api::client::room::{self, create_room},
|
||||
api::client::room::{
|
||||
self, create_room,
|
||||
create_room::v3::{CreationContent, RoomPreset},
|
||||
},
|
||||
events::{
|
||||
TimelineEventType,
|
||||
room::{
|
||||
@@ -16,21 +19,23 @@
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
name::RoomNameEventContent,
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
topic::RoomTopicEventContent,
|
||||
topic::{RoomTopicEventContent, TopicContentBlock},
|
||||
},
|
||||
},
|
||||
int,
|
||||
room_version_rules::{RoomIdFormatVersion, RoomVersionRules},
|
||||
serde::{JsonObject, Raw},
|
||||
};
|
||||
use serde_json::{json, value::to_raw_value};
|
||||
use tuwunel_core::{
|
||||
Err, Result, debug_info, debug_warn, err, info,
|
||||
matrix::{StateKey, pdu::PduBuilder},
|
||||
matrix::{StateKey, pdu::PduBuilder, room_version},
|
||||
utils::BoolExt,
|
||||
warn,
|
||||
};
|
||||
use tuwunel_service::{Services, appservice::RegistrationInfo};
|
||||
use tuwunel_service::{Services, appservice::RegistrationInfo, rooms::state::RoomMutexGuard};
|
||||
|
||||
use crate::{Ruma, client::invite_helper};
|
||||
use crate::{Ruma, client::utils::invite_check};
|
||||
|
||||
/// # `POST /_matrix/client/v3/createRoom`
|
||||
///
|
||||
@@ -53,166 +58,62 @@ pub(crate) async fn create_room_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<create_room::v3::Request>,
|
||||
) -> Result<create_room::v3::Response> {
|
||||
use create_room::v3::RoomPreset;
|
||||
can_create_room_check(&services, &body).await?;
|
||||
can_publish_directory_check(&services, &body).await?;
|
||||
|
||||
let sender_user = body.sender_user();
|
||||
// Figure out preset. We need it for preset specific events
|
||||
let preset = body
|
||||
.preset
|
||||
.clone()
|
||||
.unwrap_or(match &body.visibility {
|
||||
| room::Visibility::Public => RoomPreset::PublicChat,
|
||||
| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
|
||||
});
|
||||
|
||||
if !services.globals.allow_room_creation()
|
||||
&& body.appservice_info.is_none()
|
||||
&& !services.users.is_admin(sender_user).await
|
||||
{
|
||||
return Err!(Request(Forbidden("Room creation has been disabled.",)));
|
||||
}
|
||||
let alias: OptionFuture<_> = body
|
||||
.room_alias_name
|
||||
.as_ref()
|
||||
.map(|alias| room_alias_check(&services, alias, body.appservice_info.as_ref()))
|
||||
.into();
|
||||
|
||||
let room_id: OwnedRoomId = match &body.room_id {
|
||||
| Some(custom_room_id) => custom_room_id_check(&services, custom_room_id)?,
|
||||
| _ => RoomId::new(&services.server.name),
|
||||
};
|
||||
|
||||
// check if room ID doesn't already exist instead of erroring on auth check
|
||||
if services
|
||||
.rooms
|
||||
.short
|
||||
.get_shortroomid(&room_id)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
|
||||
}
|
||||
|
||||
if body.visibility == room::Visibility::Public
|
||||
&& services
|
||||
.server
|
||||
.config
|
||||
.lockdown_public_room_directory
|
||||
&& !services.users.is_admin(sender_user).await
|
||||
&& body.appservice_info.is_none()
|
||||
{
|
||||
warn!(
|
||||
"Non-admin user {sender_user} tried to publish {room_id} to the room directory \
|
||||
while \"lockdown_public_room_directory\" is enabled"
|
||||
);
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
// Determine room version
|
||||
let (room_version, version_rules) = body
|
||||
.room_version
|
||||
.as_ref()
|
||||
.map_or(Ok(&services.server.config.default_room_version), |version| {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!(
|
||||
"Non-admin user {sender_user} tried to publish {room_id} to the room \
|
||||
directory while \"lockdown_public_room_directory\" is enabled"
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
return Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")));
|
||||
}
|
||||
|
||||
let _short_id = services
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortroomid(&room_id)
|
||||
.await;
|
||||
|
||||
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
|
||||
|
||||
let alias: Option<OwnedRoomAliasId> = match body.room_alias_name.as_ref() {
|
||||
| Some(alias) =>
|
||||
Some(room_alias_check(&services, alias, body.appservice_info.as_ref()).await?),
|
||||
| _ => None,
|
||||
};
|
||||
|
||||
let room_version = match body.room_version.clone() {
|
||||
| Some(room_version) =>
|
||||
if services
|
||||
.server
|
||||
.supported_room_version(&room_version)
|
||||
{
|
||||
room_version
|
||||
} else {
|
||||
return Err!(Request(UnsupportedRoomVersion(
|
||||
"This server does not support that room version."
|
||||
)));
|
||||
},
|
||||
| None => services
|
||||
.server
|
||||
.config
|
||||
.default_room_version
|
||||
.clone(),
|
||||
};
|
||||
.supported_room_version(version)
|
||||
.then_ok_or_else(version, || {
|
||||
err!(Request(UnsupportedRoomVersion(
|
||||
"This server does not support room version {version:?}"
|
||||
)))
|
||||
})
|
||||
})
|
||||
.and_then(|version| Ok((version, room_version::rules(version)?)))?;
|
||||
|
||||
let create_content = match &body.creation_content {
|
||||
| Some(content) => {
|
||||
use RoomVersionId::*;
|
||||
|
||||
let mut content = content
|
||||
.deserialize_as::<CanonicalJsonObject>()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(error!(
|
||||
"Failed to deserialise content as canonical JSON: {e}"
|
||||
))))
|
||||
})?;
|
||||
|
||||
match room_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
|
||||
content.insert(
|
||||
"creator".into(),
|
||||
json!(&sender_user).try_into().map_err(|e| {
|
||||
err!(Request(BadJson(debug_error!("Invalid creation content: {e}"))))
|
||||
})?,
|
||||
);
|
||||
},
|
||||
| _ => {
|
||||
// V11+ removed the "creator" key
|
||||
},
|
||||
}
|
||||
content.insert(
|
||||
"room_version".into(),
|
||||
json!(room_version.as_str())
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
|
||||
);
|
||||
content
|
||||
},
|
||||
| None => {
|
||||
use RoomVersionId::*;
|
||||
|
||||
let content = match room_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
|
||||
RoomCreateEventContent::new_v1(sender_user.to_owned()),
|
||||
| _ => RoomCreateEventContent::new_v11(),
|
||||
};
|
||||
let mut content =
|
||||
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())
|
||||
.unwrap();
|
||||
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
|
||||
content
|
||||
},
|
||||
};
|
||||
// Error on existing alias before committing to creation.
|
||||
let alias = alias.await.transpose()?;
|
||||
|
||||
// Increment and hold the counter; the room will sync atomically to clients
|
||||
// which is preferable.
|
||||
let next_count = services.globals.next_count();
|
||||
|
||||
// 1. The room create event
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&create_content)?,
|
||||
state_key: Some(StateKey::new()),
|
||||
..Default::default()
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
// 1. Create the create event.
|
||||
let (room_id, state_lock) = match version_rules.room_id_format {
|
||||
| RoomIdFormatVersion::V1 =>
|
||||
create_create_event_legacy(&services, &body, room_version, &version_rules).await?,
|
||||
| RoomIdFormatVersion::V2 =>
|
||||
create_create_event(&services, &body, &preset, room_version, &version_rules)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam("Error while creating m.room.create event: {e}")))
|
||||
})?,
|
||||
};
|
||||
|
||||
// 2. Let the room creator join
|
||||
let sender_user = body.sender_user();
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(sender_user.to_string(), &RoomMemberEventContent {
|
||||
@@ -230,17 +131,14 @@ pub(crate) async fn create_room_route(
|
||||
.await?;
|
||||
|
||||
// 3. Power levels
|
||||
|
||||
// Figure out preset. We need it for preset specific events
|
||||
let preset = body
|
||||
.preset
|
||||
.clone()
|
||||
.unwrap_or(match &body.visibility {
|
||||
| room::Visibility::Public => RoomPreset::PublicChat,
|
||||
| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
|
||||
});
|
||||
|
||||
let mut users = BTreeMap::from_iter([(sender_user.to_owned(), int!(100))]);
|
||||
let mut users = if !version_rules
|
||||
.authorization
|
||||
.explicitly_privilege_room_creators
|
||||
{
|
||||
BTreeMap::from_iter([(sender_user.to_owned(), int!(100))])
|
||||
} else {
|
||||
BTreeMap::new()
|
||||
};
|
||||
|
||||
if preset == RoomPreset::TrustedPrivateChat {
|
||||
for invite in &body.invite {
|
||||
@@ -260,18 +158,23 @@ pub(crate) async fn create_room_route(
|
||||
continue;
|
||||
}
|
||||
|
||||
users.insert(invite.clone(), int!(100));
|
||||
if !version_rules
|
||||
.authorization
|
||||
.additional_room_creators
|
||||
{
|
||||
users.insert(invite.clone(), int!(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let power_levels_content = default_power_levels_content(
|
||||
&version_rules,
|
||||
body.power_level_content_override.as_ref(),
|
||||
&body.visibility,
|
||||
users,
|
||||
)?;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
@@ -290,7 +193,6 @@ pub(crate) async fn create_room_route(
|
||||
// 4. Canonical room alias
|
||||
if let Some(room_alias_id) = &alias {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &RoomCanonicalAliasEventContent {
|
||||
@@ -309,7 +211,6 @@ pub(crate) async fn create_room_route(
|
||||
|
||||
// 5.1 Join Rules
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(
|
||||
@@ -329,7 +230,6 @@ pub(crate) async fn create_room_route(
|
||||
|
||||
// 5.2 History Visibility
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(
|
||||
@@ -345,7 +245,6 @@ pub(crate) async fn create_room_route(
|
||||
|
||||
// 5.3 Guest Access
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(
|
||||
@@ -365,7 +264,7 @@ pub(crate) async fn create_room_route(
|
||||
// 6. Events listed in initial_state
|
||||
for event in &body.initial_state {
|
||||
let mut pdu_builder = event
|
||||
.deserialize_as::<PduBuilder>()
|
||||
.deserialize_as_unchecked::<PduBuilder>()
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam(warn!("Invalid initial state event: {e:?}"))))
|
||||
})?;
|
||||
@@ -394,7 +293,6 @@ pub(crate) async fn create_room_route(
|
||||
}
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
|
||||
.boxed()
|
||||
@@ -404,7 +302,6 @@ pub(crate) async fn create_room_route(
|
||||
// 7. Events implied by name and topic
|
||||
if let Some(name) = &body.name {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &RoomNameEventContent::new(name.clone())),
|
||||
@@ -418,10 +315,12 @@ pub(crate) async fn create_room_route(
|
||||
|
||||
if let Some(topic) = &body.topic {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &RoomTopicEventContent { topic: topic.clone() }),
|
||||
PduBuilder::state(String::new(), &RoomTopicEventContent {
|
||||
topic: topic.clone(),
|
||||
topic_block: TopicContentBlock::default(),
|
||||
}),
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
@@ -433,43 +332,50 @@ pub(crate) async fn create_room_route(
|
||||
drop(next_count);
|
||||
drop(state_lock);
|
||||
|
||||
// 8. Events implied by invite (and TODO: invite_3pid)
|
||||
for user_id in &body.invite {
|
||||
if services
|
||||
.users
|
||||
.user_is_ignored(sender_user, user_id)
|
||||
// if inviting anyone with room creation and invite check passes
|
||||
if (!body.invite.is_empty() || !body.invite_3pid.is_empty())
|
||||
&& invite_check(&services, sender_user, &room_id)
|
||||
.await
|
||||
{
|
||||
continue;
|
||||
} else if services
|
||||
.users
|
||||
.user_is_ignored(user_id, sender_user)
|
||||
.await
|
||||
{
|
||||
// silently drop the invite to the recipient if they've been ignored by the
|
||||
// sender, pretend it worked
|
||||
continue;
|
||||
}
|
||||
.is_ok()
|
||||
{
|
||||
// 8. Events implied by invite (and TODO: invite_3pid)
|
||||
for user_id in &body.invite {
|
||||
if services
|
||||
.users
|
||||
.user_is_ignored(sender_user, user_id)
|
||||
.await
|
||||
{
|
||||
continue;
|
||||
} else if services
|
||||
.users
|
||||
.user_is_ignored(user_id, sender_user)
|
||||
.await
|
||||
{
|
||||
// silently drop the invite to the recipient if they've been ignored by the
|
||||
// sender, pretend it worked
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) =
|
||||
invite_helper(&services, sender_user, user_id, &room_id, None, body.is_direct)
|
||||
if let Err(e) = services
|
||||
.membership
|
||||
.invite(sender_user, user_id, &room_id, None, body.is_direct)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
warn!(%e, "Failed to send invite");
|
||||
{
|
||||
warn!(%e, "Failed to send invite");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Homeserver specific stuff
|
||||
if let Some(alias) = alias {
|
||||
services
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(&alias, &room_id, sender_user)?;
|
||||
}
|
||||
|
||||
if body.visibility == room::Visibility::Public {
|
||||
services.rooms.directory.set_public(&room_id);
|
||||
services.directory.set_public(&room_id);
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
@@ -488,25 +394,243 @@ pub(crate) async fn create_room_route(
|
||||
Ok(create_room::v3::Response::new(room_id))
|
||||
}
|
||||
|
||||
async fn create_create_event(
|
||||
services: &Services,
|
||||
body: &Ruma<create_room::v3::Request>,
|
||||
preset: &RoomPreset,
|
||||
room_version: &RoomVersionId,
|
||||
version_rules: &RoomVersionRules,
|
||||
) -> Result<(OwnedRoomId, RoomMutexGuard)> {
|
||||
let _sender_user = body.sender_user();
|
||||
|
||||
let mut create_content = match &body.creation_content {
|
||||
| Some(content) => {
|
||||
let mut content = content
|
||||
.deserialize_as_unchecked::<CanonicalJsonObject>()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(error!(
|
||||
"Failed to deserialise content as canonical JSON: {e}"
|
||||
))))
|
||||
})?;
|
||||
|
||||
if !services.config.federate_created_rooms {
|
||||
if !services.config.allow_federation || !content.contains_key("m.federate") {
|
||||
content.insert("m.federate".into(), json!(false).try_into()?);
|
||||
}
|
||||
}
|
||||
|
||||
content.insert(
|
||||
"room_version".into(),
|
||||
json!(room_version.as_str())
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
|
||||
);
|
||||
|
||||
content
|
||||
},
|
||||
| None => {
|
||||
let content = RoomCreateEventContent::new_v11();
|
||||
|
||||
let mut content =
|
||||
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
|
||||
|
||||
if !services.config.federate_created_rooms {
|
||||
content.insert("m.federate".into(), json!(false).try_into()?);
|
||||
}
|
||||
|
||||
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
|
||||
content
|
||||
},
|
||||
};
|
||||
|
||||
if version_rules
|
||||
.authorization
|
||||
.additional_room_creators
|
||||
{
|
||||
let mut additional_creators = body
|
||||
.creation_content
|
||||
.as_ref()
|
||||
.and_then(|c| {
|
||||
c.deserialize_as_unchecked::<CreationContent>()
|
||||
.ok()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.additional_creators;
|
||||
|
||||
if *preset == RoomPreset::TrustedPrivateChat {
|
||||
additional_creators.extend(body.invite.clone());
|
||||
}
|
||||
|
||||
additional_creators.sort();
|
||||
additional_creators.dedup();
|
||||
if !additional_creators.is_empty() {
|
||||
create_content
|
||||
.insert("additional_creators".into(), json!(additional_creators).try_into()?);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. The room create event, using a placeholder room_id
|
||||
let room_id = ruma::room_id!("!thiswillbereplaced").to_owned();
|
||||
let state_lock = services.state.mutex.lock(&room_id).await;
|
||||
let create_event_id = services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&create_content)?,
|
||||
state_key: Some(StateKey::new()),
|
||||
..Default::default()
|
||||
},
|
||||
body.sender_user(),
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
// The real room_id is now the event_id.
|
||||
let room_id = OwnedRoomId::from_parts('!', create_event_id.localpart(), None)?;
|
||||
let state_lock = services.state.mutex.lock(&room_id).await;
|
||||
|
||||
Ok((room_id, state_lock))
|
||||
}
|
||||
|
||||
async fn create_create_event_legacy(
|
||||
services: &Services,
|
||||
body: &Ruma<create_room::v3::Request>,
|
||||
room_version: &RoomVersionId,
|
||||
_version_rules: &RoomVersionRules,
|
||||
) -> Result<(OwnedRoomId, RoomMutexGuard)> {
|
||||
let room_id: OwnedRoomId = match &body.room_id {
|
||||
| None => RoomId::new_v1(&services.server.name),
|
||||
| Some(custom_id) => custom_room_id_check(services, custom_id).await?,
|
||||
};
|
||||
|
||||
let state_lock = services.state.mutex.lock(&room_id).await;
|
||||
|
||||
let _short_id = services
|
||||
.short
|
||||
.get_or_create_shortroomid(&room_id)
|
||||
.await;
|
||||
|
||||
let create_content = match &body.creation_content {
|
||||
| Some(content) => {
|
||||
use RoomVersionId::*;
|
||||
|
||||
let mut content = content
|
||||
.deserialize_as_unchecked::<CanonicalJsonObject>()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(error!(
|
||||
"Failed to deserialise content as canonical JSON: {e}"
|
||||
))))
|
||||
})?;
|
||||
|
||||
match room_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
|
||||
content.insert(
|
||||
"creator".into(),
|
||||
json!(body.sender_user())
|
||||
.try_into()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(debug_error!(
|
||||
"Invalid creation content: {e}"
|
||||
))))
|
||||
})?,
|
||||
);
|
||||
},
|
||||
| _ => {
|
||||
// V11+ removed the "creator" key
|
||||
},
|
||||
}
|
||||
|
||||
if !services.config.federate_created_rooms {
|
||||
if !services.config.allow_federation || !content.contains_key("m.federate") {
|
||||
content.insert("m.federate".into(), json!(false).try_into()?);
|
||||
}
|
||||
}
|
||||
|
||||
content.insert(
|
||||
"room_version".into(),
|
||||
json!(room_version.as_str())
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
|
||||
);
|
||||
|
||||
content
|
||||
},
|
||||
| None => {
|
||||
use RoomVersionId::*;
|
||||
|
||||
let content = match room_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
|
||||
RoomCreateEventContent::new_v1(body.sender_user().to_owned()),
|
||||
| _ => RoomCreateEventContent::new_v11(),
|
||||
};
|
||||
|
||||
let mut content =
|
||||
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
|
||||
|
||||
if !services.config.federate_created_rooms {
|
||||
content.insert("m.federate".into(), json!(false).try_into()?);
|
||||
}
|
||||
|
||||
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
|
||||
content
|
||||
},
|
||||
};
|
||||
|
||||
// 1. The room create event
|
||||
services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&create_content)?,
|
||||
state_key: Some(StateKey::new()),
|
||||
..Default::default()
|
||||
},
|
||||
body.sender_user(),
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
Ok((room_id, state_lock))
|
||||
}
|
||||
|
||||
/// creates the power_levels_content for the PDU builder
|
||||
fn default_power_levels_content(
|
||||
version_rules: &RoomVersionRules,
|
||||
power_level_content_override: Option<&Raw<RoomPowerLevelsEventContent>>,
|
||||
visibility: &room::Visibility,
|
||||
users: BTreeMap<OwnedUserId, Int>,
|
||||
) -> Result<serde_json::Value> {
|
||||
use serde_json::to_value;
|
||||
|
||||
let mut power_levels_content =
|
||||
to_value(RoomPowerLevelsEventContent { users, ..Default::default() })?;
|
||||
let mut power_levels_content = RoomPowerLevelsEventContent::new(&version_rules.authorization);
|
||||
power_levels_content.users = users;
|
||||
|
||||
let mut power_levels_content = to_value(power_levels_content)?;
|
||||
|
||||
// secure proper defaults of sensitive/dangerous permissions that moderators
|
||||
// (power level 50) should not have easy access to
|
||||
power_levels_content["events"]["m.room.power_levels"] = to_value(100)?;
|
||||
power_levels_content["events"]["m.room.server_acl"] = to_value(100)?;
|
||||
power_levels_content["events"]["m.room.tombstone"] = to_value(100)?;
|
||||
power_levels_content["events"]["m.room.encryption"] = to_value(100)?;
|
||||
power_levels_content["events"]["m.room.history_visibility"] = to_value(100)?;
|
||||
|
||||
if version_rules
|
||||
.authorization
|
||||
.explicitly_privilege_room_creators
|
||||
{
|
||||
power_levels_content["events"]["m.room.tombstone"] = to_value(150)?;
|
||||
} else {
|
||||
power_levels_content["events"]["m.room.tombstone"] = to_value(100)?;
|
||||
}
|
||||
|
||||
// always allow users to respond (not post new) to polls. this is primarily
|
||||
// useful in read-only announcement rooms that post a public poll.
|
||||
power_levels_content["events"]["org.matrix.msc3381.poll.response"] = to_value(0)?;
|
||||
@@ -572,7 +696,6 @@ async fn room_alias_check(
|
||||
})?;
|
||||
|
||||
if services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&full_room_alias)
|
||||
.await
|
||||
@@ -599,7 +722,7 @@ async fn room_alias_check(
|
||||
}
|
||||
|
||||
/// if a room is being created with a custom room ID, run our checks against it
|
||||
fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
|
||||
async fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
|
||||
// apply forbidden room alias checks to custom room IDs too
|
||||
if services
|
||||
.globals
|
||||
@@ -623,8 +746,64 @@ fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<Own
|
||||
let server_name = services.globals.server_name();
|
||||
let full_room_id = format!("!{custom_room_id}:{server_name}");
|
||||
|
||||
OwnedRoomId::parse(full_room_id)
|
||||
.map_err(Into::into)
|
||||
let room_id = OwnedRoomId::parse(full_room_id)
|
||||
.inspect(|full_room_id| debug_info!(?full_room_id, "Full custom room ID"))
|
||||
.inspect_err(|e| warn!(?e, ?custom_room_id, "Failed to create room with custom room ID",))
|
||||
.inspect_err(|e| {
|
||||
warn!(?e, ?custom_room_id, "Failed to create room with custom room ID");
|
||||
})?;
|
||||
|
||||
// check if room ID doesn't already exist instead of erroring on auth check
|
||||
if services
|
||||
.short
|
||||
.get_shortroomid(&room_id)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
|
||||
}
|
||||
|
||||
Ok(room_id)
|
||||
}
|
||||
|
||||
async fn can_publish_directory_check(
|
||||
services: &Services,
|
||||
body: &Ruma<create_room::v3::Request>,
|
||||
) -> Result {
|
||||
if !services
|
||||
.server
|
||||
.config
|
||||
.lockdown_public_room_directory
|
||||
|| body.appservice_info.is_some()
|
||||
|| body.visibility != room::Visibility::Public
|
||||
|| services.users.is_admin(body.sender_user()).await
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let msg = format!(
|
||||
"Non-admin user {} tried to publish new to the directory while \
|
||||
lockdown_public_room_directory is enabled",
|
||||
body.sender_user(),
|
||||
);
|
||||
|
||||
warn!("{msg}");
|
||||
if services.server.config.admin_room_notices {
|
||||
services.admin.notice(&msg).await;
|
||||
}
|
||||
|
||||
Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")))
|
||||
}
|
||||
|
||||
async fn can_create_room_check(
|
||||
services: &Services,
|
||||
body: &Ruma<create_room::v3::Request>,
|
||||
) -> Result {
|
||||
if !services.globals.allow_room_creation()
|
||||
&& body.appservice_info.is_none()
|
||||
&& !services.users.is_admin(body.sender_user()).await
|
||||
{
|
||||
return Err!(Request(Forbidden("Room creation has been disabled.",)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -16,13 +16,11 @@ pub(crate) async fn get_room_event_route(
|
||||
let room_id = &body.room_id;
|
||||
|
||||
let event = services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu(event_id)
|
||||
.map_err(|_| err!(Request(NotFound("Event {} not found.", event_id))));
|
||||
|
||||
let visible = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(body.sender_user(), room_id, event_id)
|
||||
.map(Ok);
|
||||
|
||||
@@ -17,7 +17,6 @@ pub(crate) async fn room_initial_sync_route(
|
||||
let room_id = &body.room_id;
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(body.sender_user(), room_id)
|
||||
.await
|
||||
@@ -26,19 +25,13 @@ pub(crate) async fn room_initial_sync_route(
|
||||
}
|
||||
|
||||
let membership = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.user_membership(body.sender_user(), room_id)
|
||||
.map(Ok);
|
||||
|
||||
let visibility = services
|
||||
.rooms
|
||||
.directory
|
||||
.visibility(room_id)
|
||||
.map(Ok);
|
||||
let visibility = services.directory.visibility(room_id).map(Ok);
|
||||
|
||||
let state = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_full_pdus(room_id)
|
||||
.map_ok(Event::into_format)
|
||||
@@ -46,7 +39,6 @@ pub(crate) async fn room_initial_sync_route(
|
||||
|
||||
let limit = LIMIT_MAX;
|
||||
let events = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(None, room_id, None)
|
||||
.try_take(limit)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::{
|
||||
FutureExt, StreamExt,
|
||||
FutureExt, StreamExt, TryFutureExt,
|
||||
future::{OptionFuture, join3},
|
||||
stream::FuturesUnordered,
|
||||
};
|
||||
@@ -12,7 +12,7 @@
|
||||
federation::space::{SpaceHierarchyParentSummary, get_hierarchy},
|
||||
},
|
||||
events::room::member::MembershipState,
|
||||
space::SpaceRoomJoinRule::{self, *},
|
||||
room::{JoinRuleSummary, RoomSummary},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Result, debug_warn, trace,
|
||||
@@ -34,8 +34,8 @@
|
||||
pub(crate) async fn get_room_summary_legacy(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_summary::msc3266::Request>,
|
||||
) -> Result<RumaResponse<get_summary::msc3266::Response>> {
|
||||
body: Ruma<get_summary::v1::Request>,
|
||||
) -> Result<RumaResponse<get_summary::v1::Response>> {
|
||||
get_room_summary(State(services), InsecureClientIp(client), body)
|
||||
.boxed()
|
||||
.await
|
||||
@@ -51,15 +51,14 @@ pub(crate) async fn get_room_summary_legacy(
|
||||
pub(crate) async fn get_room_summary(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_summary::msc3266::Request>,
|
||||
) -> Result<get_summary::msc3266::Response> {
|
||||
body: Ruma<get_summary::v1::Request>,
|
||||
) -> Result<get_summary::v1::Response> {
|
||||
let (room_id, servers) = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_with_servers(&body.room_id_or_alias, Some(body.via.clone()))
|
||||
.await?;
|
||||
|
||||
if services.rooms.metadata.is_banned(&room_id).await {
|
||||
if services.metadata.is_banned(&room_id).await {
|
||||
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
@@ -73,9 +72,8 @@ async fn room_summary_response(
|
||||
room_id: &RoomId,
|
||||
servers: &[OwnedServerName],
|
||||
sender_user: Option<&UserId>,
|
||||
) -> Result<get_summary::msc3266::Response> {
|
||||
) -> Result<get_summary::v1::Response> {
|
||||
if services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(services.globals.server_name(), room_id)
|
||||
.await
|
||||
@@ -85,23 +83,12 @@ async fn room_summary_response(
|
||||
.await;
|
||||
}
|
||||
|
||||
let room =
|
||||
remote_room_summary_hierarchy_response(services, room_id, servers, sender_user).await?;
|
||||
let summary = remote_room_summary_hierarchy_response(services, room_id, servers, sender_user)
|
||||
.await?
|
||||
.summary;
|
||||
|
||||
Ok(get_summary::msc3266::Response {
|
||||
room_id: room_id.to_owned(),
|
||||
canonical_alias: room.canonical_alias,
|
||||
avatar_url: room.avatar_url,
|
||||
guest_can_join: room.guest_can_join,
|
||||
name: room.name,
|
||||
num_joined_members: room.num_joined_members,
|
||||
topic: room.topic,
|
||||
world_readable: room.world_readable,
|
||||
join_rule: room.join_rule,
|
||||
room_type: room.room_type,
|
||||
room_version: room.room_version,
|
||||
encryption: room.encryption,
|
||||
allowed_room_ids: room.allowed_room_ids,
|
||||
Ok(get_summary::v1::Response {
|
||||
summary,
|
||||
membership: sender_user
|
||||
.is_some()
|
||||
.then_some(MembershipState::Leave),
|
||||
@@ -112,22 +99,13 @@ async fn local_room_summary_response(
|
||||
services: &Services,
|
||||
room_id: &RoomId,
|
||||
sender_user: Option<&UserId>,
|
||||
) -> Result<get_summary::msc3266::Response> {
|
||||
) -> Result<get_summary::v1::Response> {
|
||||
trace!(?sender_user, "Sending local room summary response for {room_id:?}");
|
||||
let join_rule = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_join_rules(room_id);
|
||||
let join_rule = services.state_accessor.get_join_rules(room_id);
|
||||
|
||||
let world_readable = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.is_world_readable(room_id);
|
||||
let world_readable = services.state_accessor.is_world_readable(room_id);
|
||||
|
||||
let guest_can_join = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.guest_can_join(room_id);
|
||||
let guest_can_join = services.state_accessor.guest_can_join(room_id);
|
||||
|
||||
let (join_rule, world_readable, guest_can_join) =
|
||||
join3(join_rule, world_readable, guest_can_join).await;
|
||||
@@ -139,55 +117,43 @@ async fn local_room_summary_response(
|
||||
&join_rule.clone().into(),
|
||||
guest_can_join,
|
||||
world_readable,
|
||||
join_rule.allowed_rooms(),
|
||||
join_rule.allowed_room_ids(),
|
||||
sender_user,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let canonical_alias = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_canonical_alias(room_id)
|
||||
.ok();
|
||||
|
||||
let name = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_name(room_id)
|
||||
.ok();
|
||||
let name = services.state_accessor.get_name(room_id).ok();
|
||||
|
||||
let topic = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_room_topic(room_id)
|
||||
.ok();
|
||||
|
||||
let room_type = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_room_type(room_id)
|
||||
.ok();
|
||||
|
||||
let avatar_url = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_avatar(room_id)
|
||||
.map(|res| res.into_option().unwrap_or_default().url);
|
||||
.map_ok(|content| content.url)
|
||||
.ok()
|
||||
.map(Option::flatten);
|
||||
|
||||
let room_version = services
|
||||
.rooms
|
||||
.state
|
||||
.get_room_version(room_id)
|
||||
.ok();
|
||||
let room_version = services.state.get_room_version(room_id).ok();
|
||||
|
||||
let encryption = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_room_encryption(room_id)
|
||||
.ok();
|
||||
|
||||
let num_joined_members = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_joined_count(room_id)
|
||||
.unwrap_or(0);
|
||||
@@ -195,7 +161,6 @@ async fn local_room_summary_response(
|
||||
let membership: OptionFuture<_> = sender_user
|
||||
.map(|sender_user| {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(room_id, sender_user)
|
||||
.map_ok_or(MembershipState::Leave, |content| content.membership)
|
||||
@@ -224,24 +189,22 @@ async fn local_room_summary_response(
|
||||
membership,
|
||||
);
|
||||
|
||||
Ok(get_summary::msc3266::Response {
|
||||
room_id: room_id.to_owned(),
|
||||
canonical_alias,
|
||||
avatar_url,
|
||||
guest_can_join,
|
||||
name,
|
||||
num_joined_members: num_joined_members.try_into().unwrap_or_default(),
|
||||
topic,
|
||||
world_readable,
|
||||
room_type,
|
||||
room_version,
|
||||
encryption,
|
||||
Ok(get_summary::v1::Response {
|
||||
summary: RoomSummary {
|
||||
room_id: room_id.to_owned(),
|
||||
canonical_alias,
|
||||
avatar_url,
|
||||
guest_can_join,
|
||||
name,
|
||||
num_joined_members: num_joined_members.try_into().unwrap_or_default(),
|
||||
topic,
|
||||
world_readable,
|
||||
room_type,
|
||||
room_version,
|
||||
encryption,
|
||||
join_rule: join_rule.into(),
|
||||
},
|
||||
membership,
|
||||
allowed_room_ids: join_rule
|
||||
.allowed_rooms()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
join_rule: join_rule.into(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -257,7 +220,7 @@ async fn remote_room_summary_hierarchy_response(
|
||||
return Err!(Request(Forbidden("Federation is disabled.")));
|
||||
}
|
||||
|
||||
if services.rooms.metadata.is_disabled(room_id).await {
|
||||
if services.metadata.is_disabled(room_id).await {
|
||||
return Err!(Request(Forbidden(
|
||||
"Federaton of room {room_id} is currently disabled on this server."
|
||||
)));
|
||||
@@ -277,10 +240,11 @@ async fn remote_room_summary_hierarchy_response(
|
||||
while let Some(Ok(response)) = requests.next().await {
|
||||
trace!("{response:?}");
|
||||
let room = response.room.clone();
|
||||
if room.room_id != room_id {
|
||||
let summary = &room.summary;
|
||||
if summary.room_id != room_id {
|
||||
debug_warn!(
|
||||
"Room ID {} returned does not belong to the requested room ID {}",
|
||||
room.room_id,
|
||||
summary.room_id,
|
||||
room_id
|
||||
);
|
||||
continue;
|
||||
@@ -289,10 +253,10 @@ async fn remote_room_summary_hierarchy_response(
|
||||
return user_can_see_summary(
|
||||
services,
|
||||
room_id,
|
||||
&room.join_rule,
|
||||
room.guest_can_join,
|
||||
room.world_readable,
|
||||
room.allowed_room_ids.iter().map(AsRef::as_ref),
|
||||
&summary.join_rule,
|
||||
summary.guest_can_join,
|
||||
summary.world_readable,
|
||||
summary.join_rule.allowed_room_ids(),
|
||||
sender_user,
|
||||
)
|
||||
.await
|
||||
@@ -308,7 +272,7 @@ async fn remote_room_summary_hierarchy_response(
|
||||
async fn user_can_see_summary<'a, I>(
|
||||
services: &Services,
|
||||
room_id: &RoomId,
|
||||
join_rule: &SpaceRoomJoinRule,
|
||||
join_rule: &JoinRuleSummary,
|
||||
guest_can_join: bool,
|
||||
world_readable: bool,
|
||||
allowed_room_ids: I,
|
||||
@@ -317,23 +281,25 @@ async fn user_can_see_summary<'a, I>(
|
||||
where
|
||||
I: Iterator<Item = &'a RoomId> + Send,
|
||||
{
|
||||
let is_public_room = matches!(join_rule, Public | Knock | KnockRestricted);
|
||||
let is_public_room = matches!(
|
||||
join_rule,
|
||||
JoinRuleSummary::Public | JoinRuleSummary::Knock | JoinRuleSummary::KnockRestricted(_)
|
||||
);
|
||||
|
||||
match sender_user {
|
||||
| Some(sender_user) => {
|
||||
let user_can_see_state_events = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(sender_user, room_id);
|
||||
|
||||
let is_guest = services
|
||||
.users
|
||||
.is_deactivated(sender_user)
|
||||
.unwrap_or(false);
|
||||
let user_in_allowed_restricted_room = allowed_room_ids.stream().any(|room| {
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(sender_user, room)
|
||||
});
|
||||
|
||||
let user_in_allowed_restricted_room = allowed_room_ids
|
||||
.stream()
|
||||
.any(|room| services.state_cache.is_joined(sender_user, room));
|
||||
|
||||
let (user_can_see_state_events, is_guest, user_in_allowed_restricted_room) =
|
||||
join3(user_can_see_state_events, is_guest, user_in_allowed_restricted_room)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
CanonicalJsonObject, RoomId, RoomVersionId,
|
||||
api::client::{error::ErrorKind, room::upgrade_room},
|
||||
api::client::room::upgrade_room,
|
||||
events::{
|
||||
StateEventType, TimelineEventType,
|
||||
room::{
|
||||
@@ -14,11 +14,12 @@
|
||||
},
|
||||
},
|
||||
int,
|
||||
room_version_rules::RoomIdFormatVersion,
|
||||
};
|
||||
use serde_json::{json, value::to_raw_value};
|
||||
use tuwunel_core::{
|
||||
Err, Error, Result, err,
|
||||
matrix::{Event, StateKey, pdu::PduBuilder},
|
||||
Err, Result, err,
|
||||
matrix::{Event, StateKey, pdu::PduBuilder, room_version},
|
||||
};
|
||||
|
||||
use crate::Ruma;
|
||||
@@ -61,33 +62,35 @@ pub(crate) async fn upgrade_room_route(
|
||||
.server
|
||||
.supported_room_version(&body.new_version)
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::UnsupportedRoomVersion,
|
||||
return Err!(Request(UnsupportedRoomVersion(
|
||||
"This server does not support that room version.",
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
if matches!(body.new_version, RoomVersionId::V12) {
|
||||
return Err!(Request(UnsupportedRoomVersion(
|
||||
"Upgrading to version 12 is still under development.",
|
||||
)));
|
||||
}
|
||||
|
||||
let room_version_rules = room_version::rules(&body.new_version)?;
|
||||
let room_id_format = &room_version_rules.room_id_format;
|
||||
assert!(*room_id_format == RoomIdFormatVersion::V1, "TODO");
|
||||
|
||||
// Create a replacement room
|
||||
let replacement_room = RoomId::new(services.globals.server_name());
|
||||
let replacement_room = RoomId::new_v1(services.globals.server_name());
|
||||
|
||||
let _short_id = services
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortroomid(&replacement_room)
|
||||
.await;
|
||||
|
||||
let state_lock = services
|
||||
.rooms
|
||||
.state
|
||||
.mutex
|
||||
.lock(&body.room_id)
|
||||
.await;
|
||||
let state_lock = services.state.mutex.lock(&body.room_id).await;
|
||||
|
||||
// Send a m.room.tombstone event to the old room to indicate that it is not
|
||||
// intended to be used any further Fail if the sender does not have the required
|
||||
// permissions
|
||||
let tombstone_event_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
|
||||
@@ -102,16 +105,10 @@ pub(crate) async fn upgrade_room_route(
|
||||
|
||||
// Change lock to replacement room
|
||||
drop(state_lock);
|
||||
let state_lock = services
|
||||
.rooms
|
||||
.state
|
||||
.mutex
|
||||
.lock(&replacement_room)
|
||||
.await;
|
||||
let state_lock = services.state.mutex.lock(&replacement_room).await;
|
||||
|
||||
// Get the old room creation event
|
||||
let mut create_event_content: CanonicalJsonObject = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content(&body.room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
@@ -164,7 +161,6 @@ pub(crate) async fn upgrade_room_route(
|
||||
}
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
@@ -183,7 +179,6 @@ pub(crate) async fn upgrade_room_route(
|
||||
|
||||
// Join the new room
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
@@ -212,7 +207,6 @@ pub(crate) async fn upgrade_room_route(
|
||||
// Replicate transferable state events to the new room
|
||||
for event_type in TRANSFERABLE_STATE_EVENTS {
|
||||
let event_content = match services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&body.room_id, event_type, "")
|
||||
.await
|
||||
@@ -222,7 +216,6 @@ pub(crate) async fn upgrade_room_route(
|
||||
};
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
@@ -240,27 +233,23 @@ pub(crate) async fn upgrade_room_route(
|
||||
|
||||
// Moves any local aliases to the new room
|
||||
let mut local_aliases = services
|
||||
.rooms
|
||||
.alias
|
||||
.local_aliases_for_room(&body.room_id)
|
||||
.boxed();
|
||||
|
||||
while let Some(alias) = local_aliases.next().await {
|
||||
services
|
||||
.rooms
|
||||
.alias
|
||||
.remove_alias(alias, sender_user)
|
||||
.await?;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(alias, &replacement_room, sender_user)?;
|
||||
}
|
||||
|
||||
// Get the old room power levels
|
||||
let power_levels_event_content: RoomPowerLevelsEventContent = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content(&body.room_id, &StateEventType::RoomPowerLevels, "")
|
||||
.await
|
||||
@@ -280,7 +269,6 @@ pub(crate) async fn upgrade_room_route(
|
||||
// Modify the power levels in the old room to prevent sending of events and
|
||||
// inviting new users
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(StateKey::new(), &RoomPowerLevelsEventContent {
|
||||
|
||||
@@ -87,7 +87,6 @@ async fn category_room_events(
|
||||
.map(StreamExt::boxed)
|
||||
.unwrap_or_else(|| {
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(sender_user)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -110,12 +109,7 @@ async fn category_room_events(
|
||||
limit,
|
||||
};
|
||||
|
||||
let (count, results) = services
|
||||
.rooms
|
||||
.search
|
||||
.search_pdus(&query)
|
||||
.await
|
||||
.ok()?;
|
||||
let (count, results) = services.search.search_pdus(&query).await.ok()?;
|
||||
|
||||
results
|
||||
.collect::<Vec<_>>()
|
||||
@@ -187,7 +181,6 @@ async fn category_room_events(
|
||||
|
||||
async fn procure_room_state(services: &Services, room_id: &RoomId) -> Result<RoomState> {
|
||||
let state = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_full_pdus(room_id)
|
||||
.map_ok(Event::into_format)
|
||||
@@ -208,14 +201,12 @@ async fn check_room_visible(
|
||||
|
||||
let is_joined = !check_visible
|
||||
|| services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(user_id, room_id)
|
||||
.await;
|
||||
|
||||
let state_visible = !check_state
|
||||
|| services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(user_id, room_id)
|
||||
.await;
|
||||
|
||||
@@ -30,16 +30,10 @@ pub(crate) async fn send_message_event_route(
|
||||
return Err!(Request(Forbidden("Encryption has been disabled")));
|
||||
}
|
||||
|
||||
let state_lock = services
|
||||
.rooms
|
||||
.state
|
||||
.mutex
|
||||
.lock(&body.room_id)
|
||||
.await;
|
||||
let state_lock = services.state.mutex.lock(&body.room_id).await;
|
||||
|
||||
if body.event_type == MessageLikeEventType::CallInvite
|
||||
&& services
|
||||
.rooms
|
||||
.directory
|
||||
.is_public_room(&body.room_id)
|
||||
.await
|
||||
@@ -75,7 +69,6 @@ pub(crate) async fn send_message_event_route(
|
||||
.map_err(|e| err!(Request(BadJson("Invalid JSON body: {e}"))))?;
|
||||
|
||||
let event_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
|
||||
@@ -22,20 +22,9 @@ pub(super) async fn handle_login(
|
||||
_body: &Ruma<Request>,
|
||||
info: &Token,
|
||||
) -> Result<OwnedUserId> {
|
||||
let config = &services.config.jwt;
|
||||
|
||||
if !config.enable {
|
||||
return Err!(Request(Unknown("JWT login is not enabled.")));
|
||||
}
|
||||
|
||||
let claim = validate(config, &info.token)?;
|
||||
let local = claim.sub.to_lowercase();
|
||||
let server = &services.server.name;
|
||||
let user_id = UserId::parse_with_server_name(local, server).map_err(|e| {
|
||||
err!(Request(InvalidUsername("JWT subject is not a valid user MXID: {e}")))
|
||||
})?;
|
||||
|
||||
let user_id = validate_user(services, &info.token)?;
|
||||
if !services.users.exists(&user_id).await {
|
||||
let config = &services.config.jwt;
|
||||
if !config.register_user {
|
||||
return Err!(Request(NotFound("User {user_id} is not registered on this server.")));
|
||||
}
|
||||
@@ -49,6 +38,22 @@ pub(super) async fn handle_login(
|
||||
Ok(user_id)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_user(services: &Services, token: &str) -> Result<OwnedUserId> {
|
||||
let config = &services.config.jwt;
|
||||
if !config.enable {
|
||||
return Err!(Request(Unauthorized("JWT login is not enabled.")));
|
||||
}
|
||||
|
||||
let claim = validate(config, token)?;
|
||||
let local = claim.sub.to_lowercase();
|
||||
let server = &services.server.name;
|
||||
let user_id = UserId::parse_with_server_name(local, server).map_err(|e| {
|
||||
err!(Request(InvalidUsername("JWT subject is not a valid user MXID: {e}")))
|
||||
})?;
|
||||
|
||||
Ok(user_id)
|
||||
}
|
||||
|
||||
fn validate(config: &JwtConfig, token: &str) -> Result<Claim> {
|
||||
let verifier = init_verifier(config)?;
|
||||
let validator = init_validator(config)?;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use futures::FutureExt;
|
||||
use ruma::{OwnedUserId, UserId};
|
||||
use tuwunel_core::{Err, Result, debug};
|
||||
use tuwunel_service::Services;
|
||||
@@ -63,6 +64,7 @@ pub(super) async fn ldap_login(
|
||||
services
|
||||
.admin
|
||||
.make_user_admin(lowercased_user_id)
|
||||
.boxed()
|
||||
.await?;
|
||||
} else if !is_ldap_admin && is_tuwunel_admin {
|
||||
services
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mod appservice;
|
||||
mod jwt;
|
||||
pub(crate) mod jwt;
|
||||
mod ldap;
|
||||
mod logout;
|
||||
mod password;
|
||||
|
||||
@@ -56,10 +56,7 @@ pub(crate) async fn login_token_route(
|
||||
|
||||
let mut uiaainfo = uiaa::UiaaInfo {
|
||||
flows: vec![password_flow],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match &body.auth {
|
||||
|
||||
+52
-28
@@ -1,21 +1,21 @@
|
||||
use std::{
|
||||
collections::{BTreeSet, VecDeque},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::{collections::BTreeSet, iter::once, str::FromStr};
|
||||
|
||||
use axum::extract::State;
|
||||
use futures::{StreamExt, TryFutureExt, future::OptionFuture};
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt, future::OptionFuture, stream::FuturesOrdered};
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedServerName, RoomId, UInt, UserId, api::client::space::get_hierarchy,
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Result,
|
||||
Err, Result, debug_error,
|
||||
utils::{future::TryExtExt, stream::IterStream},
|
||||
};
|
||||
use tuwunel_service::{
|
||||
Services,
|
||||
rooms::spaces::{
|
||||
PaginationToken, SummaryAccessibility, get_parent_children_via, summary_to_chunk,
|
||||
rooms::{
|
||||
short::ShortRoomId,
|
||||
spaces::{
|
||||
PaginationToken, SummaryAccessibility, get_parent_children_via, summary_to_chunk,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -77,30 +77,38 @@ async fn get_client_hierarchy<'a, ShortRoomIds>(
|
||||
short_room_ids: ShortRoomIds,
|
||||
) -> Result<get_hierarchy::v1::Response>
|
||||
where
|
||||
ShortRoomIds: Iterator<Item = &'a u64> + Clone + Send + Sync + 'a,
|
||||
ShortRoomIds: Iterator<Item = &'a ShortRoomId> + Clone + Send + Sync + 'a,
|
||||
{
|
||||
type Via = Vec<OwnedServerName>;
|
||||
type Entry = (OwnedRoomId, Via);
|
||||
type Rooms = VecDeque<Entry>;
|
||||
type Via = Vec<OwnedServerName>;
|
||||
|
||||
let mut queue: Rooms = [(
|
||||
room_id.to_owned(),
|
||||
room_id
|
||||
let initial = async move {
|
||||
let via = room_id
|
||||
.server_name()
|
||||
.map(ToOwned::to_owned)
|
||||
.into_iter()
|
||||
.collect(),
|
||||
)]
|
||||
.into();
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut rooms = Vec::with_capacity(limit);
|
||||
let mut parents = BTreeSet::new();
|
||||
while let Some((current_room, via)) = queue.pop_front() {
|
||||
let summary = services
|
||||
.rooms
|
||||
.spaces
|
||||
.get_summary_and_children_client(¤t_room, suggested_only, sender_user, &via)
|
||||
.await?;
|
||||
.get_summary_and_children_client(room_id, suggested_only, sender_user, &via)
|
||||
.await;
|
||||
|
||||
(room_id.to_owned(), via, summary)
|
||||
};
|
||||
|
||||
let mut parents = BTreeSet::new();
|
||||
let mut rooms = Vec::with_capacity(limit);
|
||||
let mut queue: FuturesOrdered<_> = once(initial.boxed()).collect();
|
||||
|
||||
while let Some((current_room, via, summary)) = queue.next().await {
|
||||
let summary = match summary {
|
||||
| Ok(summary) => summary,
|
||||
| Err(e) => {
|
||||
debug_error!(?current_room, ?via, ?e, "error getting summary");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
match (summary, current_room == room_id) {
|
||||
| (None | Some(SummaryAccessibility::Inaccessible), false) => {
|
||||
@@ -130,7 +138,6 @@ async fn get_client_hierarchy<'a, ShortRoomIds>(
|
||||
.stream()
|
||||
.skip_while(|(room, _)| {
|
||||
services
|
||||
.rooms
|
||||
.short
|
||||
.get_shortroomid(room)
|
||||
.map_ok(|short| {
|
||||
@@ -159,20 +166,37 @@ async fn get_client_hierarchy<'a, ShortRoomIds>(
|
||||
continue;
|
||||
}
|
||||
|
||||
queue.extend(children);
|
||||
children
|
||||
.into_iter()
|
||||
.map(|(room_id, via)| async move {
|
||||
let summary = services
|
||||
.spaces
|
||||
.get_summary_and_children_client(
|
||||
&room_id,
|
||||
suggested_only,
|
||||
sender_user,
|
||||
&via,
|
||||
)
|
||||
.await;
|
||||
|
||||
(room_id, via, summary)
|
||||
})
|
||||
.map(FutureExt::boxed)
|
||||
.for_each(|entry| queue.push_back(entry));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let next_batch: OptionFuture<_> = queue
|
||||
.pop_front()
|
||||
.map(async |(room, _)| {
|
||||
.next()
|
||||
.await
|
||||
.map(async |(room, ..)| {
|
||||
parents.insert(room);
|
||||
|
||||
let next_short_room_ids: Vec<_> = parents
|
||||
.iter()
|
||||
.stream()
|
||||
.filter_map(|room_id| services.rooms.short.get_shortroomid(room_id).ok())
|
||||
.filter_map(|room_id| services.short.get_shortroomid(room_id).ok())
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
|
||||
+52
-58
@@ -2,7 +2,7 @@
|
||||
use futures::{FutureExt, TryFutureExt, TryStreamExt};
|
||||
use ruma::{
|
||||
OwnedEventId, RoomId, UserId,
|
||||
api::client::state::{get_state_events, get_state_events_for_key, send_state_event},
|
||||
api::client::state::{get_state_event_for_key, get_state_events, send_state_event},
|
||||
events::{
|
||||
AnyStateEventContent, StateEventType,
|
||||
room::{
|
||||
@@ -78,7 +78,6 @@ pub(crate) async fn get_state_events_route(
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(sender_user, &body.room_id)
|
||||
.await
|
||||
@@ -88,7 +87,6 @@ pub(crate) async fn get_state_events_route(
|
||||
|
||||
Ok(get_state_events::v3::Response {
|
||||
room_state: services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_full_pdus(&body.room_id)
|
||||
.map_ok(Event::into_format)
|
||||
@@ -107,12 +105,11 @@ pub(crate) async fn get_state_events_route(
|
||||
/// readable
|
||||
pub(crate) async fn get_state_events_for_key_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_state_events_for_key::v3::Request>,
|
||||
) -> Result<get_state_events_for_key::v3::Response> {
|
||||
body: Ruma<get_state_event_for_key::v3::Request>,
|
||||
) -> Result<get_state_event_for_key::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(sender_user, &body.room_id)
|
||||
.await
|
||||
@@ -123,7 +120,6 @@ pub(crate) async fn get_state_events_for_key_route(
|
||||
}
|
||||
|
||||
let event = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&body.room_id, &body.event_type, &body.state_key)
|
||||
.await
|
||||
@@ -140,7 +136,7 @@ pub(crate) async fn get_state_events_for_key_route(
|
||||
.as_ref()
|
||||
.is_some_and(|f| f.to_lowercase().eq("event"));
|
||||
|
||||
Ok(get_state_events_for_key::v3::Response {
|
||||
Ok(get_state_event_for_key::v3::Response {
|
||||
content: event_format.or(|| event.get_content_as_value()),
|
||||
event: event_format.then(|| {
|
||||
json!({
|
||||
@@ -167,8 +163,8 @@ pub(crate) async fn get_state_events_for_key_route(
|
||||
/// readable
|
||||
pub(crate) async fn get_state_events_for_empty_key_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_state_events_for_key::v3::Request>,
|
||||
) -> Result<RumaResponse<get_state_events_for_key::v3::Response>> {
|
||||
body: Ruma<get_state_event_for_key::v3::Request>,
|
||||
) -> Result<RumaResponse<get_state_event_for_key::v3::Response>> {
|
||||
get_state_events_for_key_route(State(services), body)
|
||||
.await
|
||||
.map(RumaResponse)
|
||||
@@ -184,9 +180,8 @@ async fn send_state_event_for_key_helper(
|
||||
timestamp: Option<ruma::MilliSecondsSinceUnixEpoch>,
|
||||
) -> Result<OwnedEventId> {
|
||||
allowed_to_send_state_event(services, room_id, event_type, state_key, json).await?;
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
let state_lock = services.state.mutex.lock(room_id).await;
|
||||
let event_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
@@ -222,7 +217,7 @@ async fn allowed_to_send_state_event(
|
||||
| StateEventType::RoomServerAcl => {
|
||||
// prevents common ACL paw-guns as ACL management is difficult and prone to
|
||||
// irreversible mistakes
|
||||
match json.deserialize_as::<RoomServerAclEventContent>() {
|
||||
match json.deserialize_as_unchecked::<RoomServerAclEventContent>() {
|
||||
| Ok(acl_content) => {
|
||||
if acl_content.allow_is_empty() {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
@@ -282,7 +277,7 @@ async fn allowed_to_send_state_event(
|
||||
// admin room is a sensitive room, it should not ever be made public
|
||||
if let Ok(admin_room_id) = services.admin.get_admin_room().await {
|
||||
if admin_room_id == room_id {
|
||||
match json.deserialize_as::<RoomJoinRulesEventContent>() {
|
||||
match json.deserialize_as_unchecked::<RoomJoinRulesEventContent>() {
|
||||
| Ok(join_rule) =>
|
||||
if join_rule.join_rule == JoinRule::Public {
|
||||
return Err!(Request(Forbidden(
|
||||
@@ -301,7 +296,7 @@ async fn allowed_to_send_state_event(
|
||||
| StateEventType::RoomHistoryVisibility => {
|
||||
// admin room is a sensitive room, it should not ever be made world readable
|
||||
if let Ok(admin_room_id) = services.admin.get_admin_room().await {
|
||||
match json.deserialize_as::<RoomHistoryVisibilityEventContent>() {
|
||||
match json.deserialize_as_unchecked::<RoomHistoryVisibilityEventContent>() {
|
||||
| Ok(visibility_content) => {
|
||||
if admin_room_id == room_id
|
||||
&& visibility_content.history_visibility
|
||||
@@ -322,7 +317,7 @@ async fn allowed_to_send_state_event(
|
||||
}
|
||||
},
|
||||
| StateEventType::RoomCanonicalAlias => {
|
||||
match json.deserialize_as::<RoomCanonicalAliasEventContent>() {
|
||||
match json.deserialize_as_unchecked::<RoomCanonicalAliasEventContent>() {
|
||||
| Ok(canonical_alias_content) => {
|
||||
let mut aliases = canonical_alias_content.alt_aliases.clone();
|
||||
|
||||
@@ -332,7 +327,6 @@ async fn allowed_to_send_state_event(
|
||||
|
||||
for alias in aliases {
|
||||
let (alias_room_id, _servers) = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&alias, None)
|
||||
.await
|
||||
@@ -354,52 +348,52 @@ async fn allowed_to_send_state_event(
|
||||
},
|
||||
}
|
||||
},
|
||||
| StateEventType::RoomMember => match json.deserialize_as::<RoomMemberEventContent>() {
|
||||
| Ok(membership_content) => {
|
||||
let Ok(_state_key) = UserId::parse(state_key) else {
|
||||
return Err!(Request(BadJson(
|
||||
"Membership event has invalid or non-existent state key"
|
||||
)));
|
||||
};
|
||||
|
||||
if let Some(authorising_user) =
|
||||
membership_content.join_authorized_via_users_server
|
||||
{
|
||||
if membership_content.membership != MembershipState::Join {
|
||||
| StateEventType::RoomMember =>
|
||||
match json.deserialize_as_unchecked::<RoomMemberEventContent>() {
|
||||
| Ok(membership_content) => {
|
||||
let Ok(_state_key) = UserId::parse(state_key) else {
|
||||
return Err!(Request(BadJson(
|
||||
"join_authorised_via_users_server is only for member joins"
|
||||
"Membership event has invalid or non-existent state key"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
if !services.globals.user_is_local(&authorising_user) {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Authorising user {authorising_user} does not belong to this \
|
||||
homeserver"
|
||||
)));
|
||||
}
|
||||
if let Some(authorising_user) =
|
||||
membership_content.join_authorized_via_users_server
|
||||
{
|
||||
if membership_content.membership != MembershipState::Join {
|
||||
return Err!(Request(BadJson(
|
||||
"join_authorised_via_users_server is only for member joins"
|
||||
)));
|
||||
}
|
||||
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(&authorising_user, room_id)
|
||||
.map(is_false!())
|
||||
.map(BoolExt::into_result)
|
||||
.map_err(|()| {
|
||||
err!(Request(InvalidParam(
|
||||
"Authorising user {authorising_user} is not in the room. They \
|
||||
cannot authorise the join."
|
||||
)))
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
if !services.globals.user_is_local(&authorising_user) {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Authorising user {authorising_user} does not belong to this \
|
||||
homeserver"
|
||||
)));
|
||||
}
|
||||
|
||||
services
|
||||
.state_cache
|
||||
.is_joined(&authorising_user, room_id)
|
||||
.map(is_false!())
|
||||
.map(BoolExt::into_result)
|
||||
.map_err(|()| {
|
||||
err!(Request(InvalidParam(
|
||||
"Authorising user {authorising_user} is not in the room. \
|
||||
They cannot authorise the join."
|
||||
)))
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(BadJson(
|
||||
"Membership content must have a valid JSON body with at least a valid \
|
||||
membership state: {e}"
|
||||
)));
|
||||
},
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(BadJson(
|
||||
"Membership content must have a valid JSON body with at least a valid \
|
||||
membership state: {e}"
|
||||
)));
|
||||
},
|
||||
},
|
||||
| _ => (),
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
use tuwunel_core::{
|
||||
Error, PduCount, Result,
|
||||
matrix::pdu::PduEvent,
|
||||
utils::stream::{BroadbandExt, ReadyExt, TryIgnore},
|
||||
utils::stream::{BroadbandExt, ReadyExt},
|
||||
};
|
||||
use tuwunel_service::Services;
|
||||
|
||||
@@ -27,22 +27,20 @@ async fn load_timeline(
|
||||
roomsincecount: PduCount,
|
||||
next_batch: Option<PduCount>,
|
||||
limit: usize,
|
||||
) -> Result<(Vec<(PduCount, PduEvent)>, bool), Error> {
|
||||
) -> Result<(Vec<(PduCount, PduEvent)>, bool, PduCount), Error> {
|
||||
let last_timeline_count = services
|
||||
.rooms
|
||||
.timeline
|
||||
.last_timeline_count(Some(sender_user), room_id)
|
||||
.last_timeline_count(Some(sender_user), room_id, next_batch)
|
||||
.await?;
|
||||
|
||||
if last_timeline_count <= roomsincecount {
|
||||
return Ok((Vec::new(), false));
|
||||
return Ok((Vec::new(), false, last_timeline_count));
|
||||
}
|
||||
|
||||
let non_timeline_pdus = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(Some(sender_user), room_id, None)
|
||||
.ignore_err()
|
||||
.ready_filter_map(Result::ok)
|
||||
.ready_skip_while(|&(pducount, _)| pducount > next_batch.unwrap_or_else(PduCount::max))
|
||||
.ready_take_while(|&(pducount, _)| pducount > roomsincecount);
|
||||
|
||||
@@ -60,7 +58,7 @@ async fn load_timeline(
|
||||
// is limited unless there are events in non_timeline_pdus
|
||||
let limited = non_timeline_pdus.next().await.is_some();
|
||||
|
||||
Ok((timeline_pdus, limited))
|
||||
Ok((timeline_pdus, limited, last_timeline_count))
|
||||
}
|
||||
|
||||
async fn share_encrypted_room(
|
||||
@@ -70,14 +68,12 @@ async fn share_encrypted_room(
|
||||
ignore_room: Option<&RoomId>,
|
||||
) -> bool {
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_shared_rooms(sender_user, user_id)
|
||||
.ready_filter(|&room_id| Some(room_id) != ignore_room)
|
||||
.map(ToOwned::to_owned)
|
||||
.broad_any(async |other_room_id| {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.is_encrypted_room(&other_room_id)
|
||||
.await
|
||||
|
||||
+73
-117
@@ -6,7 +6,7 @@
|
||||
use axum::extract::State;
|
||||
use futures::{
|
||||
FutureExt, StreamExt, TryFutureExt, TryStreamExt,
|
||||
future::{OptionFuture, join, join3, join4, join5, try_join4},
|
||||
future::{OptionFuture, join, join3, join4, join5, try_join3},
|
||||
pin_mut,
|
||||
};
|
||||
use ruma::{
|
||||
@@ -18,7 +18,7 @@
|
||||
v3::{
|
||||
Ephemeral, Filter, GlobalAccountData, InviteState, InvitedRoom, JoinedRoom,
|
||||
KnockState, KnockedRoom, LeftRoom, Presence, RoomAccountData, RoomSummary, Rooms,
|
||||
State as RoomState, Timeline, ToDevice,
|
||||
State as RoomState, StateEvents, Timeline, ToDevice,
|
||||
},
|
||||
},
|
||||
uiaa::UiaaResponse,
|
||||
@@ -133,6 +133,11 @@ pub(crate) async fn sync_events_route(
|
||||
.await
|
||||
.log_err()
|
||||
.ok();
|
||||
|
||||
// Record user as actively syncing for push suppression heuristic.
|
||||
if services.config.suppress_push_when_active {
|
||||
services.presence.note_sync(sender_user).await;
|
||||
}
|
||||
}
|
||||
|
||||
let mut since = body
|
||||
@@ -177,9 +182,10 @@ pub(crate) async fn sync_events_route(
|
||||
}
|
||||
|
||||
// Wait for activity
|
||||
if time::timeout_at(stop_at, watchers).await.is_err() {
|
||||
if time::timeout_at(stop_at, watchers).await.is_err() || services.server.is_stopping() {
|
||||
let response = build_empty_response(&services, &body, next_batch).await;
|
||||
trace!(since, next_batch, "empty response");
|
||||
return Ok(sync_events::v3::Response::new(next_batch.to_string()));
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
trace!(
|
||||
@@ -194,6 +200,21 @@ pub(crate) async fn sync_events_route(
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_empty_response(
|
||||
services: &Services,
|
||||
body: &Ruma<sync_events::v3::Request>,
|
||||
next_batch: u64,
|
||||
) -> sync_events::v3::Response {
|
||||
sync_events::v3::Response {
|
||||
device_one_time_keys_count: services
|
||||
.users
|
||||
.count_one_time_keys(body.sender_user(), body.sender_device())
|
||||
.await,
|
||||
|
||||
..sync_events::v3::Response::new(next_batch.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "build",
|
||||
level = "debug",
|
||||
@@ -225,7 +246,6 @@ async fn build_sync_events(
|
||||
};
|
||||
|
||||
let joined_rooms = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(sender_user)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -258,7 +278,6 @@ async fn build_sync_events(
|
||||
);
|
||||
|
||||
let left_rooms = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_left(sender_user)
|
||||
.broad_filter_map(|(room_id, _)| {
|
||||
@@ -278,12 +297,10 @@ async fn build_sync_events(
|
||||
.collect();
|
||||
|
||||
let invited_rooms = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_invited(sender_user)
|
||||
.fold_default(async |mut invited_rooms: BTreeMap<_, _>, (room_id, invite_state)| {
|
||||
let invite_count = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_invite_count(&room_id, sender_user)
|
||||
.await
|
||||
@@ -303,12 +320,10 @@ async fn build_sync_events(
|
||||
});
|
||||
|
||||
let knocked_rooms = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_knocked(sender_user)
|
||||
.fold_default(async |mut knocked_rooms: BTreeMap<_, _>, (room_id, knock_state)| {
|
||||
let knock_count = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_knock_count(&room_id, sender_user)
|
||||
.await
|
||||
@@ -439,7 +454,6 @@ async fn process_presence_updates(
|
||||
.presence_since(since, Some(next_batch))
|
||||
.filter(|(user_id, ..)| {
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.user_sees_user(syncing_user, user_id)
|
||||
})
|
||||
@@ -475,7 +489,6 @@ async fn handle_left_room(
|
||||
filter: &FilterDefinition,
|
||||
) -> Result<Option<LeftRoom>> {
|
||||
let left_count = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_left_count(room_id, sender_user)
|
||||
.await
|
||||
@@ -504,11 +517,11 @@ async fn handle_left_room(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let is_not_found = services.rooms.metadata.exists(room_id).eq(&false);
|
||||
let is_not_found = services.metadata.exists(room_id).eq(&false);
|
||||
|
||||
let is_disabled = services.rooms.metadata.is_disabled(room_id);
|
||||
let is_disabled = services.metadata.is_disabled(room_id);
|
||||
|
||||
let is_banned = services.rooms.metadata.is_banned(room_id);
|
||||
let is_banned = services.metadata.is_banned(room_id);
|
||||
|
||||
pin_mut!(is_not_found, is_disabled, is_banned);
|
||||
if is_not_found.or(is_disabled).or(is_banned).await {
|
||||
@@ -540,21 +553,19 @@ async fn handle_left_room(
|
||||
prev_batch: Some(next_batch.to_string()),
|
||||
events: Vec::new(),
|
||||
},
|
||||
state: RoomState { events: vec![event.into_format()] },
|
||||
state: RoomState::Before(StateEvents { events: vec![event.into_format()] }),
|
||||
}));
|
||||
}
|
||||
|
||||
let mut left_state_events = Vec::new();
|
||||
|
||||
let since_shortstatehash = services
|
||||
.rooms
|
||||
.user
|
||||
.get_token_shortstatehash(room_id, since);
|
||||
|
||||
let since_state_ids: HashMap<_, OwnedEventId> = since_shortstatehash
|
||||
.map_ok(|since_shortstatehash| {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_full_ids(since_shortstatehash)
|
||||
.map(Ok)
|
||||
@@ -565,7 +576,6 @@ async fn handle_left_room(
|
||||
.unwrap_or_default();
|
||||
|
||||
let Ok(left_event_id): Result<OwnedEventId> = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_id(room_id, &StateEventType::RoomMember, sender_user.as_str())
|
||||
.await
|
||||
@@ -575,7 +585,6 @@ async fn handle_left_room(
|
||||
};
|
||||
|
||||
let Ok(left_shortstatehash) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.pdu_shortstatehash(&left_event_id)
|
||||
.await
|
||||
@@ -585,14 +594,12 @@ async fn handle_left_room(
|
||||
};
|
||||
|
||||
let mut left_state_ids: HashMap<_, _> = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_full_ids(left_shortstatehash)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
let leave_shortstatekey = services
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortstatekey(&StateEventType::RoomMember, sender_user.as_str())
|
||||
.await;
|
||||
@@ -602,7 +609,6 @@ async fn handle_left_room(
|
||||
for (shortstatekey, event_id) in left_state_ids {
|
||||
if full_state || since_state_ids.get(&shortstatekey) != Some(&event_id) {
|
||||
let (event_type, state_key) = services
|
||||
.rooms
|
||||
.short
|
||||
.get_statekey_from_short(shortstatekey)
|
||||
.await?;
|
||||
@@ -618,7 +624,7 @@ async fn handle_left_room(
|
||||
continue;
|
||||
}
|
||||
|
||||
let Ok(pdu) = services.rooms.timeline.get_pdu(&event_id).await else {
|
||||
let Ok(pdu) = services.timeline.get_pdu(&event_id).await else {
|
||||
error!("Pdu in state not found: {event_id}");
|
||||
continue;
|
||||
};
|
||||
@@ -635,7 +641,7 @@ async fn handle_left_room(
|
||||
prev_batch: Some(next_batch.to_string()),
|
||||
events: Vec::new(), // and so we dont need to set this to empty vec
|
||||
},
|
||||
state: RoomState { events: left_state_events },
|
||||
state: RoomState::Before(StateEvents { events: left_state_events }),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -658,17 +664,7 @@ async fn load_joined_room(
|
||||
full_state: bool,
|
||||
filter: &FilterDefinition,
|
||||
) -> Result<(JoinedRoom, HashSet<OwnedUserId>, HashSet<OwnedUserId>)> {
|
||||
let sincecount = PduCount::Normal(since);
|
||||
let next_batchcount = PduCount::Normal(next_batch);
|
||||
|
||||
let current_shortstatehash = services
|
||||
.rooms
|
||||
.state
|
||||
.get_room_shortstatehash(room_id)
|
||||
.map_err(|_| err!(Database(error!("Room {room_id} has no state"))));
|
||||
|
||||
let since_shortstatehash = services
|
||||
.rooms
|
||||
.user
|
||||
.get_token_shortstatehash(room_id, since)
|
||||
.ok()
|
||||
@@ -685,13 +681,12 @@ async fn load_joined_room(
|
||||
services,
|
||||
sender_user,
|
||||
room_id,
|
||||
sincecount,
|
||||
Some(next_batchcount),
|
||||
PduCount::Normal(since),
|
||||
Some(PduCount::Normal(next_batch)),
|
||||
timeline_limit,
|
||||
);
|
||||
|
||||
let receipt_events = services
|
||||
.rooms
|
||||
.read_receipt
|
||||
.readreceipts_since(room_id, since, Some(next_batch))
|
||||
.filter_map(async |(read_user, _, edu)| {
|
||||
@@ -704,29 +699,40 @@ async fn load_joined_room(
|
||||
.collect::<HashMap<OwnedUserId, Raw<AnySyncEphemeralRoomEvent>>>()
|
||||
.map(Ok);
|
||||
|
||||
let (current_shortstatehash, since_shortstatehash, (timeline_pdus, limited), receipt_events) =
|
||||
try_join4(current_shortstatehash, since_shortstatehash, timeline, receipt_events)
|
||||
let (since_shortstatehash, (timeline_pdus, limited, last_timeline_count), receipt_events) =
|
||||
try_join3(since_shortstatehash, timeline, receipt_events)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
// State was changed after the cutoff for this sync; similar to other handlers.
|
||||
if current_shortstatehash > next_batch {
|
||||
// Transfer the since_shortstatehash not the current over to the next sync.
|
||||
if let Some(since_shortstatehash) = since_shortstatehash {
|
||||
let horizon_shortstatehash: OptionFuture<_> = timeline_pdus
|
||||
.iter()
|
||||
.map(at!(0))
|
||||
.map(PduCount::into_unsigned)
|
||||
.map(|shorteventid| {
|
||||
services
|
||||
.rooms
|
||||
.user
|
||||
.associate_token_shortstatehash(room_id, next_batch, since_shortstatehash)
|
||||
.await;
|
||||
}
|
||||
.state_accessor
|
||||
.get_shortstatehash(shorteventid)
|
||||
})
|
||||
.next()
|
||||
.into();
|
||||
|
||||
return Ok((JoinedRoom::default(), HashSet::new(), HashSet::new()));
|
||||
}
|
||||
let current_shortstatehash = services
|
||||
.state_accessor
|
||||
.get_shortstatehash(last_timeline_count.into_unsigned())
|
||||
.or_else(|_| services.state.get_room_shortstatehash(room_id));
|
||||
|
||||
let associate_token = services
|
||||
.rooms
|
||||
.user
|
||||
.associate_token_shortstatehash(room_id, next_batch, current_shortstatehash);
|
||||
let (horizon_shortstatehash, current_shortstatehash) =
|
||||
join(horizon_shortstatehash, current_shortstatehash)
|
||||
.boxed()
|
||||
.await;
|
||||
|
||||
let current_shortstatehash = current_shortstatehash
|
||||
.map_err(|_| err!(Database(error!("Room {room_id} has no state"))))?;
|
||||
|
||||
let associate_token =
|
||||
services
|
||||
.user
|
||||
.associate_token_shortstatehash(room_id, next_batch, current_shortstatehash);
|
||||
|
||||
let lazy_loading_enabled = filter.room.state.lazy_load_options.is_enabled()
|
||||
|| filter
|
||||
@@ -747,12 +753,7 @@ async fn load_joined_room(
|
||||
|
||||
// Reset lazy loading because this is an initial sync
|
||||
let lazy_load_reset: OptionFuture<_> = initial
|
||||
.then(|| {
|
||||
services
|
||||
.rooms
|
||||
.lazy_loading
|
||||
.reset(lazy_loading_context)
|
||||
})
|
||||
.then(|| services.lazy_loading.reset(lazy_loading_context))
|
||||
.into();
|
||||
|
||||
lazy_load_reset.await;
|
||||
@@ -767,30 +768,15 @@ async fn load_joined_room(
|
||||
.collect();
|
||||
|
||||
services
|
||||
.rooms
|
||||
.lazy_loading
|
||||
.witness_retain(witness, lazy_loading_context)
|
||||
})
|
||||
.into();
|
||||
|
||||
let horizon_shortstatehash: OptionFuture<_> = timeline_pdus
|
||||
.iter()
|
||||
.map(at!(0))
|
||||
.map(PduCount::into_unsigned)
|
||||
.map(|shorteventid| {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_shortstatehash(shorteventid)
|
||||
})
|
||||
.next()
|
||||
.into();
|
||||
|
||||
let last_notification_read: OptionFuture<_> = timeline_pdus
|
||||
.is_empty()
|
||||
.then(|| {
|
||||
services
|
||||
.rooms
|
||||
.user
|
||||
.last_notification_read(sender_user, room_id)
|
||||
})
|
||||
@@ -799,32 +785,26 @@ async fn load_joined_room(
|
||||
let since_sender_member: OptionFuture<_> = since_shortstatehash
|
||||
.map(|short| {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_get_content(short, &StateEventType::RoomMember, sender_user.as_str())
|
||||
.ok()
|
||||
})
|
||||
.into();
|
||||
|
||||
let encrypted_room = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_get(current_shortstatehash, &StateEventType::RoomEncryption, "")
|
||||
.is_ok();
|
||||
let encrypted_room = services.state_accessor.is_encrypted_room(room_id);
|
||||
|
||||
let last_privateread_update = services
|
||||
.rooms
|
||||
.read_receipt
|
||||
.last_privateread_update(sender_user, room_id);
|
||||
|
||||
let (
|
||||
witness,
|
||||
(encrypted_room, last_privateread_update, last_notification_read),
|
||||
(since_sender_member, horizon_shortstatehash, ()),
|
||||
(witness, since_sender_member),
|
||||
(encrypted_room, ()),
|
||||
(last_privateread_update, last_notification_read),
|
||||
) = join3(
|
||||
witness,
|
||||
join3(encrypted_room, last_privateread_update, last_notification_read),
|
||||
join3(since_sender_member, horizon_shortstatehash, associate_token),
|
||||
join(witness, since_sender_member),
|
||||
join(encrypted_room, associate_token),
|
||||
join(last_privateread_update, last_notification_read),
|
||||
)
|
||||
.boxed()
|
||||
.await;
|
||||
@@ -877,7 +857,6 @@ async fn load_joined_room(
|
||||
let notification_count: OptionFuture<_> = send_notification_counts
|
||||
.then(|| {
|
||||
services
|
||||
.rooms
|
||||
.user
|
||||
.notification_count(sender_user, room_id)
|
||||
.map(TryInto::try_into)
|
||||
@@ -888,7 +867,6 @@ async fn load_joined_room(
|
||||
let highlight_count: OptionFuture<_> = send_notification_counts
|
||||
.then(|| {
|
||||
services
|
||||
.rooms
|
||||
.user
|
||||
.highlight_count(sender_user, room_id)
|
||||
.map(TryInto::try_into)
|
||||
@@ -900,7 +878,6 @@ async fn load_joined_room(
|
||||
.gt(&since)
|
||||
.then(|| {
|
||||
services
|
||||
.rooms
|
||||
.read_receipt
|
||||
.private_read_get(room_id, sender_user)
|
||||
.map(Result::ok)
|
||||
@@ -908,7 +885,6 @@ async fn load_joined_room(
|
||||
.into();
|
||||
|
||||
let typing_events = services
|
||||
.rooms
|
||||
.typing
|
||||
.last_typing_update(room_id)
|
||||
.and_then(async |count| {
|
||||
@@ -1042,7 +1018,7 @@ async fn load_joined_room(
|
||||
let joined_room = JoinedRoom {
|
||||
account_data: RoomAccountData { events: account_data_events },
|
||||
ephemeral: Ephemeral { events: edus },
|
||||
state: RoomState { events: state_events },
|
||||
state: RoomState::Before(StateEvents { events: state_events }),
|
||||
summary: RoomSummary {
|
||||
joined_member_count: joined_member_count.map(ruma_from_u64),
|
||||
invited_member_count: invited_member_count.map(ruma_from_u64),
|
||||
@@ -1096,7 +1072,6 @@ async fn calculate_state_changes<'a>(
|
||||
|
||||
let state_get_shorteventid = |user_id: &'a UserId| {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_get_shortid(
|
||||
horizon_shortstatehash,
|
||||
@@ -1122,7 +1097,6 @@ async fn calculate_state_changes<'a>(
|
||||
.then(|| {
|
||||
StreamExt::into_future(
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_added((since_shortstatehash, horizon_shortstatehash))
|
||||
.boxed(),
|
||||
@@ -1134,7 +1108,6 @@ async fn calculate_state_changes<'a>(
|
||||
.then(|| {
|
||||
StreamExt::into_future(
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_full_shortids(horizon_shortstatehash)
|
||||
.expect_ok(),
|
||||
@@ -1155,18 +1128,12 @@ async fn calculate_state_changes<'a>(
|
||||
.chain(lazy_state_ids.stream())
|
||||
.broad_filter_map(|shorteventid| {
|
||||
services
|
||||
.rooms
|
||||
.short
|
||||
.get_eventid_from_short(shorteventid)
|
||||
.ok()
|
||||
})
|
||||
.broad_filter_map(async |event_id: OwnedEventId| {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu(&event_id)
|
||||
.ok()
|
||||
.await
|
||||
services.timeline.get_pdu(&event_id).ok().await
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.boxed()
|
||||
@@ -1198,7 +1165,6 @@ async fn lazy_filter(
|
||||
shorteventid: ShortEventId,
|
||||
) -> Option<ShortEventId> {
|
||||
let (event_type, state_key) = services
|
||||
.rooms
|
||||
.short
|
||||
.get_statekey_from_short(shortstatekey)
|
||||
.await
|
||||
@@ -1214,13 +1180,11 @@ async fn calculate_counts(
|
||||
sender_user: &UserId,
|
||||
) -> (Option<u64>, Option<u64>, Option<Vec<OwnedUserId>>) {
|
||||
let joined_member_count = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_joined_count(room_id)
|
||||
.unwrap_or(0);
|
||||
|
||||
let invited_member_count = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_invited_count(room_id)
|
||||
.unwrap_or(0);
|
||||
@@ -1243,7 +1207,6 @@ async fn calculate_heroes(
|
||||
sender_user: &UserId,
|
||||
) -> Vec<OwnedUserId> {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.all_pdus(sender_user, room_id)
|
||||
.ready_filter(|(_, pdu)| pdu.kind == RoomMember)
|
||||
@@ -1287,14 +1250,8 @@ async fn fold_hero(
|
||||
}
|
||||
|
||||
let (is_invited, is_joined) = join(
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_invited(user_id, room_id),
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(user_id, room_id),
|
||||
services.state_cache.is_invited(user_id, room_id),
|
||||
services.state_cache.is_joined(user_id, room_id),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1314,7 +1271,6 @@ async fn typings_event_for_user(
|
||||
Ok(SyncEphemeralRoomEvent {
|
||||
content: TypingEventContent {
|
||||
user_ids: services
|
||||
.rooms
|
||||
.typing
|
||||
.typing_users_for_user(room_id, sender_user)
|
||||
.await?,
|
||||
|
||||
+896
-646
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
use ruma::api::client::thirdparty::get_protocols;
|
||||
use tuwunel_core::Result;
|
||||
|
||||
use crate::{Ruma, RumaResponse};
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `GET /_matrix/client/r0/thirdparty/protocols`
|
||||
///
|
||||
@@ -14,13 +14,3 @@ pub(crate) async fn get_protocols_route(
|
||||
// TODO
|
||||
Ok(get_protocols::v3::Response { protocols: BTreeMap::new() })
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/unstable/thirdparty/protocols`
|
||||
///
|
||||
/// Same as `get_protocols_route`, except for some reason Element Android legacy
|
||||
/// calls this
|
||||
pub(crate) async fn get_protocols_route_unstable(
|
||||
body: Ruma<get_protocols::v3::Request>,
|
||||
) -> Result<RumaResponse<get_protocols::v3::Response>> {
|
||||
get_protocols_route(body).await.map(RumaResponse)
|
||||
}
|
||||
|
||||
@@ -32,13 +32,11 @@ pub(crate) async fn get_threads_route(
|
||||
.unwrap_or_else(PduCount::max);
|
||||
|
||||
let threads: Vec<(PduCount, PduEvent)> = services
|
||||
.rooms
|
||||
.threads
|
||||
.threads_until(body.sender_user(), &body.room_id, from, &body.include)
|
||||
.take(limit)
|
||||
.try_filter_map(async |(count, pdu)| {
|
||||
Ok(services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(body.sender_user(), &body.room_id, &pdu.event_id)
|
||||
.await
|
||||
|
||||
@@ -19,7 +19,6 @@ pub(crate) async fn create_typing_event_route(
|
||||
}
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(sender_user, &body.room_id)
|
||||
.await
|
||||
@@ -46,7 +45,6 @@ pub(crate) async fn create_typing_event_route(
|
||||
.try_mul(1000)?,
|
||||
);
|
||||
services
|
||||
.rooms
|
||||
.typing
|
||||
.typing_add(
|
||||
sender_user,
|
||||
@@ -59,7 +57,6 @@ pub(crate) async fn create_typing_event_route(
|
||||
},
|
||||
| _ => {
|
||||
services
|
||||
.rooms
|
||||
.typing
|
||||
.typing_remove(sender_user, &body.room_id)
|
||||
.await?;
|
||||
|
||||
+58
-90
@@ -1,5 +1,3 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::StreamExt;
|
||||
@@ -10,8 +8,8 @@
|
||||
error::ErrorKind,
|
||||
membership::mutual_rooms,
|
||||
profile::{
|
||||
delete_profile_key, delete_timezone_key, get_profile_key, get_timezone_key,
|
||||
set_profile_key, set_timezone_key,
|
||||
ProfileFieldName, ProfileFieldValue, delete_profile_field, delete_timezone_key,
|
||||
get_profile_field, get_timezone_key, set_profile_field, set_timezone_key,
|
||||
},
|
||||
},
|
||||
federation,
|
||||
@@ -20,7 +18,6 @@
|
||||
};
|
||||
use tuwunel_core::{Err, Error, Result};
|
||||
|
||||
use super::{update_avatar_url, update_displayname};
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `GET /_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms`
|
||||
@@ -47,7 +44,6 @@ pub(crate) async fn get_mutual_rooms_route(
|
||||
}
|
||||
|
||||
let mutual_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_shared_rooms(sender_user, &body.user_id)
|
||||
.map(ToOwned::to_owned)
|
||||
@@ -123,80 +119,55 @@ pub(crate) async fn set_timezone_key_route(
|
||||
/// Updates the profile key-value field of a user, as per MSC4133.
|
||||
///
|
||||
/// This also handles the avatar_url and displayname being updated.
|
||||
pub(crate) async fn set_profile_key_route(
|
||||
pub(crate) async fn set_profile_field_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<set_profile_key::unstable::Request>,
|
||||
) -> Result<set_profile_key::unstable::Response> {
|
||||
body: Ruma<set_profile_field::v3::Request>,
|
||||
) -> Result<set_profile_field::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||
}
|
||||
|
||||
if body.kv_pair.is_empty() {
|
||||
return Err!(Request(BadJson(
|
||||
"The key-value pair JSON body is empty. Use DELETE to delete a key"
|
||||
)));
|
||||
}
|
||||
|
||||
if body.kv_pair.len() > 1 {
|
||||
// TODO: support PATCH or "recursively" adding keys in some sort
|
||||
return Err!(Request(BadJson(
|
||||
"This endpoint can only take one key-value pair at a time"
|
||||
)));
|
||||
}
|
||||
|
||||
let Some(profile_key_value) = body.kv_pair.get(&body.key_name) else {
|
||||
return Err!(Request(BadJson(
|
||||
"The key does not match the URL field key, or JSON body is empty (use DELETE)"
|
||||
)));
|
||||
};
|
||||
|
||||
if body
|
||||
.kv_pair
|
||||
.keys()
|
||||
.any(|key| key.starts_with("u.") && !profile_key_value.is_string())
|
||||
{
|
||||
return Err!(Request(BadJson("u.* profile key fields must be strings")));
|
||||
}
|
||||
|
||||
if body.kv_pair.keys().any(|key| key.len() > 128) {
|
||||
if body.value.field_name().as_str().len() > 128 {
|
||||
return Err!(Request(BadJson("Key names cannot be longer than 128 bytes")));
|
||||
}
|
||||
|
||||
if body.key_name == "displayname" {
|
||||
if body.value.field_name() == ProfileFieldName::DisplayName {
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
update_displayname(
|
||||
&services,
|
||||
&body.user_id,
|
||||
Some(profile_key_value.to_string()),
|
||||
&all_joined_rooms,
|
||||
)
|
||||
.await;
|
||||
} else if body.key_name == "avatar_url" {
|
||||
let mxc = ruma::OwnedMxcUri::from(profile_key_value.to_string());
|
||||
services
|
||||
.users
|
||||
.update_displayname(
|
||||
&body.user_id,
|
||||
Some(body.value.value().to_string()),
|
||||
&all_joined_rooms,
|
||||
)
|
||||
.await;
|
||||
} else if body.value.field_name() == ProfileFieldName::AvatarUrl {
|
||||
let mxc = ruma::OwnedMxcUri::from(body.value.value().to_string());
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
update_avatar_url(&services, &body.user_id, Some(mxc), None, &all_joined_rooms).await;
|
||||
services
|
||||
.users
|
||||
.update_avatar_url(&body.user_id, Some(mxc), None, &all_joined_rooms)
|
||||
.await;
|
||||
} else {
|
||||
services.users.set_profile_key(
|
||||
&body.user_id,
|
||||
&body.key_name,
|
||||
Some(profile_key_value.clone()),
|
||||
body.value.field_name().as_str(),
|
||||
Some(body.value.value().into_owned()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -208,7 +179,7 @@ pub(crate) async fn set_profile_key_route(
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(set_profile_key::unstable::Response {})
|
||||
Ok(set_profile_field::v3::Response {})
|
||||
}
|
||||
|
||||
/// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}`
|
||||
@@ -216,47 +187,44 @@ pub(crate) async fn set_profile_key_route(
|
||||
/// Deletes the profile key-value field of a user, as per MSC4133.
|
||||
///
|
||||
/// This also handles the avatar_url and displayname being updated.
|
||||
pub(crate) async fn delete_profile_key_route(
|
||||
pub(crate) async fn delete_profile_field_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_profile_key::unstable::Request>,
|
||||
) -> Result<delete_profile_key::unstable::Response> {
|
||||
body: Ruma<delete_profile_field::v3::Request>,
|
||||
) -> Result<delete_profile_field::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||
}
|
||||
|
||||
if body.kv_pair.len() > 1 {
|
||||
// TODO: support PATCH or "recursively" adding keys in some sort
|
||||
return Err!(Request(BadJson(
|
||||
"This endpoint can only take one key-value pair at a time"
|
||||
)));
|
||||
}
|
||||
|
||||
if body.key_name == "displayname" {
|
||||
if body.field == ProfileFieldName::DisplayName {
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
update_displayname(&services, &body.user_id, None, &all_joined_rooms).await;
|
||||
} else if body.key_name == "avatar_url" {
|
||||
services
|
||||
.users
|
||||
.update_displayname(&body.user_id, None, &all_joined_rooms)
|
||||
.await;
|
||||
} else if body.field == ProfileFieldName::AvatarUrl {
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
update_avatar_url(&services, &body.user_id, None, None, &all_joined_rooms).await;
|
||||
services
|
||||
.users
|
||||
.update_avatar_url(&body.user_id, None, None, &all_joined_rooms)
|
||||
.await;
|
||||
} else {
|
||||
services
|
||||
.users
|
||||
.set_profile_key(&body.user_id, &body.key_name, None);
|
||||
.set_profile_key(&body.user_id, body.field.as_str(), None);
|
||||
}
|
||||
|
||||
if services.config.allow_local_presence {
|
||||
@@ -267,7 +235,7 @@ pub(crate) async fn delete_profile_key_route(
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(delete_profile_key::unstable::Response {})
|
||||
Ok(delete_profile_field::v3::Response {})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/us.cloke.msc4175.tz`
|
||||
@@ -337,12 +305,10 @@ pub(crate) async fn get_timezone_key_route(
|
||||
///
|
||||
/// - If user is on another server and we do not have a local copy already fetch
|
||||
/// `timezone` over federation
|
||||
pub(crate) async fn get_profile_key_route(
|
||||
pub(crate) async fn get_profile_field_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_profile_key::unstable::Request>,
|
||||
) -> Result<get_profile_key::unstable::Response> {
|
||||
let mut profile_key_value: BTreeMap<String, serde_json::Value> = BTreeMap::new();
|
||||
|
||||
body: Ruma<get_profile_field::v3::Request>,
|
||||
) -> Result<get_profile_field::v3::Response> {
|
||||
if !services.globals.user_is_local(&body.user_id) {
|
||||
// Create and update our local copy of the user
|
||||
if let Ok(response) = services
|
||||
@@ -379,25 +345,29 @@ pub(crate) async fn get_profile_key_route(
|
||||
.users
|
||||
.set_timezone(&body.user_id, response.tz.clone());
|
||||
|
||||
match response.custom_profile_fields.get(&body.key_name) {
|
||||
let profile_key_value: Option<ProfileFieldValue> = match response
|
||||
.custom_profile_fields
|
||||
.get(body.field.as_str())
|
||||
{
|
||||
| Some(value) => {
|
||||
profile_key_value.insert(body.key_name.clone(), value.clone());
|
||||
services.users.set_profile_key(
|
||||
&body.user_id,
|
||||
&body.key_name,
|
||||
body.field.as_str(),
|
||||
Some(value.clone()),
|
||||
);
|
||||
|
||||
Some(ProfileFieldValue::new(body.field.as_str(), value.clone())?)
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotFound("The requested profile key does not exist.")));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if profile_key_value.is_empty() {
|
||||
if profile_key_value.is_none() {
|
||||
return Err!(Request(NotFound("The requested profile key does not exist.")));
|
||||
}
|
||||
|
||||
return Ok(get_profile_key::unstable::Response { value: profile_key_value });
|
||||
return Ok(get_profile_field::v3::Response { value: profile_key_value });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,22 +377,20 @@ pub(crate) async fn get_profile_key_route(
|
||||
return Err!(Request(NotFound("Profile was not found.")));
|
||||
}
|
||||
|
||||
match services
|
||||
let profile_key_value: Option<ProfileFieldValue> = match services
|
||||
.users
|
||||
.profile_key(&body.user_id, &body.key_name)
|
||||
.profile_key(&body.user_id, body.field.as_str())
|
||||
.await
|
||||
{
|
||||
| Ok(value) => {
|
||||
profile_key_value.insert(body.key_name.clone(), value);
|
||||
},
|
||||
| Ok(value) => Some(ProfileFieldValue::new(body.field.as_str(), value)?),
|
||||
| _ => {
|
||||
return Err!(Request(NotFound("The requested profile key does not exist.")));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if profile_key_value.is_empty() {
|
||||
if profile_key_value.is_none() {
|
||||
return Err!(Request(NotFound("The requested profile key does not exist.")));
|
||||
}
|
||||
|
||||
Ok(get_profile_key::unstable::Response { value: profile_key_value })
|
||||
Ok(get_profile_field::v3::Response { value: profile_key_value })
|
||||
}
|
||||
|
||||
@@ -57,13 +57,11 @@ pub(crate) async fn search_users_route(
|
||||
}
|
||||
|
||||
let user_in_public_room = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&user_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.broad_any(async |room_id| {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_join_rules(&room_id)
|
||||
.map(|rule| matches!(rule, JoinRule::Public))
|
||||
@@ -71,7 +69,6 @@ pub(crate) async fn search_users_route(
|
||||
});
|
||||
|
||||
let user_sees_user = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.user_sees_user(sender_user, &user_id);
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
use ruma::{RoomId, UserId};
|
||||
use tuwunel_core::{Err, Result, warn};
|
||||
use tuwunel_service::Services;
|
||||
|
||||
pub(crate) async fn invite_check(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result {
|
||||
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
|
||||
warn!("{sender_user} is not an admin and attempted to send an invite to {room_id}");
|
||||
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
use axum::{Json, extract::State, response::IntoResponse};
|
||||
use ruma::api::client::{
|
||||
discovery::{
|
||||
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
|
||||
discover_support::{self, Contact},
|
||||
},
|
||||
error::ErrorKind,
|
||||
use ruma::api::client::discovery::{
|
||||
discover_homeserver::{self, HomeserverInfo},
|
||||
discover_support::{self, Contact},
|
||||
};
|
||||
use tuwunel_core::{Error, Result};
|
||||
use tuwunel_core::{Err, Result};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -19,13 +16,12 @@ pub(crate) async fn well_known_client(
|
||||
) -> Result<discover_homeserver::Response> {
|
||||
let client_url = match services.server.config.well_known.client.as_ref() {
|
||||
| Some(url) => url.to_string(),
|
||||
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||
| None => return Err!(Request(NotFound("Not found."))),
|
||||
};
|
||||
|
||||
Ok(discover_homeserver::Response {
|
||||
homeserver: HomeserverInfo { base_url: client_url.clone() },
|
||||
homeserver: HomeserverInfo { base_url: client_url },
|
||||
identity_server: None,
|
||||
sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }),
|
||||
tile_server: None,
|
||||
})
|
||||
}
|
||||
@@ -54,7 +50,7 @@ pub(crate) async fn well_known_support(
|
||||
|
||||
// support page or role must be either defined for this to be valid
|
||||
if support_page.is_none() && role.is_none() {
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found."));
|
||||
return Err!(Request(NotFound("Not found.")));
|
||||
}
|
||||
|
||||
let email_address = services
|
||||
@@ -63,6 +59,7 @@ pub(crate) async fn well_known_support(
|
||||
.well_known
|
||||
.support_email
|
||||
.clone();
|
||||
|
||||
let matrix_id = services
|
||||
.server
|
||||
.config
|
||||
@@ -72,7 +69,7 @@ pub(crate) async fn well_known_support(
|
||||
|
||||
// if a role is specified, an email address or matrix id is required
|
||||
if role.is_some() && (email_address.is_none() && matrix_id.is_none()) {
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found."));
|
||||
return Err!(Request(NotFound("Not found.")));
|
||||
}
|
||||
|
||||
// TODO: support defining multiple contacts in the config
|
||||
@@ -86,7 +83,7 @@ pub(crate) async fn well_known_support(
|
||||
|
||||
// support page or role+contacts must be either defined for this to be valid
|
||||
if contacts.is_empty() && support_page.is_none() {
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found."));
|
||||
return Err!(Request(NotFound("Not found.")));
|
||||
}
|
||||
|
||||
Ok(discover_support::Response { contacts, support_page })
|
||||
@@ -103,7 +100,7 @@ pub(crate) async fn syncv3_client_server_json(
|
||||
| Some(url) => url.to_string(),
|
||||
| None => match services.server.config.well_known.server.as_ref() {
|
||||
| Some(url) => url.to_string(),
|
||||
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||
| None => return Err!(Request(NotFound("Not found."))),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#![type_length_limit = "65536"] //TODO: reduce me
|
||||
#![type_length_limit = "163840"] //TODO: REDUCE ME
|
||||
#![allow(clippy::toplevel_ref_arg)]
|
||||
|
||||
pub mod client;
|
||||
|
||||
+6
-6
@@ -16,16 +16,18 @@
|
||||
use tuwunel_core::{Server, err};
|
||||
|
||||
use self::handler::RouterExt;
|
||||
pub(super) use self::{args::Args as Ruma, response::RumaResponse, state::State};
|
||||
pub(super) use self::{
|
||||
args::Args as Ruma, auth::auth_uiaa, response::RumaResponse, state::State,
|
||||
};
|
||||
use crate::{client, server};
|
||||
|
||||
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
let config = &server.config;
|
||||
let mut router = router
|
||||
.ruma_route(&client::get_timezone_key_route)
|
||||
.ruma_route(&client::get_profile_key_route)
|
||||
.ruma_route(&client::set_profile_key_route)
|
||||
.ruma_route(&client::delete_profile_key_route)
|
||||
.ruma_route(&client::get_profile_field_route)
|
||||
.ruma_route(&client::set_profile_field_route)
|
||||
.ruma_route(&client::delete_profile_field_route)
|
||||
.ruma_route(&client::set_timezone_key_route)
|
||||
.ruma_route(&client::delete_timezone_key_route)
|
||||
.ruma_route(&client::appservice_ping)
|
||||
@@ -116,8 +118,6 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
.ruma_route(&client::search_users_route)
|
||||
.ruma_route(&client::get_member_events_route)
|
||||
.ruma_route(&client::get_protocols_route)
|
||||
.route("/_matrix/client/unstable/thirdparty/protocols",
|
||||
get(client::get_protocols_route_unstable))
|
||||
.ruma_route(&client::send_message_event_route)
|
||||
.ruma_route(&client::send_state_event_for_key_route)
|
||||
.ruma_route(&client::get_state_events_route)
|
||||
|
||||
@@ -82,7 +82,13 @@ impl<T> FromRequest<State, Body> for Args<T>
|
||||
{
|
||||
type Rejection = Error;
|
||||
|
||||
#[tracing::instrument(name = "ar", level = "debug", skip(services), ret, err)]
|
||||
#[tracing::instrument(
|
||||
name = "ar",
|
||||
level = "debug",
|
||||
skip(services),
|
||||
err(level = "debug")
|
||||
ret,
|
||||
)]
|
||||
async fn from_request(
|
||||
request: hyper::Request<Body>,
|
||||
services: &State,
|
||||
|
||||
+32
-175
@@ -1,10 +1,13 @@
|
||||
mod appservice;
|
||||
mod server;
|
||||
mod uiaa;
|
||||
|
||||
use std::{fmt::Debug, time::SystemTime};
|
||||
|
||||
use axum::RequestPartsExt;
|
||||
use axum_extra::{
|
||||
TypedHeader,
|
||||
headers::{Authorization, authorization::Bearer},
|
||||
typed_header::TypedHeaderRejectionReason,
|
||||
};
|
||||
use futures::{
|
||||
TryFutureExt,
|
||||
@@ -15,29 +18,26 @@
|
||||
pin_mut,
|
||||
};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
|
||||
CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId,
|
||||
api::{
|
||||
AuthScheme, IncomingRequest, Metadata,
|
||||
client::{
|
||||
directory::get_public_rooms,
|
||||
error::ErrorKind,
|
||||
profile::{
|
||||
get_avatar_url, get_display_name, get_profile, get_profile_key, get_timezone_key,
|
||||
get_avatar_url, get_display_name, get_profile, get_profile_field,
|
||||
get_timezone_key,
|
||||
},
|
||||
voip::get_turn_server_info,
|
||||
},
|
||||
federation::{authentication::XMatrix, openid::get_openid_userinfo},
|
||||
federation::openid::get_openid_userinfo,
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Error, Result, debug_error, err, is_less_than, utils::result::LogDebugErr, warn,
|
||||
};
|
||||
use tuwunel_service::{
|
||||
Services,
|
||||
appservice::RegistrationInfo,
|
||||
server_keys::{PubKeyMap, PubKeys},
|
||||
};
|
||||
use tuwunel_core::{Err, Error, Result, is_less_than, utils::result::LogDebugErr};
|
||||
use tuwunel_service::{Services, appservice::RegistrationInfo};
|
||||
|
||||
pub(crate) use self::uiaa::auth_uiaa;
|
||||
use self::{appservice::auth_appservice, server::auth_server};
|
||||
use super::request::Request;
|
||||
|
||||
enum Token {
|
||||
@@ -60,8 +60,8 @@ pub(super) struct Auth {
|
||||
#[tracing::instrument(
|
||||
level = "trace",
|
||||
skip(services, request, json_body),
|
||||
ret,
|
||||
err
|
||||
err(level = "debug"),
|
||||
ret
|
||||
)]
|
||||
pub(super) async fn auth(
|
||||
services: &Services,
|
||||
@@ -69,7 +69,10 @@ pub(super) async fn auth(
|
||||
json_body: Option<&CanonicalJsonValue>,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Auth> {
|
||||
use AuthScheme::{AccessToken, AccessTokenOptional, AppserviceToken, ServerSignatures};
|
||||
use AuthScheme::{
|
||||
AccessToken, AccessTokenOptional, AppserviceToken, AppserviceTokenOptional,
|
||||
ServerSignatures,
|
||||
};
|
||||
use Error::BadRequest;
|
||||
use ErrorKind::UnknownToken;
|
||||
use Token::{Appservice, Expired, Invalid, User};
|
||||
@@ -129,7 +132,7 @@ pub(super) async fn auth(
|
||||
|
||||
| (AccessToken, Appservice(info)) => Ok(auth_appservice(services, request, info).await?),
|
||||
|
||||
| (AccessToken, Token::None) => match metadata {
|
||||
| (AccessToken | AppserviceToken, Token::None) => match metadata {
|
||||
| &get_turn_server_info::v3::Request::METADATA
|
||||
if services.server.config.turn_allow_guests =>
|
||||
Ok(Auth::default()),
|
||||
@@ -137,22 +140,25 @@ pub(super) async fn auth(
|
||||
| _ => Err!(Request(MissingToken("Missing access token."))),
|
||||
},
|
||||
|
||||
| (AccessToken | AccessTokenOptional | AuthScheme::None, User(user)) => Ok(Auth {
|
||||
| (
|
||||
AccessToken | AccessTokenOptional | AppserviceTokenOptional | AuthScheme::None,
|
||||
User(user),
|
||||
) => Ok(Auth {
|
||||
sender_user: Some(user.0),
|
||||
sender_device: Some(user.1),
|
||||
_expires_at: user.2,
|
||||
..Auth::default()
|
||||
}),
|
||||
|
||||
//TODO: add AppserviceTokenOptional
|
||||
| (AccessTokenOptional | AppserviceToken | AuthScheme::None, Appservice(info)) =>
|
||||
Ok(Auth {
|
||||
appservice_info: Some(*info),
|
||||
..Auth::default()
|
||||
}),
|
||||
| (
|
||||
AccessTokenOptional | AppserviceTokenOptional | AppserviceToken | AuthScheme::None,
|
||||
Appservice(info),
|
||||
) => Ok(Auth {
|
||||
appservice_info: Some(*info),
|
||||
..Auth::default()
|
||||
}),
|
||||
|
||||
//TODO: add AppserviceTokenOptional
|
||||
| (AccessTokenOptional | AppserviceToken | AuthScheme::None, Token::None) =>
|
||||
| (AccessTokenOptional | AppserviceTokenOptional | AuthScheme::None, Token::None) =>
|
||||
Ok(Auth::default()),
|
||||
}
|
||||
}
|
||||
@@ -166,7 +172,7 @@ fn check_auth_still_required(services: &Services, metadata: &Metadata, token: &T
|
||||
|
||||
match metadata {
|
||||
| &get_profile::v3::Request::METADATA
|
||||
| &get_profile_key::unstable::Request::METADATA
|
||||
| &get_profile_field::v3::Request::METADATA
|
||||
| &get_display_name::v3::Request::METADATA
|
||||
| &get_avatar_url::v3::Request::METADATA
|
||||
| &get_timezone_key::unstable::Request::METADATA
|
||||
@@ -216,152 +222,3 @@ async fn find_token(services: &Services, token: Option<&str>) -> Result<Token> {
|
||||
| _ => Ok(Token::Invalid),
|
||||
}
|
||||
}
|
||||
|
||||
async fn auth_appservice(
|
||||
services: &Services,
|
||||
request: &Request,
|
||||
info: Box<RegistrationInfo>,
|
||||
) -> Result<Auth> {
|
||||
let user_id_default = || {
|
||||
UserId::parse_with_server_name(
|
||||
info.registration.sender_localpart.as_str(),
|
||||
services.globals.server_name(),
|
||||
)
|
||||
};
|
||||
|
||||
let Ok(user_id) = request
|
||||
.query
|
||||
.user_id
|
||||
.clone()
|
||||
.map_or_else(user_id_default, OwnedUserId::parse)
|
||||
else {
|
||||
return Err!(Request(InvalidUsername("Username is invalid.")));
|
||||
};
|
||||
|
||||
if !info.is_user_match(&user_id) {
|
||||
return Err!(Request(Exclusive("User is not in namespace.")));
|
||||
}
|
||||
|
||||
Ok(Auth {
|
||||
sender_user: Some(user_id),
|
||||
appservice_info: Some(*info),
|
||||
..Auth::default()
|
||||
})
|
||||
}
|
||||
|
||||
async fn auth_server(
|
||||
services: &Services,
|
||||
request: &mut Request,
|
||||
body: Option<&CanonicalJsonValue>,
|
||||
) -> Result<Auth> {
|
||||
type Member = (String, CanonicalJsonValue);
|
||||
type Object = CanonicalJsonObject;
|
||||
type Value = CanonicalJsonValue;
|
||||
|
||||
let x_matrix = parse_x_matrix(request).await?;
|
||||
auth_server_checks(services, &x_matrix)?;
|
||||
|
||||
let destination = services.globals.server_name();
|
||||
let origin = &x_matrix.origin;
|
||||
let signature_uri = request
|
||||
.parts
|
||||
.uri
|
||||
.path_and_query()
|
||||
.expect("all requests have a path")
|
||||
.to_string();
|
||||
|
||||
let signature: [Member; 1] =
|
||||
[(x_matrix.key.as_str().into(), Value::String(x_matrix.sig.to_string()))];
|
||||
|
||||
let signatures: [Member; 1] = [(origin.as_str().into(), Value::Object(signature.into()))];
|
||||
|
||||
let authorization: Object = if let Some(body) = body.cloned() {
|
||||
let authorization: [Member; 6] = [
|
||||
("content".into(), body),
|
||||
("destination".into(), Value::String(destination.into())),
|
||||
("method".into(), Value::String(request.parts.method.as_str().into())),
|
||||
("origin".into(), Value::String(origin.as_str().into())),
|
||||
("signatures".into(), Value::Object(signatures.into())),
|
||||
("uri".into(), Value::String(signature_uri)),
|
||||
];
|
||||
|
||||
authorization.into()
|
||||
} else {
|
||||
let authorization: [Member; 5] = [
|
||||
("destination".into(), Value::String(destination.into())),
|
||||
("method".into(), Value::String(request.parts.method.as_str().into())),
|
||||
("origin".into(), Value::String(origin.as_str().into())),
|
||||
("signatures".into(), Value::Object(signatures.into())),
|
||||
("uri".into(), Value::String(signature_uri)),
|
||||
];
|
||||
|
||||
authorization.into()
|
||||
};
|
||||
|
||||
let key = services
|
||||
.server_keys
|
||||
.get_verify_key(origin, &x_matrix.key)
|
||||
.await
|
||||
.map_err(|e| err!(Request(Forbidden(warn!("Failed to fetch signing keys: {e}")))))?;
|
||||
|
||||
let keys: PubKeys = [(x_matrix.key.to_string(), key.key)].into();
|
||||
let keys: PubKeyMap = [(origin.as_str().into(), keys)].into();
|
||||
if let Err(e) = ruma::signatures::verify_json(&keys, authorization) {
|
||||
debug_error!("Failed to verify federation request from {origin}: {e}");
|
||||
if request.parts.uri.to_string().contains('@') {
|
||||
warn!(
|
||||
"Request uri contained '@' character. Make sure your reverse proxy gives \
|
||||
tuwunel the raw uri (apache: use nocanon)"
|
||||
);
|
||||
}
|
||||
|
||||
return Err!(Request(Forbidden("Failed to verify X-Matrix signatures.")));
|
||||
}
|
||||
|
||||
Ok(Auth {
|
||||
origin: origin.to_owned().into(),
|
||||
..Auth::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn auth_server_checks(services: &Services, x_matrix: &XMatrix) -> Result {
|
||||
if !services.server.config.allow_federation {
|
||||
return Err!(Config("allow_federation", "Federation is disabled."));
|
||||
}
|
||||
|
||||
let destination = services.globals.server_name();
|
||||
if x_matrix.destination.as_deref() != Some(destination) {
|
||||
return Err!(Request(Forbidden("Invalid destination.")));
|
||||
}
|
||||
|
||||
let origin = &x_matrix.origin;
|
||||
if services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(origin.host())
|
||||
{
|
||||
return Err!(Request(Forbidden(debug_warn!(
|
||||
"Federation requests from {origin} denied."
|
||||
))));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn parse_x_matrix(request: &mut Request) -> Result<XMatrix> {
|
||||
let TypedHeader(Authorization(x_matrix)) = request
|
||||
.parts
|
||||
.extract::<TypedHeader<Authorization<XMatrix>>>()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let msg = match e.reason() {
|
||||
| TypedHeaderRejectionReason::Missing => "Missing Authorization header.",
|
||||
| TypedHeaderRejectionReason::Error(_) => "Invalid X-Matrix signatures.",
|
||||
| _ => "Unknown header-related error",
|
||||
};
|
||||
|
||||
err!(Request(Forbidden(warn!("{msg}: {e}"))))
|
||||
})?;
|
||||
|
||||
Ok(x_matrix)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
use ruma::{OwnedUserId, UserId};
|
||||
use tuwunel_core::{Err, Result};
|
||||
use tuwunel_service::{Services, appservice::RegistrationInfo};
|
||||
|
||||
use super::{Auth, Request};
|
||||
|
||||
pub(super) async fn auth_appservice(
|
||||
services: &Services,
|
||||
request: &Request,
|
||||
info: Box<RegistrationInfo>,
|
||||
) -> Result<Auth> {
|
||||
let user_id_default = || {
|
||||
UserId::parse_with_server_name(
|
||||
info.registration.sender_localpart.as_str(),
|
||||
services.globals.server_name(),
|
||||
)
|
||||
};
|
||||
|
||||
let Ok(user_id) = request
|
||||
.query
|
||||
.user_id
|
||||
.clone()
|
||||
.map_or_else(user_id_default, OwnedUserId::parse)
|
||||
else {
|
||||
return Err!(Request(InvalidUsername("Username is invalid.")));
|
||||
};
|
||||
|
||||
if !info.is_user_match(&user_id) {
|
||||
return Err!(Request(Exclusive("User is not in namespace.")));
|
||||
}
|
||||
|
||||
Ok(Auth {
|
||||
sender_user: Some(user_id),
|
||||
appservice_info: Some(*info),
|
||||
..Auth::default()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
use axum::RequestPartsExt;
|
||||
use axum_extra::{TypedHeader, headers::Authorization, typed_header::TypedHeaderRejectionReason};
|
||||
use ruma::{CanonicalJsonObject, CanonicalJsonValue, api::federation::authentication::XMatrix};
|
||||
use tuwunel_core::{Err, Result, debug_error, err, warn};
|
||||
use tuwunel_service::{
|
||||
Services,
|
||||
server_keys::{PubKeyMap, PubKeys},
|
||||
};
|
||||
|
||||
use super::{Auth, Request};
|
||||
|
||||
pub(super) async fn auth_server(
|
||||
services: &Services,
|
||||
request: &mut Request,
|
||||
body: Option<&CanonicalJsonValue>,
|
||||
) -> Result<Auth> {
|
||||
type Member = (String, CanonicalJsonValue);
|
||||
type Object = CanonicalJsonObject;
|
||||
type Value = CanonicalJsonValue;
|
||||
|
||||
let x_matrix = parse_x_matrix(request).await?;
|
||||
auth_server_checks(services, &x_matrix)?;
|
||||
|
||||
let destination = services.globals.server_name();
|
||||
let origin = &x_matrix.origin;
|
||||
let signature_uri = request
|
||||
.parts
|
||||
.uri
|
||||
.path_and_query()
|
||||
.expect("all requests have a path")
|
||||
.to_string();
|
||||
|
||||
let signature: [Member; 1] =
|
||||
[(x_matrix.key.as_str().into(), Value::String(x_matrix.sig.to_string()))];
|
||||
|
||||
let signatures: [Member; 1] = [(origin.as_str().into(), Value::Object(signature.into()))];
|
||||
|
||||
let authorization: Object = if let Some(body) = body.cloned() {
|
||||
let authorization: [Member; 6] = [
|
||||
("content".into(), body),
|
||||
("destination".into(), Value::String(destination.into())),
|
||||
("method".into(), Value::String(request.parts.method.as_str().into())),
|
||||
("origin".into(), Value::String(origin.as_str().into())),
|
||||
("signatures".into(), Value::Object(signatures.into())),
|
||||
("uri".into(), Value::String(signature_uri)),
|
||||
];
|
||||
|
||||
authorization.into()
|
||||
} else {
|
||||
let authorization: [Member; 5] = [
|
||||
("destination".into(), Value::String(destination.into())),
|
||||
("method".into(), Value::String(request.parts.method.as_str().into())),
|
||||
("origin".into(), Value::String(origin.as_str().into())),
|
||||
("signatures".into(), Value::Object(signatures.into())),
|
||||
("uri".into(), Value::String(signature_uri)),
|
||||
];
|
||||
|
||||
authorization.into()
|
||||
};
|
||||
|
||||
let key = services
|
||||
.server_keys
|
||||
.get_verify_key(origin, &x_matrix.key)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(Forbidden(debug_warn!("Failed to fetch signing keys: {e}"))))
|
||||
})?;
|
||||
|
||||
let keys: PubKeys = [(x_matrix.key.to_string(), key.key)].into();
|
||||
let keys: PubKeyMap = [(origin.as_str().into(), keys)].into();
|
||||
if let Err(e) = ruma::signatures::verify_json(&keys, &authorization) {
|
||||
debug_error!("Failed to verify federation request from {origin}: {e}");
|
||||
if request.parts.uri.to_string().contains('@') {
|
||||
warn!(
|
||||
"Request uri contained '@' character. Make sure your reverse proxy gives \
|
||||
tuwunel the raw uri (apache: use nocanon)"
|
||||
);
|
||||
}
|
||||
|
||||
return Err!(Request(Forbidden("Failed to verify X-Matrix signatures.")));
|
||||
}
|
||||
|
||||
Ok(Auth {
|
||||
origin: origin.to_owned().into(),
|
||||
..Auth::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn auth_server_checks(services: &Services, x_matrix: &XMatrix) -> Result {
|
||||
if !services.server.config.allow_federation {
|
||||
return Err!(Config("allow_federation", "Federation is disabled."));
|
||||
}
|
||||
|
||||
let destination = services.globals.server_name();
|
||||
if x_matrix.destination.as_deref() != Some(destination) {
|
||||
return Err!(Request(Forbidden("Invalid destination.")));
|
||||
}
|
||||
|
||||
let origin = &x_matrix.origin;
|
||||
if services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(origin.host())
|
||||
{
|
||||
return Err!(Request(Forbidden(debug_warn!(
|
||||
"Federation requests from {origin} denied."
|
||||
))));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn parse_x_matrix(request: &mut Request) -> Result<XMatrix> {
|
||||
let TypedHeader(Authorization(x_matrix)) = request
|
||||
.parts
|
||||
.extract::<TypedHeader<Authorization<XMatrix>>>()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let msg = match e.reason() {
|
||||
| TypedHeaderRejectionReason::Missing => "Missing Authorization header.",
|
||||
| TypedHeaderRejectionReason::Error(_) => "Invalid X-Matrix signatures.",
|
||||
| _ => "Unknown header-related error",
|
||||
};
|
||||
|
||||
err!(Request(Forbidden(debug_warn!("{msg}: {e}"))))
|
||||
})?;
|
||||
|
||||
Ok(x_matrix)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
use ruma::{
|
||||
CanonicalJsonValue, OwnedUserId,
|
||||
api::{
|
||||
IncomingRequest,
|
||||
client::uiaa::{AuthData, AuthFlow, AuthType, Jwt, UiaaInfo},
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{Err, Error, Result, err, utils};
|
||||
use tuwunel_service::{Services, uiaa::SESSION_ID_LENGTH};
|
||||
|
||||
use crate::{Ruma, client::jwt};
|
||||
|
||||
pub(crate) async fn auth_uiaa<T>(services: &Services, body: &Ruma<T>) -> Result<OwnedUserId>
|
||||
where
|
||||
T: IncomingRequest + Send + Sync,
|
||||
{
|
||||
let flows = [
|
||||
AuthFlow::new([AuthType::Password].into()),
|
||||
AuthFlow::new([AuthType::Jwt].into()),
|
||||
];
|
||||
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: flows.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match body
|
||||
.json_body
|
||||
.as_ref()
|
||||
.and_then(CanonicalJsonValue::as_object)
|
||||
.and_then(|body| body.get("auth"))
|
||||
.cloned()
|
||||
.map(CanonicalJsonValue::into)
|
||||
.map(serde_json::from_value)
|
||||
.transpose()?
|
||||
{
|
||||
| Some(AuthData::Jwt(Jwt { ref token, .. })) => {
|
||||
let sender_user = jwt::validate_user(services, token)?;
|
||||
if !services.users.exists(&sender_user).await {
|
||||
return Err!(Request(NotFound("User {sender_user} is not registered.")));
|
||||
}
|
||||
|
||||
// Success!
|
||||
Ok(sender_user)
|
||||
},
|
||||
| Some(ref auth) => {
|
||||
let sender_user = body
|
||||
.sender_user
|
||||
.as_deref()
|
||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
||||
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
|
||||
// Success!
|
||||
Ok(sender_user.to_owned())
|
||||
},
|
||||
| _ => match body.json_body {
|
||||
| Some(ref json) => {
|
||||
let sender_user = body
|
||||
.sender_user
|
||||
.as_ref()
|
||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
||||
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services
|
||||
.uiaa
|
||||
.create(sender_user, body.sender_device(), &uiaainfo, json);
|
||||
|
||||
Err(Error::Uiaa(uiaainfo))
|
||||
},
|
||||
| _ => Err!(Request(NotJson("JSON body is not valid"))),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,6 @@ pub(crate) async fn get_backfill_route(
|
||||
.stream()
|
||||
.filter_map(|event_id| {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_count(event_id)
|
||||
.map(Result::ok)
|
||||
@@ -60,13 +59,11 @@ pub(crate) async fn get_backfill_route(
|
||||
origin: services.globals.server_name().to_owned(),
|
||||
|
||||
pdus: services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(None, &body.room_id, Some(from.saturating_add(1)))
|
||||
.try_take(limit)
|
||||
.try_filter_map(async |(_, pdu)| {
|
||||
Ok(services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.server_can_see_event(body.origin(), &pdu.room_id, &pdu.event_id)
|
||||
.await
|
||||
@@ -74,7 +71,6 @@ pub(crate) async fn get_backfill_route(
|
||||
})
|
||||
.try_filter_map(async |pdu| {
|
||||
Ok(services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json(&pdu.event_id)
|
||||
.await
|
||||
@@ -82,8 +78,8 @@ pub(crate) async fn get_backfill_route(
|
||||
})
|
||||
.and_then(|pdu| {
|
||||
services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(pdu)
|
||||
.federation
|
||||
.format_pdu_into(pdu, None)
|
||||
.map(Ok)
|
||||
})
|
||||
.try_collect()
|
||||
|
||||
@@ -16,7 +16,6 @@ pub(crate) async fn get_event_route(
|
||||
body: Ruma<get_event::v1::Request>,
|
||||
) -> Result<get_event::v1::Response> {
|
||||
let event = services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json(&body.event_id)
|
||||
.await
|
||||
@@ -42,8 +41,8 @@ pub(crate) async fn get_event_route(
|
||||
origin: services.globals.server_name().to_owned(),
|
||||
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
|
||||
pdu: services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(event)
|
||||
.federation
|
||||
.format_pdu_into(event, None)
|
||||
.await,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ pub(crate) async fn get_event_authorization_route(
|
||||
.await?;
|
||||
|
||||
let event = services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json(&body.event_id)
|
||||
.await
|
||||
@@ -45,23 +44,11 @@ pub(crate) async fn get_event_authorization_route(
|
||||
.map_err(|_| Error::bad_database("Invalid room_id in event in database."))?;
|
||||
|
||||
let auth_chain = services
|
||||
.rooms
|
||||
.auth_chain
|
||||
.event_ids_iter(room_id, once(body.event_id.borrow()))
|
||||
.ready_filter_map(Result::ok)
|
||||
.filter_map(async |id| {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json(&id)
|
||||
.await
|
||||
.ok()
|
||||
})
|
||||
.then(|pdu| {
|
||||
services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(pdu)
|
||||
})
|
||||
.filter_map(async |id| services.timeline.get_pdu_json(&id).await.ok())
|
||||
.then(|pdu| services.federation.format_pdu_into(pdu, None))
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user