Compare commits

..

1 Commits

Author SHA1 Message Date
Jade Ellis 7a8c409ff9 refactor: Use snafu
This should gradtly improve the debugging experience by allowing
tracking backtraces to get more context from the error.
A lot of the touced files here are just cleaning up the old way of
creating errors.
2026-02-22 14:06:15 +00:00
133 changed files with 1556 additions and 2603 deletions
+12 -12
View File
@@ -30,22 +30,22 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "distribution=$DISTRIBUTION" >> $GITHUB_OUTPUT echo "distribution=$DISTRIBUTION" >> $GITHUB_OUTPUT
echo "Debian distribution: $DISTRIBUTION ($VERSION)" echo "Debian distribution: $DISTRIBUTION ($VERSION)"
#- name: Work around llvm-project#153385 - name: Work around llvm-project#153385
# id: llvm-workaround id: llvm-workaround
# run: | run: |
# if [ -f /usr/share/apt/default-sequoia.config ]; then if [ -f /usr/share/apt/default-sequoia.config ]; then
# echo "Applying workaround for llvm-project#153385" echo "Applying workaround for llvm-project#153385"
# mkdir -p /etc/crypto-policies/back-ends/ mkdir -p /etc/crypto-policies/back-ends/
# cp /usr/share/apt/default-sequoia.config /etc/crypto-policies/back-ends/apt-sequoia.config cp /usr/share/apt/default-sequoia.config /etc/crypto-policies/back-ends/apt-sequoia.config
# sed -i 's/\(sha1\.second_preimage_resistance = \)2026-02-01/\12026-06-01/' /etc/crypto-policies/back-ends/apt-sequoia.config sed -i 's/\(sha1\.second_preimage_resistance = \)2026-02-01/\12026-06-01/' /etc/crypto-policies/back-ends/apt-sequoia.config
# else else
# echo "No workaround needed for llvm-project#153385" echo "No workaround needed for llvm-project#153385"
# fi fi
- name: Pick compatible clang version - name: Pick compatible clang version
id: clang-version id: clang-version
run: | run: |
# both latest need to use clang-23, but oldstable and previous can just use clang # both latest need to use clang-23, but oldstable and previous can just use clang
if [[ "${{ matrix.container }}" == "ubuntu-latest" ]]; then if [[ "${{ matrix.container }}" == "ubuntu-latest" || "${{ matrix.container }}" == "debian-latest" ]]; then
echo "Using clang-23 package for ${{ matrix.container }}" echo "Using clang-23 package for ${{ matrix.container }}"
echo "version=clang-23" >> $GITHUB_OUTPUT echo "version=clang-23" >> $GITHUB_OUTPUT
else else
+2 -14
View File
@@ -1,6 +1,5 @@
default_install_hook_types: default_install_hook_types:
- pre-commit - pre-commit
- pre-push
- commit-msg - commit-msg
default_stages: default_stages:
- pre-commit - pre-commit
@@ -24,7 +23,7 @@ repos:
- id: check-added-large-files - id: check-added-large-files
- repo: https://github.com/crate-ci/typos - repo: https://github.com/crate-ci/typos
rev: v1.44.0 rev: v1.43.5
hooks: hooks:
- id: typos - id: typos
- id: typos - id: typos
@@ -32,7 +31,7 @@ repos:
stages: [commit-msg] stages: [commit-msg]
- repo: https://github.com/crate-ci/committed - repo: https://github.com/crate-ci/committed
rev: v1.1.11 rev: v1.1.10
hooks: hooks:
- id: committed - id: committed
@@ -46,14 +45,3 @@ repos:
pass_filenames: false pass_filenames: false
stages: stages:
- pre-commit - pre-commit
- repo: local
hooks:
- id: cargo-clippy
name: cargo clippy
entry: cargo clippy -- -D warnings
language: system
pass_filenames: false
types: [rust]
stages:
- pre-push
-29
View File
@@ -1,32 +1,3 @@
# Continuwuity 0.5.6 (2026-03-03)
## Security
- Admin escape commands received over federation will never be executed, as this is never valid in a genuine situation. Contributed by @Jade.
- Fixed data amplification vulnerability (CWE-409) that affected configurations with server-side compression enabled (non-default). Contributed by @nex.
## Features
- Outgoing presence is now disabled by default, and the config option documentation has been adjusted to more accurately represent the weight of presence, typing indicators, and read receipts. Contributed by @nex. ([#1399](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1399))
- Improved the concurrency handling of federation transactions, vastly improving performance and reliability by more accurately handling inbound transactions and reducing the amount of repeated wasted work. Contributed by @nex and @Jade. ([#1428](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1428))
- Added [MSC3202](https://github.com/matrix-org/matrix-spec-proposals/pull/3202) Device masquerading (not all of MSC3202). This should fix issues with enabling [MSC4190](https://github.com/matrix-org/matrix-spec-proposals/pull/4190) for some Mautrix bridges. Contributed by @Jade ([#1435](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1435))
- Added [MSC3814](https://github.com/matrix-org/matrix-spec-proposals/pull/3814) Dehydrated Devices - you can now decrypt messages sent while all devices were logged out. ([#1436](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1436))
- Implement [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) MatrixRTC transport discovery endpoint. Move RTC foci configuration from `[global.well_known]` to a new `[global.matrix_rtc]` section with a `foci` field. Contributed by @0xnim ([#1442](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1442))
- Updated `list-backups` admin command to output one backup per line. ([#1394](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1394))
- Improved URL preview fetching with a more compatible user agent for sites like YouTube Music. Added `!admin media delete-url-preview <url>` command to clear cached URL previews that were stuck and broken. ([#1434](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1434))
## Bugfixes
- Removed non-compliant nor functional room alias lookups over federation. Contributed by @nex ([#1393](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1393))
- Removed ability to set rocksdb as read only. Doing so would cause unintentional and buggy behaviour. Contributed by @Terryiscool160. ([#1418](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1418))
- Fixed a startup crash in the sender service if we can't detect the number of CPU cores, even if the `sender_workers` config option is set correctly. Contributed by @katie. ([#1421](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1421))
- Removed the `allow_public_room_directory_without_auth` config option. Contributed by @0xnim. ([#1441](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1441))
- Fixed sliding sync v5 list ranges always starting from 0, causing extra rooms to be unnecessarily processed and returned. Contributed by @0xnim ([#1445](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1445))
- Fixed a bug that (repairably) caused a room split between continuwuity and non-continuwuity servers when the room had both `m.room.policy` and `org.matrix.msc4284.policy` in its room state. Contributed by @nex ([#1481](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1481))
- Fixed `!admin media delete --mxc <url>` responding with an error message when the media was deleted successfully. Contributed by @lynxize
- Fixed spurious 404 media errors in the logs. Contributed by @benbot.
- Fixed spurious warn about needed backfill via federation for non-federated rooms. Contributed by @kraem.
# Continuwuity v0.5.5 (2026-02-15) # Continuwuity v0.5.5 (2026-02-15)
## Features ## Features
Generated
+53 -45
View File
@@ -445,14 +445,13 @@ dependencies = [
[[package]] [[package]]
name = "axum-extra" name = "axum-extra"
version = "0.12.5" version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fef252edff26ddba56bbcdf2ee3307b8129acb86f5749b68990c168a6fcc9c76" checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96"
dependencies = [ dependencies = [
"axum", "axum",
"axum-core", "axum-core",
"bytes", "bytes",
"futures-core",
"futures-util", "futures-util",
"headers", "headers",
"http", "http",
@@ -460,6 +459,8 @@ dependencies = [
"http-body-util", "http-body-util",
"mime", "mime",
"pin-project-lite", "pin-project-lite",
"rustversion",
"serde_core",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing", "tracing",
@@ -887,7 +888,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit" name = "conduwuit"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"clap", "clap",
"conduwuit_admin", "conduwuit_admin",
@@ -919,7 +920,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_admin" name = "conduwuit_admin"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"clap", "clap",
"conduwuit_api", "conduwuit_api",
@@ -940,7 +941,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_api" name = "conduwuit_api"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum", "axum",
@@ -972,14 +973,14 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_build_metadata" name = "conduwuit_build_metadata"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"built", "built",
] ]
[[package]] [[package]]
name = "conduwuit_core" name = "conduwuit_core"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"argon2", "argon2",
"arrayvec", "arrayvec",
@@ -1013,6 +1014,7 @@ dependencies = [
"nix", "nix",
"num-traits", "num-traits",
"parking_lot", "parking_lot",
"paste",
"rand 0.10.0", "rand 0.10.0",
"rand_core 0.6.4", "rand_core 0.6.4",
"regex", "regex",
@@ -1026,7 +1028,7 @@ dependencies = [
"serde_regex", "serde_regex",
"smallstr", "smallstr",
"smallvec", "smallvec",
"thiserror 2.0.18", "snafu",
"tikv-jemalloc-ctl", "tikv-jemalloc-ctl",
"tikv-jemalloc-sys", "tikv-jemalloc-sys",
"tikv-jemallocator", "tikv-jemallocator",
@@ -1041,7 +1043,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_database" name = "conduwuit_database"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"conduwuit_core", "conduwuit_core",
@@ -1059,7 +1061,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_macros" name = "conduwuit_macros"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"itertools 0.14.0", "itertools 0.14.0",
"proc-macro2", "proc-macro2",
@@ -1069,7 +1071,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_router" name = "conduwuit_router"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"axum", "axum",
"axum-client-ip", "axum-client-ip",
@@ -1103,7 +1105,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_service" name = "conduwuit_service"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"askama", "askama",
"async-trait", "async-trait",
@@ -1145,7 +1147,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_web" name = "conduwuit_web"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"askama", "askama",
"axum", "axum",
@@ -1153,7 +1155,7 @@ dependencies = [
"conduwuit_service", "conduwuit_service",
"futures", "futures",
"rand 0.10.0", "rand 0.10.0",
"thiserror 2.0.18", "snafu",
"tracing", "tracing",
] ]
@@ -1221,7 +1223,7 @@ dependencies = [
[[package]] [[package]]
name = "continuwuity-admin-api" name = "continuwuity-admin-api"
version = "0.1.0" version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"ruma-common", "ruma-common",
"serde", "serde",
@@ -1600,7 +1602,7 @@ dependencies = [
[[package]] [[package]]
name = "draupnir-antispam" name = "draupnir-antispam"
version = "0.1.0" version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"ruma-common", "ruma-common",
"serde", "serde",
@@ -3002,7 +3004,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]] [[package]]
name = "meowlnir-antispam" name = "meowlnir-antispam"
version = "0.1.0" version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"ruma-common", "ruma-common",
"serde", "serde",
@@ -4055,14 +4057,12 @@ dependencies = [
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"tokio-util",
"tower", "tower",
"tower-http", "tower-http",
"tower-service", "tower-service",
"url", "url",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-streams",
"web-sys", "web-sys",
"webpki-roots", "webpki-roots",
] ]
@@ -4096,7 +4096,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma" name = "ruma"
version = "0.10.1" version = "0.10.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"assign", "assign",
"continuwuity-admin-api", "continuwuity-admin-api",
@@ -4119,7 +4119,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-appservice-api" name = "ruma-appservice-api"
version = "0.10.0" version = "0.10.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@@ -4131,7 +4131,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-client-api" name = "ruma-client-api"
version = "0.18.0" version = "0.18.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"assign", "assign",
@@ -4154,7 +4154,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-common" name = "ruma-common"
version = "0.13.0" version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"base64 0.22.1", "base64 0.22.1",
@@ -4186,7 +4186,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-events" name = "ruma-events"
version = "0.28.1" version = "0.28.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"indexmap", "indexmap",
@@ -4211,7 +4211,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-federation-api" name = "ruma-federation-api"
version = "0.9.0" version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"bytes", "bytes",
"headers", "headers",
@@ -4233,7 +4233,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identifiers-validation" name = "ruma-identifiers-validation"
version = "0.9.5" version = "0.9.5"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"js_int", "js_int",
"thiserror 2.0.18", "thiserror 2.0.18",
@@ -4242,7 +4242,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identity-service-api" name = "ruma-identity-service-api"
version = "0.9.0" version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@@ -4252,7 +4252,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-macros" name = "ruma-macros"
version = "0.13.0" version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"proc-macro-crate", "proc-macro-crate",
@@ -4267,7 +4267,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-push-gateway-api" name = "ruma-push-gateway-api"
version = "0.9.0" version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@@ -4279,7 +4279,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-signatures" name = "ruma-signatures"
version = "0.15.0" version = "0.15.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=bb12ed288a31a23aa11b10ba0fad22b7f985eb88#bb12ed288a31a23aa11b10ba0fad22b7f985eb88" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e087ff15888156942ca2ffe6097d1b4c3fd27628#e087ff15888156942ca2ffe6097d1b4c3fd27628"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"ed25519-dalek", "ed25519-dalek",
@@ -4912,6 +4912,27 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "snafu"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2"
dependencies = [
"snafu-derive",
]
[[package]]
name = "snafu-derive"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.10" version = "0.5.10"
@@ -5870,19 +5891,6 @@ dependencies = [
"wasmparser", "wasmparser",
] ]
[[package]]
name = "wasm-streams"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]] [[package]]
name = "wasmparser" name = "wasmparser"
version = "0.244.0" version = "0.244.0"
@@ -6347,7 +6355,7 @@ dependencies = [
[[package]] [[package]]
name = "xtask" name = "xtask"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"askama", "askama",
"cargo_metadata", "cargo_metadata",
+10 -8
View File
@@ -12,7 +12,7 @@ license = "Apache-2.0"
# See also `rust-toolchain.toml` # See also `rust-toolchain.toml`
readme = "README.md" readme = "README.md"
repository = "https://forgejo.ellis.link/continuwuation/continuwuity" repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
version = "0.5.6" version = "0.5.5"
[workspace.metadata.crane] [workspace.metadata.crane]
name = "conduwuit" name = "conduwuit"
@@ -97,7 +97,7 @@ features = [
] ]
[workspace.dependencies.axum-extra] [workspace.dependencies.axum-extra]
version = "0.12.0" version = "0.10.1"
default-features = false default-features = false
features = ["typed-header", "tracing"] features = ["typed-header", "tracing"]
@@ -144,7 +144,6 @@ features = [
"socks", "socks",
"hickory-dns", "hickory-dns",
"http2", "http2",
"stream",
] ]
[workspace.dependencies.serde] [workspace.dependencies.serde]
@@ -308,9 +307,14 @@ features = [
] ]
# Used for conduwuit::Error type # Used for conduwuit::Error type
[workspace.dependencies.thiserror] [workspace.dependencies.snafu]
version = "2.0.12" version = "0.8"
default-features = false default-features = false
features = ["std", "rust_1_81"]
# Used for macro name generation
[workspace.dependencies.paste]
version = "1.0"
# Used when hashing the state # Used when hashing the state
[workspace.dependencies.ring] [workspace.dependencies.ring]
@@ -344,7 +348,7 @@ version = "0.1.2"
[workspace.dependencies.ruma] [workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma" git = "https://forgejo.ellis.link/continuwuation/ruwuma"
#branch = "conduwuit-changes" #branch = "conduwuit-changes"
rev = "bb12ed288a31a23aa11b10ba0fad22b7f985eb88" rev = "e087ff15888156942ca2ffe6097d1b4c3fd27628"
features = [ features = [
"compat", "compat",
"rand", "rand",
@@ -364,7 +368,6 @@ features = [
"unstable-msc2870", "unstable-msc2870",
"unstable-msc3026", "unstable-msc3026",
"unstable-msc3061", "unstable-msc3061",
"unstable-msc3814",
"unstable-msc3245", "unstable-msc3245",
"unstable-msc3266", "unstable-msc3266",
"unstable-msc3381", # polls "unstable-msc3381", # polls
@@ -383,7 +386,6 @@ features = [
"unstable-pdu", "unstable-pdu",
"unstable-msc4155", "unstable-msc4155",
"unstable-msc4143", # livekit well_known response "unstable-msc4143", # livekit well_known response
"unstable-msc4284"
] ]
[workspace.dependencies.rust-rocksdb] [workspace.dependencies.rust-rocksdb]
+3 -8
View File
@@ -57,15 +57,10 @@ Continuwuity aims to:
### Can I try it out? ### Can I try it out?
Check out the [documentation](https://continuwuity.org) for installation instructions. Check out the [documentation](https://continuwuity.org) for installation instructions, or join one of these vetted public homeservers running Continuwuity to get a feel for things!
If you want to try it out as a user, we have some partnered homeservers you can use: - https://continuwuity.rocks -- A public demo server operated by the Continuwuity Team.
* You can head over to [https://federated.nexus](https://federated.nexus/) in your browser. - https://federated.nexus -- Federated Nexus is a community resource hosting multiple FOSS (especially federated) services, including Matrix and Forgejo.
* Hit the `Apply to Join` button. Once your request has been accepted, you will receive an email with your username and password.
* Head over to [https://app.federated.nexus](https://app.federated.nexus/) and you can sign in there, or use any other matrix chat client you wish elsewhere.
* Your username for matrix will be in the form of `@username:federated.nexus`, however you can simply use the `username` part to log in. Your password is your password.
* There's also [https://continuwuity.rocks/](https://continuwuity.rocks/). You can register a new account using Cinny via [this convenient link](https://app.cinny.in/register/continuwuity.rocks), or you can use Element or another matrix client *that supports registration*.
### What are we working on? ### What are we working on?
+2 -2
View File
@@ -6,10 +6,10 @@ set -euo pipefail
COMPLEMENT_SRC="${COMPLEMENT_SRC:-$1}" COMPLEMENT_SRC="${COMPLEMENT_SRC:-$1}"
# A `.jsonl` file to write test logs to # A `.jsonl` file to write test logs to
LOG_FILE="${2:-tests/test_results/complement/test_logs.jsonl}" LOG_FILE="${2:-complement_test_logs.jsonl}"
# A `.jsonl` file to write test results to # A `.jsonl` file to write test results to
RESULTS_FILE="${3:-tests/test_results/complement/test_results.jsonl}" RESULTS_FILE="${3:-complement_test_results.jsonl}"
# The base docker image to use for complement tests # The base docker image to use for complement tests
# You can build the default with `docker build -t continuwuity:complement -f ./docker/complement.Dockerfile .` # You can build the default with `docker build -t continuwuity:complement -f ./docker/complement.Dockerfile .`
-1
View File
@@ -1 +0,0 @@
Stopped left rooms from being unconditionally sent on initial sync, hopefully fixing spurious appearances of left rooms in some clients (and making sync faster as a bonus). Contributed by @ginger
-1
View File
@@ -1 +0,0 @@
Re-added support for reading registration tokens from a file. Contributed by @ginger and @benbot.
+1
View File
@@ -0,0 +1 @@
Removed non-compliant nor functional room alias lookups over federation. Contributed by @nex
+1
View File
@@ -0,0 +1 @@
Outgoing presence is now disabled by default, and the config option documentation has been adjusted to more accurately represent the weight of presence, typing indicators, and read receipts. Contributed by @nex.
+1
View File
@@ -0,0 +1 @@
Removed ability to set rocksdb as read only. Doing so would cause unintentional and buggy behaviour. Contributed by @Terryiscool160.
+1
View File
@@ -0,0 +1 @@
Fixed a startup crash in the sender service if we can't detect the number of CPU cores, even if the `sender_workers' config option is set correctly. Contributed by @katie.
-1
View File
@@ -1 +0,0 @@
Prevent removing the admin room alias (`#admins`) to avoid accidentally breaking admin room functionality. Contributed by @0xnim
@@ -0,0 +1 @@
Updated `list-backups` admin command to output one backup per line.
+3 -15
View File
@@ -15,18 +15,6 @@ disallowed-macros = [
{ path = "log::trace", reason = "use conduwuit_core::trace" }, { path = "log::trace", reason = "use conduwuit_core::trace" },
] ]
[[disallowed-methods]] disallowed-methods = [
path = "tokio::spawn" { path = "tokio::spawn", reason = "use and pass conduuwit_core::server::Server::runtime() to spawn from" },
reason = "use and pass conduwuit_core::server::Server::runtime() to spawn from" ]
[[disallowed-methods]]
path = "reqwest::Response::bytes"
reason = "bytes is unsafe, use limit_read via the conduwuit_core::utils::LimitReadExt trait instead"
[[disallowed-methods]]
path = "reqwest::Response::text"
reason = "text is unsafe, use limit_read_text via the conduwuit_core::utils::LimitReadExt trait instead"
[[disallowed-methods]]
path = "reqwest::Response::json"
reason = "json is unsafe, use limit_read_text via the conduwuit_core::utils::LimitReadExt trait instead"
+3 -3
View File
@@ -9,9 +9,10 @@ address = "0.0.0.0"
allow_device_name_federation = true allow_device_name_federation = true
allow_guest_registration = true allow_guest_registration = true
allow_public_room_directory_over_federation = true allow_public_room_directory_over_federation = true
allow_public_room_directory_without_auth = true
allow_registration = true allow_registration = true
database_path = "/database" database_path = "/database"
log = "trace,h2=debug,hyper=debug,conduwuit_database=warn,conduwuit_service::manager=info,conduwuit_api::router=error,conduwuit_router=error,tower_http=error" log = "trace,h2=debug,hyper=debug"
port = [8008, 8448] port = [8008, 8448]
trusted_servers = [] trusted_servers = []
only_query_trusted_key_servers = false only_query_trusted_key_servers = false
@@ -24,7 +25,7 @@ url_preview_domain_explicit_denylist = ["*"]
media_compat_file_link = false media_compat_file_link = false
media_startup_check = true media_startup_check = true
prune_missing_media = true prune_missing_media = true
log_colors = false log_colors = true
admin_room_notices = false admin_room_notices = false
allow_check_for_updates = false allow_check_for_updates = false
intentionally_unknown_config_option_for_testing = true intentionally_unknown_config_option_for_testing = true
@@ -47,7 +48,6 @@ federation_idle_timeout = 300
sender_timeout = 300 sender_timeout = 300
sender_idle_timeout = 300 sender_idle_timeout = 300
sender_retry_backoff_limit = 300 sender_retry_backoff_limit = 300
force_disable_first_run_mode = true
[global.tls] [global.tls]
dual_protocol = true dual_protocol = true
+19 -55
View File
@@ -290,25 +290,6 @@
# #
#max_fetch_prev_events = 192 #max_fetch_prev_events = 192
# How many incoming federation transactions the server is willing to be
# processing at any given time before it becomes overloaded and starts
# rejecting further transactions until some slots become available.
#
# Setting this value too low or too high may result in unstable
# federation, and setting it too high may cause runaway resource usage.
#
#max_concurrent_inbound_transactions = 150
# Maximum age (in seconds) for cached federation transaction responses.
# Entries older than this will be removed during cleanup.
#
#transaction_id_cache_max_age_secs = 7200 (2 hours)
# Maximum number of cached federation transaction responses.
# When the cache exceeds this limit, older entries will be removed.
#
#transaction_id_cache_max_entries = 8192
# Default/base connection timeout (seconds). This is used only by URL # Default/base connection timeout (seconds). This is used only by URL
# previews and update/news endpoint checks. # previews and update/news endpoint checks.
# #
@@ -476,25 +457,18 @@
#yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = false #yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = false
# A static registration token that new users will have to provide when # A static registration token that new users will have to provide when
# creating an account. This token does not supersede tokens from other # creating an account. If unset and `allow_registration` is true,
# sources, such as the `!admin token` command or the # you must set
# `registration_token_file` configuration option. # `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
# to true to allow open registration without any conditions.
#
# If you do not want to set a static token, the `!admin token` commands
# may also be used to manage registration tokens.
# #
# example: "o&^uCtes4HPf0Vu@F20jQeeWE7" # example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
# #
#registration_token = #registration_token =
# A path to a file containing static registration tokens, one per line.
# Tokens in this file do not supersede tokens from other sources, such as
# the `!admin token` command or the `registration_token` configuration
# option.
#
# The file will be read once, when Continuwuity starts. It is not
# currently reread when the server configuration is reloaded. If the file
# cannot be read, Continuwuity will fail to start.
#
#registration_token_file =
# The public site key for reCaptcha. If this is provided, reCaptcha # The public site key for reCaptcha. If this is provided, reCaptcha
# becomes required during registration. If both captcha *and* # becomes required during registration. If both captcha *and*
# registration token are enabled, both will be required during # registration token are enabled, both will be required during
@@ -553,6 +527,12 @@
# #
#allow_public_room_directory_over_federation = false #allow_public_room_directory_over_federation = false
# Set this to true to allow your server's public room directory to be
# queried without client authentication (access token) through the Client
# APIs. Set this to false to protect against /publicRooms spiders.
#
#allow_public_room_directory_without_auth = false
# Allow guests/unauthenticated users to access TURN credentials. # Allow guests/unauthenticated users to access TURN credentials.
# #
# This is the equivalent of Synapse's `turn_allow_guests` config option. # This is the equivalent of Synapse's `turn_allow_guests` config option.
@@ -1851,13 +1831,14 @@
# #
#support_mxid = #support_mxid =
# **DEPRECATED**: Use `[global.matrix_rtc].foci` instead.
#
# A list of MatrixRTC foci URLs which will be served as part of the # A list of MatrixRTC foci URLs which will be served as part of the
# MSC4143 client endpoint at /.well-known/matrix/client. # MSC4143 client endpoint at /.well-known/matrix/client. If you're
# setting up livekit, you'd want something like:
# rtc_focus_server_urls = [
# { type = "livekit", livekit_service_url = "https://livekit.example.com" },
# ]
# #
# This option is deprecated and will be removed in a future release. # To disable, set this to be an empty vector (`[]`).
# Please migrate to the new `[global.matrix_rtc]` config section.
# #
#rtc_focus_server_urls = [] #rtc_focus_server_urls = []
@@ -1879,23 +1860,6 @@
# #
#blurhash_max_raw_size = 33554432 #blurhash_max_raw_size = 33554432
[global.matrix_rtc]
# A list of MatrixRTC foci (transports) which will be served via the
# MSC4143 RTC transports endpoint at
# `/_matrix/client/v1/rtc/transports`. If you're setting up livekit,
# you'd want something like:
# ```toml
# [global.matrix_rtc]
# foci = [
# { type = "livekit", livekit_service_url = "https://livekit.example.com" },
# ]
# ```
#
# To disable, set this to an empty list (`[]`).
#
#foci = []
[global.ldap] [global.ldap]
# Whether to enable LDAP login. # Whether to enable LDAP login.
+3 -8
View File
@@ -48,7 +48,7 @@ EOF
# Developer tool versions # Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall # renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.17.6 ENV BINSTALL_VERSION=1.17.5
# renovate: datasource=github-releases depName=psastras/sbom-rs # renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1 ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree # renovate: datasource=crate depName=lddtree
@@ -180,11 +180,6 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
export RUSTFLAGS="${RUSTFLAGS}" export RUSTFLAGS="${RUSTFLAGS}"
fi fi
RUST_PROFILE_DIR="${RUST_PROFILE}"
if [[ "${RUST_PROFILE}" == "dev" ]]; then
RUST_PROFILE_DIR="debug"
fi
TARGET_DIR=($(cargo metadata --no-deps --format-version 1 | \ TARGET_DIR=($(cargo metadata --no-deps --format-version 1 | \
jq -r ".target_directory")) jq -r ".target_directory"))
mkdir /out/sbin mkdir /out/sbin
@@ -196,8 +191,8 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name")) jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
for BINARY in "${BINARIES[@]}"; do for BINARY in "${BINARIES[@]}"; do
echo $BINARY echo $BINARY
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE_DIR}/$BINARY xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE_DIR}/$BINARY /out/sbin/$BINARY cp $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY /out/sbin/$BINARY
done done
EOF EOF
+1 -1
View File
@@ -18,7 +18,7 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
# Developer tool versions # Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall # renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.17.6 ENV BINSTALL_VERSION=1.17.5
# renovate: datasource=github-releases depName=psastras/sbom-rs # renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1 ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree # renovate: datasource=crate depName=lddtree
-5
View File
@@ -34,11 +34,6 @@
"name": "troubleshooting", "name": "troubleshooting",
"label": "Troubleshooting" "label": "Troubleshooting"
}, },
{
"type": "dir",
"name": "advanced",
"label": "Advanced"
},
"security", "security",
{ {
"type": "dir-section-header", "type": "dir-section-header",
+1 -1
View File
@@ -2,7 +2,7 @@
{ {
"text": "Guide", "text": "Guide",
"link": "/introduction", "link": "/introduction",
"activeMatch": "^/(introduction|configuration|deploying|calls|appservices|maintenance|troubleshooting|advanced)" "activeMatch": "^/(introduction|configuration|deploying|calls|appservices|maintenance|troubleshooting)"
}, },
{ {
"text": "Development", "text": "Development",
-7
View File
@@ -1,7 +0,0 @@
[
{
"type": "file",
"name": "delegation",
"label": "Delegation / split-domain"
}
]
-209
View File
@@ -1,209 +0,0 @@
# Delegation/split-domain deployment
Matrix allows clients and servers to discover a homeserver's "true" destination via **`.well-known` delegation**. This is especially useful if you would like to:
- Serve Continuwuity on a subdomain while having only the base domain for your usernames
- Use a port other than `:8448` for server-to-server connections
This guide will show you how to have `@user:example.com` usernames while serving Continuwuity on `https://matrix.example.com`. It assumes you are using port 443 for both client-to-server connections and server-to-server federation.
## Configuration
First, ensure you have set up A/AAAA records for `matrix.example.com` and `example.com` pointing to your IP.
Then, ensure that the `server_name` field matches your intended username suffix. If this is not the case, you **MUST** wipe the database directory and reinstall Continuwuity with your desired `server_name`.
Then, in the `[global.well_known]` section of your config file, add the following fields:
```toml
[global.well_known]
client = "https://matrix.example.com"
# port number MUST be specified
server = "matrix.example.com:443"
# (optional) customize your support contacts
#support_page =
#support_role = "m.role.admin"
#support_email =
#support_mxid = "@user:example.com"
```
Alternatively if you are using Docker, you can set the `CONTINUWUITY_WELL_KNOWN` environment variable as below:
```yaml
services:
continuwuity:
...
environment:
CONTINUWUITY_WELL_KNOWN: |
{
client=https://matrix.example.com,
server=matrix.example.com:443
}
```
## Serving with a reverse proxy
After doing the steps above, Continuwuity will serve these 3 JSON files:
- `/.well-known/matrix/client`: for Client-Server discovery
- `/.well-known/matrix/server`: for Server-Server (federation) discovery
- `/.well-known/matrix/support`: admin contact details (strongly recommended to have)
To enable full discovery, you will need to reverse proxy these paths from the base domain back to Continuwuity.
<details>
<summary>For Caddy</summary>
```
matrix.example.com:443 {
reverse_proxy 127.0.0.1:8008
}
example.com:443 {
reverse_proxy /.well-known/matrix* 127.0.0.1:8008
}
```
</details>
<details>
<summary>For Traefik (via Docker labels)</summary>
```
services:
continuwuity:
...
labels:
- "traefik.enable=true"
- "traefik.http.routers.continuwuity.rule=(Host(`matrix.example.com`) || (Host(`example.com`) && PathPrefix(`/.well-known/matrix`)))"
- "traefik.http.routers.continuwuity.service=continuwuity"
- "traefik.http.services.continuwuity.loadbalancer.server.port=8008"
```
</details>
Restart Continuwuity and your reverse proxy. Once that's done, visit these routes and check that the responses match the examples below:
<details open>
<summary>`https://example.com/.well-known/matrix/server`</summary>
```json
{
"m.server": "matrix.example.com:443"
}
```
</details>
<details open>
<summary>`https://example.com/.well-known/matrix/client`</summary>
```json
{
"m.homeserver": {
"base_url": "https://matrix.example.com/"
},
"org.matrix.msc3575.proxy": {
"url": "https://matrix.example.com/"
}
}
```
</details>
## Troubleshooting
### Cannot log in with web clients
Make sure there is an `Access-Control-Allow-Origin: *` header in your `/.well-known/matrix/client` path. While Continuwuity serves this header by default, it may be dropped by reverse proxies or other middlewares.
---
## Using SRV records (not recommended)
:::warning
The following methods are **not recommended** due to increased complexity with little benefits. If you have already set up `.well-known` delegation as above, you can safely skip this part.
:::
The following methods uses SRV DNS records and only work with federation traffic. They are only included for completeness.
<details>
<summary>Using only SRV records</summary>
If you can't set up `/.well-known/matrix/server` on :443 for some reason, you can set up a SRV record (via your DNS provider) as below:
- Service and name: `_matrix-fed._tcp.example.com.`
- Priority: `10` (can be any number)
- Weight: `10` (can be any number)
- Port: `443`
- Target: `matrix.example.com.`
On the target's IP at port 443, you must configure a valid route and cert for your server name, `example.com`. Therefore, this method only works to redirect traffic into the right IP/port combo, and can not delegate your federation to a different domain.
</details>
<details>
<summary>Using SRV records + .well-known</summary>
You can also set up `/.well-known/matrix/server` with a delegated domain but no ports:
```toml
[global.well_known]
server = "matrix.example.com"
```
Then, set up a SRV record (via your DNS provider) to announce the port number as below:
- Service and name: `_matrix-fed._tcp.matrix.example.com.`
- Priority: `10` (can be any number)
- Weight: `10` (can be any number)
- Port: `443`
- Target: `matrix.example.com.`
On the target's IP at port 443, you'll need to provide a valid route and cert for `matrix.example.com`. It provides the same feature as pure `.well-known` delegation, albeit with more parts to handle.
</details>
<details>
<summary>Using SRV records as a fallback for .well-known delegation</summary>
Assume your delegation is as below:
```toml
[global.well_known]
server = "example.com:443"
```
If your Continuwuity instance becomes temporarily unreachable, other servers will not be able to find your `/.well-known/matrix/server` file, and defaults to using `server_name:8448`. This incorrect cache can persist for a long time, and would hinder re-federation when your server eventually comes back online.
If you want other servers to default to using port :443 even when it is offline, you could set up a SRV record (via your DNS provider) as follows:
- Service and name: `_matrix-fed._tcp.example.com.`
- Priority: `10` (can be any number)
- Weight: `10` (can be any number)
- Port: `443`
- Target: `example.com.`
On the target's IP at port 443, you'll need to provide a valid route and cert for `example.com`.
</details>
---
## Related Documentation
See the following Matrix Specs for full details on client/server resolution mechanisms:
- [Server-to-Server resolution](https://spec.matrix.org/v1.17/server-server-api/#resolving-server-names) (see this for more information on SRV records)
- [Client-to-Server resolution](https://spec.matrix.org/v1.17/client-server-api/#server-discovery)
- [MSC1929: Homeserver Admin Contact and Support page](https://github.com/matrix-org/matrix-spec-proposals/pull/1929)
+32 -4
View File
@@ -78,19 +78,47 @@ You will need to allow ports `7881/tcp` and `50100:50200/udp` through your firew
### 3. Telling clients where to find LiveKit ### 3. Telling clients where to find LiveKit
To tell clients where to find LiveKit, you need to add the address of your `lk-jwt-service` to the `[global.matrix_rtc]` config section using the `foci` option. To tell clients where to find LiveKit, you need to add the address of your `lk-jwt-service` to your client .well-known file. To do so, in the config section `global.well-known`, add (or modify) the option `rtc_focus_server_urls`.
The variable should be a list of servers serving as MatrixRTC endpoints. Clients discover these via the `/_matrix/client/v1/rtc/transports` endpoint (MSC4143). The variable should be a list of servers serving as MatrixRTC endpoints to serve in the well-known file to the client.
```toml ```toml
[global.matrix_rtc] rtc_focus_server_urls = [
foci = [
{ type = "livekit", livekit_service_url = "https://livekit.example.com" }, { type = "livekit", livekit_service_url = "https://livekit.example.com" },
] ]
``` ```
Remember to replace the URL with the address you are deploying your instance of lk-jwt-service to. Remember to replace the URL with the address you are deploying your instance of lk-jwt-service to.
#### Serving .well-known manually
If you don't let Continuwuity serve your `.well-known` files, you need to add the following lines to your `.well-known/matrix/client` file, remembering to replace the URL with your own `lk-jwt-service` deployment:
```json
"org.matrix.msc4143.rtc_foci": [
{
"type": "livekit",
"livekit_service_url": "https://livekit.example.com"
}
]
```
The final file should look something like this:
```json
{
"m.homeserver": {
"base_url":"https://matrix.example.com"
},
"org.matrix.msc4143.rtc_foci": [
{
"type": "livekit",
"livekit_service_url": "https://livekit.example.com"
}
]
}
```
### 4. Configure your Reverse Proxy ### 4. Configure your Reverse Proxy
Reverse proxies can be configured in many different ways - so we can't provide a step by step for this. Reverse proxies can be configured in many different ways - so we can't provide a step by step for this.
+6 -9
View File
@@ -13,9 +13,8 @@ settings.
The config file to use can be specified on the commandline when running The config file to use can be specified on the commandline when running
Continuwuity by specifying the `-c`, `--config` flag. Alternatively, you can use Continuwuity by specifying the `-c`, `--config` flag. Alternatively, you can use
the environment variable `CONTINUWUITY_CONFIG` to specify the config file to be the environment variable `CONDUWUIT_CONFIG` to specify the config file to used.
used; see [the section on environment variables](#environment-variables) for Conduit's environment variables are supported for backwards compatibility.
more information.
## Option commandline flag ## Option commandline flag
@@ -53,15 +52,13 @@ This commandline argument can be paired with the `--option` flag.
All of the settings that are found in the config file can be specified by using All of the settings that are found in the config file can be specified by using
environment variables. The environment variable names should be all caps and environment variables. The environment variable names should be all caps and
prefixed with `CONTINUWUITY_`. prefixed with `CONDUWUIT_`.
For example, if the setting you are changing is `max_request_size`, then the For example, if the setting you are changing is `max_request_size`, then the
environment variable to set is `CONTINUWUITY_MAX_REQUEST_SIZE`. environment variable to set is `CONDUWUIT_MAX_REQUEST_SIZE`.
To modify config options not in the `[global]` context such as To modify config options not in the `[global]` context such as
`[global.well_known]`, use the `__` suffix split: `[global.well_known]`, use the `__` suffix split: `CONDUWUIT_WELL_KNOWN__SERVER`
`CONTINUWUITY_WELL_KNOWN__SERVER`
Conduit and conduwuit's environment variables are also supported for backwards Conduit's environment variables are supported for backwards compatibility (e.g.
compatibility, via the `CONDUIT_` and `CONDUWUIT_` prefixes respectively (e.g.
`CONDUIT_SERVER_NAME`). `CONDUIT_SERVER_NAME`).
+2 -5
View File
@@ -16,7 +16,8 @@ services:
restart: unless-stopped restart: unless-stopped
labels: labels:
caddy: example.com caddy: example.com
caddy.reverse_proxy: /.well-known/matrix/* homeserver:6167 caddy.0_respond: /.well-known/matrix/server {"m.server":"matrix.example.com:443"}
caddy.1_respond: /.well-known/matrix/client {"m.server":{"base_url":"https://matrix.example.com"},"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc3575.proxy":{"url":"https://matrix.example.com"}}
homeserver: homeserver:
### If you already built the Continuwuity image with 'docker build' or want to use a registry image, ### If you already built the Continuwuity image with 'docker build' or want to use a registry image,
@@ -41,10 +42,6 @@ services:
#CONTINUWUITY_LOG: warn,state_res=warn #CONTINUWUITY_LOG: warn,state_res=warn
CONTINUWUITY_ADDRESS: 0.0.0.0 CONTINUWUITY_ADDRESS: 0.0.0.0
#CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above #CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
# Required for .well-known delegation - edit these according to your chosen domain
CONTINUWUITY_WELL_KNOWN__CLIENT: https://matrix.example.com
CONTINUWUITY_WELL_KNOWN__SERVER: matrix.example.com:443
networks: networks:
- caddy - caddy
labels: labels:
+1 -7
View File
@@ -51,13 +51,7 @@ continuwuity aims to:
Check out the [documentation](https://continuwuity.org) for installation instructions. Check out the [documentation](https://continuwuity.org) for installation instructions.
If you want to try it out as a user, we have some partnered homeservers you can use: There are currently no open registration continuwuity instances available.
* You can head over to [https://federated.nexus](https://federated.nexus/) in your browser.
* Hit the `Apply to Join` button. Once your request has been accepted, you will receive an email with your username and password.
* Head over to [https://app.federated.nexus](https://app.federated.nexus/) and you can sign in there, or use any other matrix chat client you wish elsewhere.
* Your username for matrix will be in the form of `@username:federated.nexus`, however you can simply use the `username` part to log in. Your password is your password.
* There's also [https://continuwuity.rocks/](https://continuwuity.rocks/). You can register a new account using Cinny via [this convenient link](https://app.cinny.in/register/continuwuity.rocks), or you can use Element or another matrix client *that supports registration*.
## What are we working on? ## What are we working on?
@@ -6,10 +6,10 @@
"message": "Welcome to Continuwuity! Important announcements about the project will appear here." "message": "Welcome to Continuwuity! Important announcements about the project will appear here."
}, },
{ {
"id": 10, "id": 9,
"mention_room": false, "mention_room": false,
"date": "2026-03-03", "date": "2026-02-09",
"message": "We've just released [v0.5.6](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.6), which contains a few security improvements - plus significant reliability and performance improvements. Please update as soon as possible. \n\nWe released [v0.5.5](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.5) two weeks ago, but it skipped your admin room straight to [our announcements channel](https://matrix.to/#/!jIdNjSM5X-V5JVx2h2kAhUZIIQ08GyzPL55NFZAH1vM?via=ellis.link&via=gingershaped.computer&via=matrix.org). Make sure you're there to get important information as soon as we announce it! [Our space](https://matrix.to/#/!8cR4g-i9ucof69E4JHNg9LbPVkGprHb3SzcrGBDDJgk?via=continuwuity.org&via=ellis.link&via=matrix.org) has also gained a bunch of new and interesting rooms - be there or be square." "message": "Yesterday we released [v0.5.4](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.4). Bugfixes, performance improvements and more moderation features! There's also a security fix, so please update as soon as possible. Don't forget to join [our announcements channel](https://matrix.to/#/!jIdNjSM5X-V5JVx2h2kAhUZIIQ08GyzPL55NFZAH1vM/%2489TY9CqRg4-ff1MGo3Ulc5r5X4pakfdzT-99RD8Docc?via=ellis.link&via=explodie.org&via=matrix.org) to get important information sooner <3 "
} }
] ]
} }
+1 -5
View File
@@ -27,7 +27,7 @@ default.
* Delete all remote and local media from 3 days ago, up until now: * Delete all remote and local media from 3 days ago, up until now:
`!admin media delete-past-remote-media -a 3d `!admin media delete-past-remote-media -a 3d
--yes-i-want-to-delete-local-media` -yes-i-want-to-delete-local-media`
## `!admin media delete-all-from-user` ## `!admin media delete-all-from-user`
@@ -36,7 +36,3 @@ Deletes all the local media from a local user on our server. This will always ig
## `!admin media delete-all-from-server` ## `!admin media delete-all-from-server`
Deletes all remote media from the specified remote server. This will always ignore errors by default Deletes all remote media from the specified remote server. This will always ignore errors by default
## `!admin media delete-url-preview`
Deletes a cached URL preview, forcing it to be re-fetched. Use --all to purge all cached URL previews
+1
View File
@@ -12,6 +12,7 @@
rocksdbAllFeatures = self'.packages.rocksdb.override { rocksdbAllFeatures = self'.packages.rocksdb.override {
enableJemalloc = true; enableJemalloc = true;
enableLiburing = true;
}; };
commonAttrs = (uwulib.build.commonAttrs { }) // { commonAttrs = (uwulib.build.commonAttrs { }) // {
+1
View File
@@ -27,6 +27,7 @@
commonAttrsArgs.profile = "release"; commonAttrsArgs.profile = "release";
rocksdb = self'.packages.rocksdb.override { rocksdb = self'.packages.rocksdb.override {
enableJemalloc = true; enableJemalloc = true;
enableLiburing = true;
}; };
features = { features = {
enabledFeatures = "all"; enabledFeatures = "all";
+3 -2
View File
@@ -7,6 +7,7 @@
rust-jemalloc-sys-unprefixed, rust-jemalloc-sys-unprefixed,
enableJemalloc ? false, enableJemalloc ? false,
enableLiburing ? false,
fetchFromGitea, fetchFromGitea,
@@ -31,7 +32,7 @@ in
# for some reason enableLiburing in nixpkgs rocksdb is default true # for some reason enableLiburing in nixpkgs rocksdb is default true
# which breaks Darwin entirely # which breaks Darwin entirely
enableLiburing = notDarwin; enableLiburing = enableLiburing && notDarwin;
}).overrideAttrs }).overrideAttrs
(old: { (old: {
src = fetchFromGitea { src = fetchFromGitea {
@@ -73,7 +74,7 @@ in
"USE_RTTI" "USE_RTTI"
]); ]);
enableLiburing = notDarwin; enableLiburing = enableLiburing && notDarwin;
# outputs has "tools" which we don't need or use # outputs has "tools" which we don't need or use
outputs = [ "out" ]; outputs = [ "out" ];
+1
View File
@@ -11,6 +11,7 @@
uwulib = inputs.self.uwulib.init pkgs; uwulib = inputs.self.uwulib.init pkgs;
rocksdbAllFeatures = self'.packages.rocksdb.override { rocksdbAllFeatures = self'.packages.rocksdb.override {
enableJemalloc = true; enableJemalloc = true;
enableLiburing = true;
}; };
in in
{ {
+153 -188
View File
@@ -119,14 +119,15 @@
} }
}, },
"node_modules/@rsbuild/core": { "node_modules/@rsbuild/core": {
"version": "2.0.0-beta.6", "version": "2.0.0-beta.3",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.6.tgz", "resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.3.tgz",
"integrity": "sha512-DUBhUzvzj6xlGUAHTTipFskSuZmVEuTX7lGU+ToPuo8n3bsQrWn/UBOEQAd45g66k7QfXadoZ/v7eodQErpvGQ==", "integrity": "sha512-dfH+Pt2GuF3rWOWGsf5XOhn3Zarvr4DoHwoI1arAsCGvpzoeud3DNGmWPy13tngj0r/YvQRcPTRBCRV4RP5CMw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rspack/core": "2.0.0-beta.3", "@rspack/core": "2.0.0-beta.0",
"@swc/helpers": "^0.5.19" "@swc/helpers": "^0.5.18",
"jiti": "^2.6.1"
}, },
"bin": { "bin": {
"rsbuild": "bin/rsbuild.js" "rsbuild": "bin/rsbuild.js"
@@ -158,28 +159,28 @@
} }
}, },
"node_modules/@rspack/binding": { "node_modules/@rspack/binding": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-beta.0.tgz",
"integrity": "sha512-GSj+d8AlLs1oElhYq32vIN/eAsxWG9jy0EiNgSxWTt5Gdamv87kcvsV4jwfWIjlltdnBIJgey2RnU+hDZlTAvw==", "integrity": "sha512-L6PPqhwZWC2vzwdhBItNPXw+7V4sq+MBDRXLdd8NMqaJSCB5iKdJIbpbEQucST9Nn7V28IYoQTXs6+ol5vWUBA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optionalDependencies": { "optionalDependencies": {
"@rspack/binding-darwin-arm64": "2.0.0-beta.3", "@rspack/binding-darwin-arm64": "2.0.0-beta.0",
"@rspack/binding-darwin-x64": "2.0.0-beta.3", "@rspack/binding-darwin-x64": "2.0.0-beta.0",
"@rspack/binding-linux-arm64-gnu": "2.0.0-beta.3", "@rspack/binding-linux-arm64-gnu": "2.0.0-beta.0",
"@rspack/binding-linux-arm64-musl": "2.0.0-beta.3", "@rspack/binding-linux-arm64-musl": "2.0.0-beta.0",
"@rspack/binding-linux-x64-gnu": "2.0.0-beta.3", "@rspack/binding-linux-x64-gnu": "2.0.0-beta.0",
"@rspack/binding-linux-x64-musl": "2.0.0-beta.3", "@rspack/binding-linux-x64-musl": "2.0.0-beta.0",
"@rspack/binding-wasm32-wasi": "2.0.0-beta.3", "@rspack/binding-wasm32-wasi": "2.0.0-beta.0",
"@rspack/binding-win32-arm64-msvc": "2.0.0-beta.3", "@rspack/binding-win32-arm64-msvc": "2.0.0-beta.0",
"@rspack/binding-win32-ia32-msvc": "2.0.0-beta.3", "@rspack/binding-win32-ia32-msvc": "2.0.0-beta.0",
"@rspack/binding-win32-x64-msvc": "2.0.0-beta.3" "@rspack/binding-win32-x64-msvc": "2.0.0-beta.0"
} }
}, },
"node_modules/@rspack/binding-darwin-arm64": { "node_modules/@rspack/binding-darwin-arm64": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-beta.0.tgz",
"integrity": "sha512-QebSomLWlCbFsC0sfDuGqLJtkgyrnr38vrCepWukaAXIY4ANy5QB49LDKdLpVv6bKlC95MpnW37NvSNWY5GMYA==", "integrity": "sha512-PPx1+SPEROSvDKmBuCbsE7W9tk07ajPosyvyuafv2wbBI6PW2rNcz62uzpIFS+FTgwwZ5u/06WXRtlD2xW9bKg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -191,9 +192,9 @@
] ]
}, },
"node_modules/@rspack/binding-darwin-x64": { "node_modules/@rspack/binding-darwin-x64": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-beta.0.tgz",
"integrity": "sha512-EysmBq+sz+Ph0bu0gXpU1uuZG9gXgjqY+w3MJel+ieTFyQO3L/R56V32McgssMbheJbYcviDDn7Tz4D+lTvdJA==", "integrity": "sha512-GucsfjrSKBZ9cuOTXmHWxeY2wPmaNyvGNxTyzttjRcfwqOWz8r+ku6PCsMSXUqxZRYWW1L9mvtTdlDrzTYJZ0w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -205,9 +206,9 @@
] ]
}, },
"node_modules/@rspack/binding-linux-arm64-gnu": { "node_modules/@rspack/binding-linux-arm64-gnu": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-beta.0.tgz",
"integrity": "sha512-iFPj4TQZKewnqWPfTbyk3F8QCBI/Edv7TVSRIPBHRnCM0lvYZl/8IZlUzXSamLvrtDpouF0nUzht/fktoWOhAg==", "integrity": "sha512-nTtYtklRZD4sb2RIFCF9YS8tZ/MjpqIBKVS3YIvdXcfHUdVfmQHTZGtwEuZGg6AxTC5L1hcvkYmTXCG0ok7auw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -219,9 +220,9 @@
] ]
}, },
"node_modules/@rspack/binding-linux-arm64-musl": { "node_modules/@rspack/binding-linux-arm64-musl": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-beta.0.tgz",
"integrity": "sha512-355mygfCNb0eF/y4HgtJcd0i9csNTG4Z15PCCplIkSAKJpFpkORM2xJb50BqsbhVafYl6AHoBlGWAo9iIzUb/w==", "integrity": "sha512-S2fshx0Rf7/XYwoMLaqFsVg4y+VAfHzubrczy8AW5xIs6UNC3eRLVTgShLerUPtF6SG+v6NQxQ9JI3vOo2qPOA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -233,9 +234,9 @@
] ]
}, },
"node_modules/@rspack/binding-linux-x64-gnu": { "node_modules/@rspack/binding-linux-x64-gnu": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-beta.0.tgz",
"integrity": "sha512-U8a+bcP/tkMyiwiO9XfeRYYO20YPGiZNxWWt7FEsdmRuRAl6M+EmWaJllJFQtKH+GG8IN93pNoVPMvARjLoJOQ==", "integrity": "sha512-yx5Fk1gl7lfkvqcjolNLCNeduIs6C2alMsQ/kZ1pLeP5MPquVOYNqs6EcDPIp+fUjo3lZYtnJBiZKK+QosbzYg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -247,9 +248,9 @@
] ]
}, },
"node_modules/@rspack/binding-linux-x64-musl": { "node_modules/@rspack/binding-linux-x64-musl": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-beta.0.tgz",
"integrity": "sha512-g81rqkaqDFRTID2VrHBYeM+xZe8yWov7IcryTrl9RGXXr61s+6Tu/mWyM378PuHOCyMNu7G3blVaSjLvKauG6Q==", "integrity": "sha512-sBX4b2W0PgehlAVT224k0Q6GaH6t9HP+hBNDrbX/g6d0hfxZN56gm5NfOTOD1Rien4v7OBEejJ3/uFbm1WjwYQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -261,9 +262,9 @@
] ]
}, },
"node_modules/@rspack/binding-wasm32-wasi": { "node_modules/@rspack/binding-wasm32-wasi": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-beta.0.tgz",
"integrity": "sha512-tzGd8H2oj5F3oR/Hxp+J68zVU/nG+9ndH2KK3/RieVjNAiVNHCR0/ZU9D47s6fnmvWOqAQ1qO8gnVoVLopC4YA==", "integrity": "sha512-o6OatnNvb4kCzXbCaomhENGaCsO3naIyAqqErew90HeAwa1lfY3NhRfDLeIyuANQ+xqFl34/R7n8q3ZDx3nd4Q==",
"cpu": [ "cpu": [
"wasm32" "wasm32"
], ],
@@ -275,9 +276,9 @@
} }
}, },
"node_modules/@rspack/binding-win32-arm64-msvc": { "node_modules/@rspack/binding-win32-arm64-msvc": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-beta.0.tgz",
"integrity": "sha512-TZZRSWa34sm5WyoQHwnyBjLJ4w3fcWRYA9ybYjSVWjUU6tVGdMiHiZp+WexUpIETvChLXU1JENNmBg/U7wvZEA==", "integrity": "sha512-neCzVllXzIqM8p8qKb89qV7wyk233gC/V9VrHIKbGeQjAEzpBsk5GOWlFbq5DDL6tivQ+uzYaTrZWm9tb2qxXg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -289,9 +290,9 @@
] ]
}, },
"node_modules/@rspack/binding-win32-ia32-msvc": { "node_modules/@rspack/binding-win32-ia32-msvc": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-beta.0.tgz",
"integrity": "sha512-VFnfdbJhyl6gNW1VzTyd1ZrHCboHPR7vrOalEsulQRqVNbtDkjm1sqLHtDcLmhTEv0a9r4lli8uubWDwmel8KQ==", "integrity": "sha512-/f0n2eO+DxMKQm9IebeMQJITx8M/+RvY/i8d3sAQZBgR53izn8y7EcDlidXpr24/2DvkLbiub8IyCKPlhLB+1A==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -303,9 +304,9 @@
] ]
}, },
"node_modules/@rspack/binding-win32-x64-msvc": { "node_modules/@rspack/binding-win32-x64-msvc": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-beta.0.tgz",
"integrity": "sha512-rwZ6Y3b3oqPj+ZDPPRxr3136HUPKDSlPQa4v7bBOPLDlrFDFOynMIEqDUUi5+8lPaUQ8WWR0aJK4cgcTTT0Siw==", "integrity": "sha512-dx4zgiAT88EQE7kEUpr7Z9EZAwLnO5FhzWzvd/cDK4bkqYsx+rTklgf/c0EYPBeroXCxlGiMsuC9wHAFNK7sFw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -317,19 +318,20 @@
] ]
}, },
"node_modules/@rspack/core": { "node_modules/@rspack/core": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-beta.0.tgz",
"integrity": "sha512-VuLteRIesuyFFTXZaciUY0lwDZiwMc7JcpE8guvjArztDhtpVvlaOcLlVBp/Yza8c/Tk8Dxwe1ARzFL7xG1/0w==", "integrity": "sha512-aEqlQQjiXixT5i9S4DFtiAap8ZjF6pOgfY2ALHOizins/QqWyB8dyLxSoXdzt7JixmKcFmHkbL9XahO28BlVUA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rspack/binding": "2.0.0-beta.3" "@rspack/binding": "2.0.0-beta.0",
"@rspack/lite-tapable": "1.1.0"
}, },
"engines": { "engines": {
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
}, },
"peerDependencies": { "peerDependencies": {
"@module-federation/runtime-tools": "^0.24.1 || ^2.0.0", "@module-federation/runtime-tools": ">=0.22.0",
"@swc/helpers": ">=0.5.1" "@swc/helpers": ">=0.5.1"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
@@ -341,6 +343,13 @@
} }
} }
}, },
"node_modules/@rspack/lite-tapable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.1.0.tgz",
"integrity": "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==",
"dev": true,
"license": "MIT"
},
"node_modules/@rspack/plugin-react-refresh": { "node_modules/@rspack/plugin-react-refresh": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.0.tgz", "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.0.tgz",
@@ -362,22 +371,22 @@
} }
}, },
"node_modules/@rspress/core": { "node_modules/@rspress/core": {
"version": "2.0.4", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.3.tgz",
"integrity": "sha512-OdeGMY75OFzyRZvXuBEMre3q8Y4/OjYJa4vVBDp4Z2E65LSt8+hYkzzkarEl6sFWqbp8c1o9qfSUf4xMctmKvw==", "integrity": "sha512-a+JJFiALqMxGJBqR38/lkN6tas42UF4jRIhu6RilC/3DdqpfqR8j6jjQFOmqoNKo6ZGXW2W+i1Pscn6drvoG3w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@mdx-js/mdx": "^3.1.1", "@mdx-js/mdx": "^3.1.1",
"@mdx-js/react": "^3.1.1", "@mdx-js/react": "^3.1.1",
"@rsbuild/core": "2.0.0-beta.6", "@rsbuild/core": "2.0.0-beta.3",
"@rsbuild/plugin-react": "~1.4.5", "@rsbuild/plugin-react": "~1.4.5",
"@rspress/shared": "2.0.4", "@rspress/shared": "2.0.3",
"@shikijs/rehype": "^4.0.1", "@shikijs/rehype": "^3.21.0",
"@types/unist": "^3.0.3", "@types/unist": "^3.0.3",
"@unhead/react": "^2.1.9", "@unhead/react": "^2.1.4",
"body-scroll-lock": "4.0.0-beta.0", "body-scroll-lock": "4.0.0-beta.0",
"cac": "^7.0.0", "cac": "^6.7.14",
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
"clsx": "2.1.1", "clsx": "2.1.1",
"copy-to-clipboard": "^3.3.3", "copy-to-clipboard": "^3.3.3",
@@ -395,8 +404,7 @@
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-lazy-with-preload": "^2.2.1", "react-lazy-with-preload": "^2.2.1",
"react-reconciler": "0.33.0", "react-reconciler": "0.33.0",
"react-render-to-markdown": "19.0.1", "react-router-dom": "^7.13.0",
"react-router-dom": "^7.13.1",
"rehype-external-links": "^3.0.0", "rehype-external-links": "^3.0.0",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
@@ -404,7 +412,7 @@
"remark-parse": "^11.0.0", "remark-parse": "^11.0.0",
"remark-stringify": "^11.0.0", "remark-stringify": "^11.0.0",
"scroll-into-view-if-needed": "^3.1.0", "scroll-into-view-if-needed": "^3.1.0",
"shiki": "^4.0.1", "shiki": "^3.21.0",
"tinyglobby": "^0.2.15", "tinyglobby": "^0.2.15",
"tinypool": "^1.1.1", "tinypool": "^1.1.1",
"unified": "^11.0.5", "unified": "^11.0.5",
@@ -420,162 +428,125 @@
} }
}, },
"node_modules/@rspress/plugin-client-redirects": { "node_modules/@rspress/plugin-client-redirects": {
"version": "2.0.4", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.3.tgz",
"integrity": "sha512-cm7VNfisVCHe+YHNjd9YrWt6/WtJ5I/oNRyjt+tqCeOcC1IJSX2LhNXpNN5h9az3wxYn37kVctBUjzqkj2FQ+A==", "integrity": "sha512-9+SoAbfoxM6OCRWx8jWHHi2zwJDcNaej/URx0CWZk8tvQ618yJW5mXJydknlac62399eYh/F7C3w8TZM3ORGVA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
}, },
"peerDependencies": { "peerDependencies": {
"@rspress/core": "^2.0.4" "@rspress/core": "^2.0.3"
} }
}, },
"node_modules/@rspress/plugin-sitemap": { "node_modules/@rspress/plugin-sitemap": {
"version": "2.0.4", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.3.tgz",
"integrity": "sha512-TKaj3/8+P1fP3sD5NOaWVMXvRvJFQmuJQlUBxhRM0oiUHhzNNkVy/2YXkjYJuXuMhFPLnOWCjrYjTG3xcZE7Wg==", "integrity": "sha512-SKa7YEAdkUqya2YjMKbakg3kcYMkXgXhTQdDsHd+QlJWN8j8cDPiCcctMZu8iIPeKZlb+hTJkTWvh27LSIKdOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
}, },
"peerDependencies": { "peerDependencies": {
"@rspress/core": "^2.0.4" "@rspress/core": "^2.0.3"
} }
}, },
"node_modules/@rspress/shared": { "node_modules/@rspress/shared": {
"version": "2.0.4", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.3.tgz",
"integrity": "sha512-os2nzsPgHKVFXjDoW7N53rmhLChCw/y2O2TGilT4w2A4HNJa2oJwRk0UryXbxxWD5C85HErTjovs2uBdhdOTtA==", "integrity": "sha512-yI9G4P165fSsmm6QoYTUrdgUis1aFnDh04GcM4SQIpL3itvEZhGtItgoeGkX9EWbnEjhriwI8mTqDDJIp+vrGA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rsbuild/core": "2.0.0-beta.6", "@rsbuild/core": "2.0.0-beta.3",
"@shikijs/rehype": "^4.0.1", "@shikijs/rehype": "^3.21.0",
"gray-matter": "4.0.3", "gray-matter": "4.0.3",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"unified": "^11.0.5" "unified": "^11.0.5"
} }
}, },
"node_modules/@shikijs/core": { "node_modules/@shikijs/core": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz",
"integrity": "sha512-vWvqi9JNgz1dRL9Nvog5wtx7RuNkf7MEPl2mU/cyUUxJeH1CAr3t+81h8zO8zs7DK6cKLMoU9TvukWIDjP4Lzg==", "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/primitive": "4.0.1", "@shikijs/types": "3.22.0",
"@shikijs/types": "4.0.1",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4", "@types/hast": "^3.0.4",
"hast-util-to-html": "^9.0.5" "hast-util-to-html": "^9.0.5"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/engine-javascript": { "node_modules/@shikijs/engine-javascript": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz",
"integrity": "sha512-DJK9NiwtGYqMuKCRO4Ip0FKNDQpmaiS+K5bFjJ7DWFn4zHueDWgaUG8kAofkrnXF6zPPYYQY7J5FYVW9MbZyBg==", "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.1", "@shikijs/types": "3.22.0",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"oniguruma-to-es": "^4.3.4" "oniguruma-to-es": "^4.3.4"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/engine-oniguruma": { "node_modules/@shikijs/engine-oniguruma": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz",
"integrity": "sha512-oCWdCTDch3J8Kc0OZJ98KuUPC02O1VqIE3W/e2uvrHqTxYRR21RGEJMtchrgrxhsoJJCzmIciKsqG+q/yD+Cxg==", "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.1", "@shikijs/types": "3.22.0",
"@shikijs/vscode-textmate": "^10.0.2" "@shikijs/vscode-textmate": "^10.0.2"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/langs": { "node_modules/@shikijs/langs": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz",
"integrity": "sha512-v/mluaybWdnGJR4GqAR6zh8qAZohW9k+cGYT28Y7M8+jLbC0l4yG085O1A+WkseHTn+awd+P3UBymb2+MXFc8w==", "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.1" "@shikijs/types": "3.22.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@shikijs/primitive": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.1.tgz",
"integrity": "sha512-ns0hHZc5eWZuvuIEJz2pTx3Qecz0aRVYumVQJ8JgWY2tq/dH8WxdcVM49Fc2NsHEILNIT6vfdW9MF26RANWiTA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.0.1",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/rehype": { "node_modules/@shikijs/rehype": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-3.22.0.tgz",
"integrity": "sha512-bx7bYA0/p/pgeEICaPO0jT6TXrXHmr9tGRUDhOMy1cAUN2YA0iANfXX7seBnImy8DGu/rxm1ij9/ZofYrAaUjQ==", "integrity": "sha512-69b2VPc6XBy/VmAJlpBU5By+bJSBdE2nvgRCZXav7zujbrjXuT0F60DIrjKuutjPqNufuizE+E8tIZr2Yn8Z+g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.1", "@shikijs/types": "3.22.0",
"@types/hast": "^3.0.4", "@types/hast": "^3.0.4",
"hast-util-to-string": "^3.0.1", "hast-util-to-string": "^3.0.1",
"shiki": "4.0.1", "shiki": "3.22.0",
"unified": "^11.0.5", "unified": "^11.0.5",
"unist-util-visit": "^5.1.0" "unist-util-visit": "^5.1.0"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/themes": { "node_modules/@shikijs/themes": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz",
"integrity": "sha512-FW41C/D6j/yKQkzVdjrRPiJCtgeDaYRJFEyCKFCINuRJRj9WcmubhP4KQHPZ4+9eT87jruSrYPyoblNRyDFzvA==", "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.1" "@shikijs/types": "3.22.0"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/types": { "node_modules/@shikijs/types": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz",
"integrity": "sha512-EaygPEn57+jJ76mw+nTLvIpJMAcMPokFbrF8lufsZP7Ukk+ToJYEcswN1G0e49nUZAq7aCQtoeW219A8HK1ZOw==", "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4" "@types/hast": "^3.0.4"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/vscode-textmate": { "node_modules/@shikijs/vscode-textmate": {
@@ -586,9 +557,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@swc/helpers": { "node_modules/@swc/helpers": {
"version": "0.5.19", "version": "0.5.18",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz",
"integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==", "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -693,13 +664,13 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/@unhead/react": { "node_modules/@unhead/react": {
"version": "2.1.10", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.10.tgz", "resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.4.tgz",
"integrity": "sha512-z9IzzkaCI1GyiBwVRMt4dGc2mOvsj9drbAdXGMy6DWpu9FwTR37ZTmAi7UeCVyIkpVdIaNalz7vkbvGG8afFng==", "integrity": "sha512-3DzMi5nJkUyLVfQF/q78smCvcSy84TTYgTwXVz5s3AjUcLyHro5Z7bLWriwk1dn5+YRfEsec8aPkLCMi5VjMZg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"unhead": "2.1.10" "unhead": "2.1.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/harlan-zw" "url": "https://github.com/sponsors/harlan-zw"
@@ -810,13 +781,13 @@
} }
}, },
"node_modules/cac": { "node_modules/cac": {
"version": "7.0.0", "version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
"integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=20.19.0" "node": ">=8"
} }
}, },
"node_modules/ccount": { "node_modules/ccount": {
@@ -1727,6 +1698,16 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "3.14.2", "version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
@@ -3038,23 +3019,10 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-render-to-markdown": {
"version": "19.0.1",
"resolved": "https://registry.npmjs.org/react-render-to-markdown/-/react-render-to-markdown-19.0.1.tgz",
"integrity": "sha512-BPv48o+ubcu2JyUDIktvJXFqLIZqR7hA4mvGu1eFIofz9fogT2me9UvXwRvqvGs9jEtNaJkxZIUKUX0oiK4hDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"react-reconciler": "0.33.0"
},
"peerDependencies": {
"react": ">=19"
}
},
"node_modules/react-router": { "node_modules/react-router": {
"version": "7.13.1", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
"integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3075,13 +3043,13 @@
} }
}, },
"node_modules/react-router-dom": { "node_modules/react-router-dom": {
"version": "7.13.1", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
"integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==", "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"react-router": "7.13.1" "react-router": "7.13.0"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
@@ -3377,23 +3345,20 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/shiki": { "node_modules/shiki": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.1.tgz", "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz",
"integrity": "sha512-EkAEhDTN5WhpoQFXFw79OHIrSAfHhlImeCdSyg4u4XvrpxKEmdo/9x/HWSowujAnUrFsGOwWiE58a6GVentMnQ==", "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/core": "4.0.1", "@shikijs/core": "3.22.0",
"@shikijs/engine-javascript": "4.0.1", "@shikijs/engine-javascript": "3.22.0",
"@shikijs/engine-oniguruma": "4.0.1", "@shikijs/engine-oniguruma": "3.22.0",
"@shikijs/langs": "4.0.1", "@shikijs/langs": "3.22.0",
"@shikijs/themes": "4.0.1", "@shikijs/themes": "3.22.0",
"@shikijs/types": "4.0.1", "@shikijs/types": "3.22.0",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4" "@types/hast": "^3.0.4"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/source-map": { "node_modules/source-map": {
@@ -3598,9 +3563,9 @@
} }
}, },
"node_modules/unhead": { "node_modules/unhead": {
"version": "2.1.10", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.10.tgz", "resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.4.tgz",
"integrity": "sha512-We8l9uNF8zz6U8lfQaVG70+R/QBfQx1oPIgXin4BtZnK2IQpz6yazQ0qjMNVBDw2ADgF2ea58BtvSK+XX5AS7g==", "integrity": "sha512-+5091sJqtNNmgfQ07zJOgUnMIMKzVKAWjeMlSrTdSGPB6JSozhpjUKuMfWEoLxlMAfhIvgOU8Me0XJvmMA/0fA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+4 -15
View File
@@ -1,6 +1,6 @@
use std::fmt::Write; use std::fmt::Write;
use conduwuit::{Err, Result, utils::response::LimitReadExt}; use conduwuit::{Err, Result};
use futures::StreamExt; use futures::StreamExt;
use ruma::{OwnedRoomId, OwnedServerName, OwnedUserId}; use ruma::{OwnedRoomId, OwnedServerName, OwnedUserId};
@@ -30,15 +30,12 @@ pub(super) async fn incoming_federation(&self) -> Result {
.federation_handletime .federation_handletime
.read(); .read();
let mut msg = format!( let mut msg = format!("Handling {} incoming pdus:\n", map.len());
"Handling {} incoming PDUs across {} active transactions:\n",
map.len(),
self.services.transactions.txn_active_handle_count()
);
for (r, (e, i)) in map.iter() { for (r, (e, i)) in map.iter() {
let elapsed = i.elapsed(); let elapsed = i.elapsed();
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60)?; writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60)?;
} }
msg msg
}; };
@@ -55,15 +52,7 @@ pub(super) async fn fetch_support_well_known(&self, server_name: OwnedServerName
.send() .send()
.await?; .await?;
let text = response let text = response.text().await?;
.limit_read_text(
self.services
.config
.max_request_size
.try_into()
.expect("u64 fits into usize"),
)
.await?;
if text.is_empty() { if text.is_empty() {
return Err!("Response text/body is empty."); return Err!("Response text/body is empty.");
+1 -19
View File
@@ -29,9 +29,7 @@ pub(super) async fn delete(
.delete(&mxc.as_str().try_into()?) .delete(&mxc.as_str().try_into()?)
.await?; .await?;
return self return Err!("Deleted the MXC from our database and on our filesystem.",);
.write_str("Deleted the MXC from our database and on our filesystem.")
.await;
} }
if let Some(event_id) = event_id { if let Some(event_id) = event_id {
@@ -390,19 +388,3 @@ pub(super) async fn get_remote_thumbnail(
self.write_str(&format!("```\n{result:#?}\nreceived {len} bytes for file content.\n```")) self.write_str(&format!("```\n{result:#?}\nreceived {len} bytes for file content.\n```"))
.await .await
} }
#[admin_command]
pub(super) async fn delete_url_preview(&self, url: Option<String>, all: bool) -> Result {
if all {
self.services.media.clear_url_previews().await;
return self.write_str("Deleted all cached URL previews.").await;
}
let url = url.expect("clap enforces url is required unless --all");
self.services.media.remove_url_preview(&url).await?;
self.write_str(&format!("Deleted cached URL preview for: {url}"))
.await
}
+1 -13
View File
@@ -40,7 +40,7 @@ pub enum MediaCommand {
/// * Delete all remote and local media from 3 days ago, up until now: /// * Delete all remote and local media from 3 days ago, up until now:
/// ///
/// `!admin media delete-past-remote-media -a 3d /// `!admin media delete-past-remote-media -a 3d
///--yes-i-want-to-delete-local-media` ///-yes-i-want-to-delete-local-media`
#[command(verbatim_doc_comment)] #[command(verbatim_doc_comment)]
DeletePastRemoteMedia { DeletePastRemoteMedia {
/// The relative time (e.g. 30s, 5m, 7d) from now within which to /// The relative time (e.g. 30s, 5m, 7d) from now within which to
@@ -108,16 +108,4 @@ pub enum MediaCommand {
#[arg(long, default_value("800"))] #[arg(long, default_value("800"))]
height: u32, height: u32,
}, },
/// Deletes a cached URL preview, forcing it to be re-fetched.
/// Use --all to purge all cached URL previews.
DeleteUrlPreview {
/// The URL to clear from the saved preview data
#[arg(required_unless_present = "all")]
url: Option<String>,
/// Purge all cached URL previews
#[arg(long, conflicts_with = "url")]
all: bool,
},
} }
+1 -1
View File
@@ -209,7 +209,7 @@ pub(super) async fn compact(
let parallelism = parallelism.unwrap_or(1); let parallelism = parallelism.unwrap_or(1);
let results = maps let results = maps
.into_iter() .into_iter()
.try_stream::<conduwuit::Error>() .try_stream()
.paralleln_and_then(runtime, parallelism, move |map| { .paralleln_and_then(runtime, parallelism, move |map| {
map.compact_blocking(options.clone())?; map.compact_blocking(options.clone())?;
Ok(map.name().to_owned()) Ok(map.name().to_owned())
+1 -11
View File
@@ -20,17 +20,7 @@ pub enum ResolverCommand {
name: Option<String>, name: Option<String>,
}, },
/// Flush a given server from the resolver caches or flush them completely /// Flush a specific server from the resolver caches or everything
///
/// * Examples:
/// * Flush a specific server:
///
/// `!admin query resolver flush-cache matrix.example.com`
///
/// * Flush all resolver caches completely:
///
/// `!admin query resolver flush-cache --all`
#[command(verbatim_doc_comment)]
FlushCache { FlushCache {
name: Option<OwnedServerName>, name: Option<OwnedServerName>,
+7 -7
View File
@@ -3,7 +3,7 @@ use std::fmt::Write;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Error, Event, Result, debug_info, err, error, info, Err, Event, Result, debug_info, err, error, info,
matrix::pdu::PduBuilder, matrix::pdu::PduBuilder,
utils::{self, ReadyExt, stream::BroadbandExt}, utils::{self, ReadyExt, stream::BroadbandExt},
warn, warn,
@@ -387,7 +387,7 @@ pub(crate) async fn register_route(
) )
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
}, },
@@ -401,7 +401,7 @@ pub(crate) async fn register_route(
&uiaainfo, &uiaainfo,
json, json,
); );
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err!(Request(NotJson("JSON body is not valid"))); return Err!(Request(NotJson("JSON body is not valid")));
@@ -661,7 +661,7 @@ pub(crate) async fn change_password_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
@@ -673,7 +673,7 @@ pub(crate) async fn change_password_route(
.uiaa .uiaa
.create(sender_user, body.sender_device(), &uiaainfo, json); .create(sender_user, body.sender_device(), &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err!(Request(NotJson("JSON body is not valid"))); return Err!(Request(NotJson("JSON body is not valid")));
@@ -791,7 +791,7 @@ pub(crate) async fn deactivate_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
}, },
@@ -802,7 +802,7 @@ pub(crate) async fn deactivate_route(
.uiaa .uiaa
.create(sender_user, body.sender_device(), &uiaainfo, json); .create(sender_user, body.sender_device(), &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err!(Request(NotJson("JSON body is not valid"))); return Err!(Request(NotJson("JSON body is not valid")));
+7 -1
View File
@@ -9,7 +9,7 @@ use ruma::{
}, },
events::{ events::{
AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent, AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent,
RoomAccountDataEventType, GlobalAccountDataEventType, RoomAccountDataEventType,
}, },
serde::Raw, serde::Raw,
}; };
@@ -126,6 +126,12 @@ async fn set_account_data(
))); )));
} }
if event_type_s == GlobalAccountDataEventType::PushRules.to_cow_str() {
return Err!(Request(BadJson(
"This endpoint cannot be used for setting/configuring push rules."
)));
}
let data: serde_json::Value = serde_json::from_str(data.get()) let data: serde_json::Value = serde_json::from_str(data.get())
.map_err(|e| err!(Request(BadJson(warn!("Invalid JSON provided: {e}")))))?; .map_err(|e| err!(Request(BadJson(warn!("Invalid JSON provided: {e}")))))?;
-121
View File
@@ -1,121 +0,0 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Result, at};
use futures::StreamExt;
use ruma::api::client::dehydrated_device::{
delete_dehydrated_device::unstable as delete_dehydrated_device,
get_dehydrated_device::unstable as get_dehydrated_device, get_events::unstable as get_events,
put_dehydrated_device::unstable as put_dehydrated_device,
};
use crate::Ruma;
const MAX_BATCH_EVENTS: usize = 50;
/// # `PUT /_matrix/client/../dehydrated_device`
///
/// Creates or overwrites the user's dehydrated device.
#[tracing::instrument(skip_all, fields(%client))]
pub(crate) async fn put_dehydrated_device_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<put_dehydrated_device::Request>,
) -> Result<put_dehydrated_device::Response> {
let sender_user = body
.sender_user
.as_deref()
.expect("AccessToken authentication required");
let device_id = body.body.device_id.clone();
services
.users
.set_dehydrated_device(sender_user, body.body)
.await?;
Ok(put_dehydrated_device::Response { device_id })
}
/// # `DELETE /_matrix/client/../dehydrated_device`
///
/// Deletes the user's dehydrated device without replacement.
#[tracing::instrument(skip_all, fields(%client))]
pub(crate) async fn delete_dehydrated_device_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<delete_dehydrated_device::Request>,
) -> Result<delete_dehydrated_device::Response> {
let sender_user = body.sender_user();
let device_id = services.users.get_dehydrated_device_id(sender_user).await?;
services.users.remove_device(sender_user, &device_id).await;
Ok(delete_dehydrated_device::Response { device_id })
}
/// # `GET /_matrix/client/../dehydrated_device`
///
/// Gets the user's dehydrated device
#[tracing::instrument(skip_all, fields(%client))]
pub(crate) async fn get_dehydrated_device_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_dehydrated_device::Request>,
) -> Result<get_dehydrated_device::Response> {
let sender_user = body.sender_user();
let device = services.users.get_dehydrated_device(sender_user).await?;
Ok(get_dehydrated_device::Response {
device_id: device.device_id,
device_data: device.device_data,
})
}
/// # `GET /_matrix/client/../dehydrated_device/{device_id}/events`
///
/// Paginates the events of the dehydrated device.
#[tracing::instrument(skip_all, fields(%client))]
pub(crate) async fn get_dehydrated_events_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_events::Request>,
) -> Result<get_events::Response> {
let sender_user = body.sender_user();
let device_id = &body.body.device_id;
let existing_id = services.users.get_dehydrated_device_id(sender_user).await;
if existing_id.as_ref().is_err()
|| existing_id
.as_ref()
.is_ok_and(|existing_id| existing_id != device_id)
{
return Err!(Request(Forbidden("Not the dehydrated device_id.")));
}
let since: Option<u64> = body
.body
.next_batch
.as_deref()
.map(str::parse)
.transpose()?;
let mut next_batch: Option<u64> = None;
let events = services
.users
.get_to_device_events(sender_user, device_id, since, None)
.take(MAX_BATCH_EVENTS)
.inspect(|&(count, _)| {
next_batch.replace(count);
})
.map(at!(1))
.collect()
.await;
Ok(get_events::Response {
events,
next_batch: next_batch.as_ref().map(ToString::to_string),
})
}
+4 -4
View File
@@ -1,6 +1,6 @@
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Error, Result, debug, err, utils}; use conduwuit::{Err, Result, debug, err, utils};
use futures::StreamExt; use futures::StreamExt;
use ruma::{ use ruma::{
MilliSecondsSinceUnixEpoch, OwnedDeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId,
@@ -232,7 +232,7 @@ pub(crate) async fn delete_devices_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
}, },
@@ -243,10 +243,10 @@ pub(crate) async fn delete_devices_route(
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, json); .create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json.")); return Err!(BadRequest(ErrorKind::NotJson, "Not json."));
}, },
}, },
} }
+6 -6
View File
@@ -5,7 +5,7 @@ use std::{
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug, debug_warn, err, Err, Result, debug, debug_warn, err,
result::NotFound, result::NotFound,
utils, utils,
utils::{IterStream, stream::WidebandExt}, utils::{IterStream, stream::WidebandExt},
@@ -215,7 +215,7 @@ pub(crate) async fn upload_signing_keys_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
}, },
@@ -226,10 +226,10 @@ pub(crate) async fn upload_signing_keys_route(
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, json); .create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json.")); return Err!(BadRequest(ErrorKind::NotJson, "Not json."));
}, },
}, },
} }
@@ -396,12 +396,12 @@ pub(crate) async fn get_key_changes_route(
let from = body let from = body
.from .from
.parse() .parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`."))?; .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Invalid `from`.")))?;
let to = body let to = body
.to .to
.parse() .parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`."))?; .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Invalid `to`.")))?;
device_list_updates.extend( device_list_updates.extend(
services services
+5 -27
View File
@@ -3,10 +3,9 @@ use std::time::Duration;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Result, err, Err, Result, err, error,
utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize}, utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize},
}; };
use conduwuit_core::error;
use conduwuit_service::{ use conduwuit_service::{
Services, Services,
media::{CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN, Dim, FileMeta, MXC_LENGTH}, media::{CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN, Dim, FileMeta, MXC_LENGTH},
@@ -70,7 +69,7 @@ pub(crate) async fn create_content_route(
.create(mxc, Some(user), Some(&content_disposition), content_type, &body.file) .create(mxc, Some(user), Some(&content_disposition), content_type, &body.file)
.await .await
{ {
err!("Failed to save uploaded media: {e}"); error!("Failed to save uploaded media: {e}");
return Err!(Request(Unknown("Failed to save uploaded media"))); return Err!(Request(Unknown("Failed to save uploaded media")));
} }
@@ -145,22 +144,12 @@ pub(crate) async fn get_content_route(
server_name: &body.server_name, server_name: &body.server_name,
media_id: &body.media_id, media_id: &body.media_id,
}; };
let FileMeta { let FileMeta {
content, content,
content_type, content_type,
content_disposition, content_disposition,
} = match fetch_file(&services, &mxc, user, body.timeout_ms, None).await { } = fetch_file(&services, &mxc, user, body.timeout_ms, None).await?;
| Ok(meta) => meta,
| Err(conduwuit::Error::Io(e)) => match e.kind() {
| std::io::ErrorKind::NotFound => return Err!(Request(NotFound("Media not found."))),
| std::io::ErrorKind::PermissionDenied => {
error!("Permission denied when trying to read file: {e:?}");
return Err!(Request(Unknown("Unknown error when fetching file.")));
},
| _ => return Err!(Request(Unknown("Unknown error when fetching file."))),
},
| Err(_) => return Err!(Request(Unknown("Unknown error when fetching file."))),
};
Ok(get_content::v1::Response { Ok(get_content::v1::Response {
file: content.expect("entire file contents"), file: content.expect("entire file contents"),
@@ -196,18 +185,7 @@ pub(crate) async fn get_content_as_filename_route(
content, content,
content_type, content_type,
content_disposition, content_disposition,
} = match fetch_file(&services, &mxc, user, body.timeout_ms, None).await { } = fetch_file(&services, &mxc, user, body.timeout_ms, Some(&body.filename)).await?;
| Ok(meta) => meta,
| Err(conduwuit::Error::Io(e)) => match e.kind() {
| std::io::ErrorKind::NotFound => return Err!(Request(NotFound("Media not found."))),
| std::io::ErrorKind::PermissionDenied => {
error!("Permission denied when trying to read file: {e:?}");
return Err!(Request(Unknown("Unknown error when fetching file.")));
},
| _ => return Err!(Request(Unknown("Unknown error when fetching file."))),
},
| Err(_) => return Err!(Request(Unknown("Unknown error when fetching file."))),
};
Ok(get_content_as_filename::v1::Response { Ok(get_content_as_filename::v1::Response {
file: content.expect("entire file contents"), file: content.expect("entire file contents"),
+3 -3
View File
@@ -1,7 +1,7 @@
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Error, Result, at, debug_warn, Err, Result, at, debug_warn,
matrix::{ matrix::{
event::{Event, Matches}, event::{Event, Matches},
pdu::PduCount, pdu::PduCount,
@@ -322,7 +322,7 @@ where
if server_ignored { if server_ignored {
// the sender's server is ignored, so ignore this event // the sender's server is ignored, so ignore this event
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::SenderIgnored { sender: None }, ErrorKind::SenderIgnored { sender: None },
"The sender's server is ignored by this server.", "The sender's server is ignored by this server.",
)); ));
@@ -331,7 +331,7 @@ where
if user_ignored && !services.config.send_messages_from_ignored_users_to_client { if user_ignored && !services.config.send_messages_from_ignored_users_to_client {
// the recipient of this PDU has the sender ignored, and we're not // the recipient of this PDU has the sender ignored, and we're not
// configured to send ignored messages to clients // configured to send ignored messages to clients
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::SenderIgnored { sender: Some(event.sender().to_owned()) }, ErrorKind::SenderIgnored { sender: Some(event.sender().to_owned()) },
"You have ignored this sender.", "You have ignored this sender.",
)); ));
-2
View File
@@ -6,7 +6,6 @@ pub(super) mod appservice;
pub(super) mod backup; pub(super) mod backup;
pub(super) mod capabilities; pub(super) mod capabilities;
pub(super) mod context; pub(super) mod context;
pub(super) mod dehydrated_device;
pub(super) mod device; pub(super) mod device;
pub(super) mod directory; pub(super) mod directory;
pub(super) mod filter; pub(super) mod filter;
@@ -50,7 +49,6 @@ pub(super) use appservice::*;
pub(super) use backup::*; pub(super) use backup::*;
pub(super) use capabilities::*; pub(super) use capabilities::*;
pub(super) use context::*; pub(super) use context::*;
pub(super) use dehydrated_device::*;
pub(super) use device::*; pub(super) use device::*;
pub(super) use directory::*; pub(super) use directory::*;
pub(super) use filter::*; pub(super) use filter::*;
+16 -16
View File
@@ -1,5 +1,5 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Error, Result, err}; use conduwuit::{Err, Result, err};
use conduwuit_service::Services; use conduwuit_service::Services;
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, CanonicalJsonObject, CanonicalJsonValue,
@@ -243,27 +243,27 @@ pub(crate) async fn set_pushrule_route(
body.before.as_deref(), body.before.as_deref(),
) { ) {
let err = match error { let err = match error {
| InsertPushRuleError::ServerDefaultRuleId => Error::BadRequest( | InsertPushRuleError::ServerDefaultRuleId => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Rule IDs starting with a dot are reserved for server-default rules.", "Rule IDs starting with a dot are reserved for server-default rules.",
), )),
| InsertPushRuleError::InvalidRuleId => Error::BadRequest( | InsertPushRuleError::InvalidRuleId => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Rule ID containing invalid characters.", "Rule ID containing invalid characters.",
), )),
| InsertPushRuleError::RelativeToServerDefaultRule => Error::BadRequest( | InsertPushRuleError::RelativeToServerDefaultRule => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Can't place a push rule relatively to a server-default rule.", "Can't place a push rule relatively to a server-default rule.",
), )),
| InsertPushRuleError::UnknownRuleId => Error::BadRequest( | InsertPushRuleError::UnknownRuleId => err!(BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"The before or after rule could not be found.", "The before or after rule could not be found.",
), )),
| InsertPushRuleError::BeforeHigherThanAfter => Error::BadRequest( | InsertPushRuleError::BeforeHigherThanAfter => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"The before rule has a higher priority than the after rule.", "The before rule has a higher priority than the after rule.",
), )),
| _ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."), | _ => err!(BadRequest(ErrorKind::InvalidParam, "Invalid data.")),
}; };
return Err(err); return Err(err);
@@ -433,13 +433,13 @@ pub(crate) async fn delete_pushrule_route(
.remove(body.kind.clone(), &body.rule_id) .remove(body.kind.clone(), &body.rule_id)
{ {
let err = match error { let err = match error {
| RemovePushRuleError::ServerDefault => Error::BadRequest( | RemovePushRuleError::ServerDefault => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Cannot delete a server-default pushrule.", "Cannot delete a server-default pushrule.",
), )),
| RemovePushRuleError::NotFound => | RemovePushRuleError::NotFound =>
Error::BadRequest(ErrorKind::NotFound, "Push rule not found."), err!(BadRequest(ErrorKind::NotFound, "Push rule not found.")),
| _ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."), | _ => err!(BadRequest(ErrorKind::InvalidParam, "Invalid data.")),
}; };
return Err(err); return Err(err);
+6 -6
View File
@@ -2,7 +2,7 @@ use std::cmp::max;
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Error, Event, Result, RoomVersion, debug, err, info, Err, Event, Result, RoomVersion, debug, err, info,
matrix::{StateKey, pdu::PduBuilder}, matrix::{StateKey, pdu::PduBuilder},
}; };
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
@@ -58,7 +58,7 @@ pub(crate) async fn upgrade_room_route(
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services.server.supported_room_version(&body.new_version) { if !services.server.supported_room_version(&body.new_version) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::UnsupportedRoomVersion, ErrorKind::UnsupportedRoomVersion,
"This server does not support that room version.", "This server does not support that room version.",
)); ));
@@ -170,7 +170,7 @@ pub(crate) async fn upgrade_room_route(
"creator".into(), "creator".into(),
json!(&sender_user).try_into().map_err(|e| { json!(&sender_user).try_into().map_err(|e| {
info!("Error forming creation event: {e}"); info!("Error forming creation event: {e}");
Error::BadRequest(ErrorKind::BadJson, "Error forming creation event") err!(BadRequest(ErrorKind::BadJson, "Error forming creation event"))
})?, })?,
); );
}, },
@@ -186,13 +186,13 @@ pub(crate) async fn upgrade_room_route(
"room_version".into(), "room_version".into(),
json!(&body.new_version) json!(&body.new_version)
.try_into() .try_into()
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?, .map_err(|_| err!(BadRequest(ErrorKind::BadJson, "Error forming creation event")))?,
); );
create_event_content.insert( create_event_content.insert(
"predecessor".into(), "predecessor".into(),
json!(predecessor) json!(predecessor)
.try_into() .try_into()
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?, .map_err(|_| err!(BadRequest(ErrorKind::BadJson, "Error forming creation event")))?,
); );
// Validate creation event content // Validate creation event content
@@ -203,7 +203,7 @@ pub(crate) async fn upgrade_room_route(
) )
.is_err() .is_err()
{ {
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")); return Err!(BadRequest(ErrorKind::BadJson, "Error forming creation event"));
} }
let create_event_id = services let create_event_id = services
+3 -3
View File
@@ -50,8 +50,8 @@ pub(crate) async fn send_message_event_route(
// Check if this is a new transaction id // Check if this is a new transaction id
if let Ok(response) = services if let Ok(response) = services
.transactions .transaction_ids
.get_client_txn(sender_user, sender_device, &body.txn_id) .existing_txnid(sender_user, sender_device, &body.txn_id)
.await .await
{ {
// The client might have sent a txnid of the /sendToDevice endpoint // The client might have sent a txnid of the /sendToDevice endpoint
@@ -92,7 +92,7 @@ pub(crate) async fn send_message_event_route(
) )
.await?; .await?;
services.transactions.add_client_txnid( services.transaction_ids.add_txnid(
sender_user, sender_user,
sender_device, sender_device,
&body.txn_id, &body.txn_id,
+4 -4
View File
@@ -3,7 +3,7 @@ use std::time::Duration;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug, err, info, Err, Result, debug, err, info,
utils::{self, ReadyExt, hash}, utils::{self, ReadyExt, hash},
warn, warn,
}; };
@@ -191,7 +191,7 @@ pub(crate) async fn handle_login(
} }
if services.users.is_locked(&user_id).await? { if services.users.is_locked(&user_id).await? {
return Err(Error::BadRequest(ErrorKind::UserLocked, "This account has been locked.")); return Err!(BadRequest(ErrorKind::UserLocked, "This account has been locked."));
} }
if services.users.is_login_disabled(&user_id).await { if services.users.is_login_disabled(&user_id).await {
@@ -390,7 +390,7 @@ pub(crate) async fn login_token_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
@@ -402,7 +402,7 @@ pub(crate) async fn login_token_route(
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, json); .create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err!(Request(NotJson("No JSON body was sent when required."))); return Err!(Request(NotJson("No JSON body was sent when required.")));
+24 -14
View File
@@ -1,5 +1,5 @@
use conduwuit::{ use conduwuit::{
Event, PduEvent, Result, at, debug_warn, Event, PduCount, PduEvent, Result, at, debug_warn,
pdu::EventHash, pdu::EventHash,
trace, trace,
utils::{self, IterStream, future::ReadyEqExt, stream::WidebandExt as _}, utils::{self, IterStream, future::ReadyEqExt, stream::WidebandExt as _},
@@ -68,13 +68,9 @@ pub(super) async fn load_left_room(
return Ok(None); return Ok(None);
} }
// return early if: // return early if this is an incremental sync, and we've already synced this
// - this is an initial sync and the room filter doesn't include leaves, or // leave to the user, and `include_leave` isn't set on the filter.
// - this is an incremental sync, and we've already synced the leave, and the if !filter.room.include_leave && last_sync_end_count >= Some(left_count) {
// room filter doesn't include leaves
if last_sync_end_count.is_none_or(|last_sync_end_count| last_sync_end_count >= left_count)
&& !filter.room.include_leave
{
return Ok(None); return Ok(None);
} }
@@ -199,13 +195,27 @@ async fn build_left_state_and_timeline(
leave_shortstatehash: ShortStateHash, leave_shortstatehash: ShortStateHash,
prev_membership_event: PduEvent, prev_membership_event: PduEvent,
) -> Result<(TimelinePdus, Vec<PduEvent>)> { ) -> Result<(TimelinePdus, Vec<PduEvent>)> {
let SyncContext { syncing_user, filter, .. } = sync_context; let SyncContext {
syncing_user,
last_sync_end_count,
filter,
..
} = sync_context;
let timeline_start_count = services let timeline_start_count = if let Some(last_sync_end_count) = last_sync_end_count {
.rooms // for incremental syncs, start the timeline after `since`
.timeline PduCount::Normal(last_sync_end_count)
.get_pdu_count(&prev_membership_event.event_id) } else {
.await?; // for initial syncs, start the timeline after the previous membership
// event. we don't want to include the membership event itself
// because clients get confused when they see a `join`
// membership event in a `leave` room.
services
.rooms
.timeline
.get_pdu_count(&prev_membership_event.event_id)
.await?
};
// end the timeline at the user's leave event // end the timeline at the user's leave event
let timeline_end_count = services let timeline_end_count = services
+6 -13
View File
@@ -11,7 +11,7 @@ use std::{
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Result, at, extract_variant, Result, extract_variant,
utils::{ utils::{
ReadyExt, TryFutureExtExt, ReadyExt, TryFutureExtExt,
stream::{BroadbandExt, Tools, WidebandExt}, stream::{BroadbandExt, Tools, WidebandExt},
@@ -297,18 +297,12 @@ pub(crate) async fn build_sync_events(
.rooms .rooms
.state_cache .state_cache
.rooms_left(syncing_user) .rooms_left(syncing_user)
.broad_filter_map(|(room_id, leave_pdu)| async { .broad_filter_map(|(room_id, leave_pdu)| {
let left_room = load_left_room(services, context, room_id.clone(), leave_pdu).await; load_left_room(services, context, room_id.clone(), leave_pdu)
.map_ok(move |left_room| (room_id, left_room))
match left_room { .ok()
| Ok(Some(left_room)) => Some((room_id, left_room)),
| Ok(None) => None,
| Err(err) => {
warn!(?err, %room_id, "error loading joined room");
None
},
}
}) })
.ready_filter_map(|(room_id, left_room)| left_room.map(|left_room| (room_id, left_room)))
.collect(); .collect();
let invited_rooms = services let invited_rooms = services
@@ -391,7 +385,6 @@ pub(crate) async fn build_sync_events(
last_sync_end_count, last_sync_end_count,
Some(current_count), Some(current_count),
) )
.map(at!(1))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let device_one_time_keys_count = services let device_one_time_keys_count = services
+1 -4
View File
@@ -336,9 +336,7 @@ where
let ranges = list.ranges.clone(); let ranges = list.ranges.clone();
for mut range in ranges { for mut range in ranges {
range.0 = range range.0 = uint!(0);
.0
.min(UInt::try_from(active_rooms.len()).unwrap_or(UInt::MAX));
range.1 = range.1.checked_add(uint!(1)).unwrap_or(range.1); range.1 = range.1.checked_add(uint!(1)).unwrap_or(range.1);
range.1 = range range.1 = range
.1 .1
@@ -1029,7 +1027,6 @@ async fn collect_to_device(
events: services events: services
.users .users
.get_to_device_events(sender_user, sender_device, None, Some(next_batch)) .get_to_device_events(sender_user, sender_device, None, Some(next_batch))
.map(at!(1))
.collect() .collect()
.await, .await,
}) })
+6 -6
View File
@@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result}; use conduwuit::{Result, err};
use conduwuit_service::sending::EduBuf; use conduwuit_service::sending::EduBuf;
use futures::StreamExt; use futures::StreamExt;
use ruma::{ use ruma::{
@@ -26,8 +26,8 @@ pub(crate) async fn send_event_to_device_route(
// Check if this is a new transaction id // Check if this is a new transaction id
if services if services
.transactions .transaction_ids
.get_client_txn(sender_user, sender_device, &body.txn_id) .existing_txnid(sender_user, sender_device, &body.txn_id)
.await .await
.is_ok() .is_ok()
{ {
@@ -66,7 +66,7 @@ pub(crate) async fn send_event_to_device_route(
let event = event let event = event
.deserialize_as() .deserialize_as()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid"))?; .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Event is invalid")))?;
match target_device_id_maybe { match target_device_id_maybe {
| DeviceIdOrAllDevices::DeviceId(target_device_id) => { | DeviceIdOrAllDevices::DeviceId(target_device_id) => {
@@ -104,8 +104,8 @@ pub(crate) async fn send_event_to_device_route(
// Save transaction id with empty data // Save transaction id with empty data
services services
.transactions .transaction_ids
.add_client_txnid(sender_user, sender_device, &body.txn_id, &[]); .add_txnid(sender_user, sender_device, &body.txn_id, &[]);
Ok(send_event_to_device::v3::Response {}) Ok(send_event_to_device::v3::Response {})
} }
-1
View File
@@ -50,7 +50,6 @@ pub(crate) async fn get_supported_versions_route(
("org.matrix.msc2836".to_owned(), true), /* threading/threads (https://github.com/matrix-org/matrix-spec-proposals/pull/2836) */ ("org.matrix.msc2836".to_owned(), true), /* threading/threads (https://github.com/matrix-org/matrix-spec-proposals/pull/2836) */
("org.matrix.msc2946".to_owned(), true), /* spaces/hierarchy summaries (https://github.com/matrix-org/matrix-spec-proposals/pull/2946) */ ("org.matrix.msc2946".to_owned(), true), /* spaces/hierarchy summaries (https://github.com/matrix-org/matrix-spec-proposals/pull/2946) */
("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */ ("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */
("org.matrix.msc3814".to_owned(), true), /* dehydrated devices */
("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */ ("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */
("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */ ("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */
("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */ ("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */
+8 -33
View File
@@ -1,11 +1,8 @@
use axum::{Json, extract::State, response::IntoResponse}; use axum::{Json, extract::State, response::IntoResponse};
use conduwuit::{Error, Result}; use conduwuit::{Err, Result};
use ruma::api::client::{ use ruma::api::client::discovery::{
discovery::{ discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo}, discover_support::{self, Contact},
discover_support::{self, Contact},
},
error::ErrorKind,
}; };
use crate::Ruma; use crate::Ruma;
@@ -19,7 +16,7 @@ pub(crate) async fn well_known_client(
) -> Result<discover_homeserver::Response> { ) -> Result<discover_homeserver::Response> {
let client_url = match services.config.well_known.client.as_ref() { let client_url = match services.config.well_known.client.as_ref() {
| Some(url) => url.to_string(), | Some(url) => url.to_string(),
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), | None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")),
}; };
Ok(discover_homeserver::Response { Ok(discover_homeserver::Response {
@@ -27,32 +24,10 @@ pub(crate) async fn well_known_client(
identity_server: None, identity_server: None,
sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }), sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }),
tile_server: None, tile_server: None,
rtc_foci: services rtc_foci: services.config.well_known.rtc_focus_server_urls.clone(),
.config
.matrix_rtc
.effective_foci(&services.config.well_known.rtc_focus_server_urls)
.to_vec(),
}) })
} }
/// # `GET /_matrix/client/v1/rtc/transports`
/// # `GET /_matrix/client/unstable/org.matrix.msc4143/rtc/transports`
///
/// Returns the list of MatrixRTC foci (transports) configured for this
/// homeserver, implementing MSC4143.
pub(crate) async fn get_rtc_transports(
State(services): State<crate::State>,
_body: Ruma<ruma::api::client::discovery::get_rtc_transports::Request>,
) -> Result<ruma::api::client::discovery::get_rtc_transports::Response> {
Ok(ruma::api::client::discovery::get_rtc_transports::Response::new(
services
.config
.matrix_rtc
.effective_foci(&services.config.well_known.rtc_focus_server_urls)
.to_vec(),
))
}
/// # `GET /.well-known/matrix/support` /// # `GET /.well-known/matrix/support`
/// ///
/// Server support contact and support page of a homeserver's domain. /// Server support contact and support page of a homeserver's domain.
@@ -110,7 +85,7 @@ pub(crate) async fn well_known_support(
if contacts.is_empty() && support_page.is_none() { if contacts.is_empty() && support_page.is_none() {
// No admin room, no configured contacts, and no support page // No admin room, no configured contacts, and no support page
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")); return Err!(BadRequest(ErrorKind::NotFound, "Not found."));
} }
Ok(discover_support::Response { contacts, support_page }) Ok(discover_support::Response { contacts, support_page })
@@ -127,7 +102,7 @@ pub(crate) async fn syncv3_client_server_json(
| Some(url) => url.to_string(), | Some(url) => url.to_string(),
| None => match services.config.well_known.server.as_ref() { | None => match services.config.well_known.server.as_ref() {
| Some(url) => url.to_string(), | Some(url) => url.to_string(),
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), | None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")),
}, },
}; };
-5
View File
@@ -160,10 +160,6 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::update_device_route) .ruma_route(&client::update_device_route)
.ruma_route(&client::delete_device_route) .ruma_route(&client::delete_device_route)
.ruma_route(&client::delete_devices_route) .ruma_route(&client::delete_devices_route)
.ruma_route(&client::put_dehydrated_device_route)
.ruma_route(&client::delete_dehydrated_device_route)
.ruma_route(&client::get_dehydrated_device_route)
.ruma_route(&client::get_dehydrated_events_route)
.ruma_route(&client::get_tags_route) .ruma_route(&client::get_tags_route)
.ruma_route(&client::update_tag_route) .ruma_route(&client::update_tag_route)
.ruma_route(&client::delete_tag_route) .ruma_route(&client::delete_tag_route)
@@ -188,7 +184,6 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::put_suspended_status) .ruma_route(&client::put_suspended_status)
.ruma_route(&client::well_known_support) .ruma_route(&client::well_known_support)
.ruma_route(&client::well_known_client) .ruma_route(&client::well_known_client)
.ruma_route(&client::get_rtc_transports)
.route("/_conduwuit/server_version", get(client::conduwuit_server_version)) .route("/_conduwuit/server_version", get(client::conduwuit_server_version))
.route("/_continuwuity/server_version", get(client::conduwuit_server_version)) .route("/_continuwuity/server_version", get(client::conduwuit_server_version))
.ruma_route(&client::room_initial_sync_route) .ruma_route(&client::room_initial_sync_route)
+28 -46
View File
@@ -4,7 +4,7 @@ use axum_extra::{
headers::{Authorization, authorization::Bearer}, headers::{Authorization, authorization::Bearer},
typed_header::TypedHeaderRejectionReason, typed_header::TypedHeaderRejectionReason,
}; };
use conduwuit::{Err, Error, Result, debug_error, err, warn}; use conduwuit::{Err, Result, debug_error, err, warn};
use futures::{ use futures::{
TryFutureExt, TryFutureExt,
future::{ future::{
@@ -14,8 +14,7 @@ use futures::{
pin_mut, pin_mut,
}; };
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, DeviceId, OwnedDeviceId, OwnedServerName, CanonicalJsonObject, CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
OwnedUserId, UserId,
api::{ api::{
AuthScheme, IncomingRequest, Metadata, AuthScheme, IncomingRequest, Metadata,
client::{ client::{
@@ -67,17 +66,23 @@ pub(super) async fn auth(
if metadata.authentication == AuthScheme::None { if metadata.authentication == AuthScheme::None {
match metadata { match metadata {
| &get_public_rooms::v3::Request::METADATA => { | &get_public_rooms::v3::Request::METADATA => {
match token { if !services
| Token::Appservice(_) | Token::User(_) => { .server
// we should have validated the token above .config
// already .allow_public_room_directory_without_auth
}, {
| Token::None | Token::Invalid => { match token {
return Err(Error::BadRequest( | Token::Appservice(_) | Token::User(_) => {
ErrorKind::MissingToken, // we should have validated the token above
"Missing or invalid access token.", // already
)); },
}, | Token::None | Token::Invalid => {
return Err!(BadRequest(
ErrorKind::MissingToken,
"Missing or invalid access token.",
));
},
}
} }
}, },
| &get_profile::v3::Request::METADATA | &get_profile::v3::Request::METADATA
@@ -91,7 +96,7 @@ pub(super) async fn auth(
// already // already
}, },
| Token::None | Token::Invalid => { | Token::None | Token::Invalid => {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::MissingToken, ErrorKind::MissingToken,
"Missing or invalid access token.", "Missing or invalid access token.",
)); ));
@@ -125,10 +130,10 @@ pub(super) async fn auth(
appservice_info: None, appservice_info: None,
}) })
} else { } else {
Err(Error::BadRequest(ErrorKind::MissingToken, "Missing access token.")) Err!(BadRequest(ErrorKind::MissingToken, "Missing access token."))
} }
}, },
| _ => Err(Error::BadRequest(ErrorKind::MissingToken, "Missing access token.")), | _ => Err!(BadRequest(ErrorKind::MissingToken, "Missing access token.")),
}, },
| ( | (
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None, AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None,
@@ -144,7 +149,7 @@ pub(super) async fn auth(
&ruma::api::client::session::logout::v3::Request::METADATA &ruma::api::client::session::logout::v3::Request::METADATA
| &ruma::api::client::session::logout_all::v3::Request::METADATA | &ruma::api::client::session::logout_all::v3::Request::METADATA
) { ) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::UserLocked, ErrorKind::UserLocked,
"This account has been locked.", "This account has been locked.",
)); ));
@@ -169,11 +174,11 @@ pub(super) async fn auth(
appservice_info: None, appservice_info: None,
}), }),
| (AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) => | (AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) =>
Err(Error::BadRequest( Err!(BadRequest(
ErrorKind::Unauthorized, ErrorKind::Unauthorized,
"Only server signatures should be used on this endpoint.", "Only server signatures should be used on this endpoint.",
)), )),
| (AuthScheme::AppserviceToken, Token::User(_)) => Err(Error::BadRequest( | (AuthScheme::AppserviceToken, Token::User(_)) => Err!(BadRequest(
ErrorKind::Unauthorized, ErrorKind::Unauthorized,
"Only appservice access tokens should be used on this endpoint.", "Only appservice access tokens should be used on this endpoint.",
)), )),
@@ -191,13 +196,13 @@ pub(super) async fn auth(
appservice_info: None, appservice_info: None,
}) })
} else { } else {
Err(Error::BadRequest( Err!(BadRequest(
ErrorKind::UnknownToken { soft_logout: false }, ErrorKind::UnknownToken { soft_logout: false },
"Unknown access token.", "Unknown access token.",
)) ))
} }
}, },
| (_, Token::Invalid) => Err(Error::BadRequest( | (_, Token::Invalid) => Err!(BadRequest(
ErrorKind::UnknownToken { soft_logout: false }, ErrorKind::UnknownToken { soft_logout: false },
"Unknown access token.", "Unknown access token.",
)), )),
@@ -229,33 +234,10 @@ async fn auth_appservice(
return Err!(Request(Exclusive("User is not in namespace."))); return Err!(Request(Exclusive("User is not in namespace.")));
} }
// MSC3202/MSC4190: Handle device_id masquerading for appservices.
// The device_id can be provided via `device_id` or
// `org.matrix.msc3202.device_id` query parameter.
let sender_device = if let Some(ref device_id_str) = request.query.device_id {
let device_id: &DeviceId = device_id_str.as_str().into();
// Verify the device exists for this user
if services
.users
.get_device_metadata(&user_id, device_id)
.await
.is_err()
{
return Err!(Request(Forbidden(
"Device does not exist for user or appservice cannot masquerade as this device."
)));
}
Some(device_id.to_owned())
} else {
None
};
Ok(Auth { Ok(Auth {
origin: None, origin: None,
sender_user: Some(user_id), sender_user: Some(user_id),
sender_device, sender_device: None,
appservice_info: Some(*info), appservice_info: Some(*info),
}) })
} }
-4
View File
@@ -11,10 +11,6 @@ use service::Services;
pub(super) struct QueryParams { pub(super) struct QueryParams {
pub(super) access_token: Option<String>, pub(super) access_token: Option<String>,
pub(super) user_id: Option<String>, pub(super) user_id: Option<String>,
/// Device ID for appservice device masquerading (MSC3202/MSC4190).
/// Can be provided as `device_id` or `org.matrix.msc3202.device_id`.
#[serde(alias = "org.matrix.msc3202.device_id")]
pub(super) device_id: Option<String>,
} }
pub(super) struct Request { pub(super) struct Request {
+3 -6
View File
@@ -1,12 +1,9 @@
use std::{borrow::Borrow, iter::once}; use std::{borrow::Borrow, iter::once};
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Error, Result, info, utils::stream::ReadyExt}; use conduwuit::{Err, Error, Result, err, info, utils::stream::ReadyExt};
use futures::StreamExt; use futures::StreamExt;
use ruma::{ use ruma::{RoomId, api::federation::authorization::get_event_authorization};
RoomId,
api::{client::error::ErrorKind, federation::authorization::get_event_authorization},
};
use super::AccessCheck; use super::AccessCheck;
use crate::Ruma; use crate::Ruma;
@@ -47,7 +44,7 @@ pub(crate) async fn get_event_authorization_route(
.timeline .timeline
.get_pdu_json(&body.event_id) .get_pdu_json(&body.event_id)
.await .await
.map_err(|_| Error::BadRequest(ErrorKind::NotFound, "Event not found."))?; .map_err(|_| err!(BadRequest(ErrorKind::NotFound, "Event not found.")))?;
let room_id_str = event let room_id_str = event
.get("room_id") .get("room_id")
+2 -2
View File
@@ -2,7 +2,7 @@ use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use base64::{Engine as _, engine::general_purpose}; use base64::{Engine as _, engine::general_purpose};
use conduwuit::{ use conduwuit::{
Err, Error, PduEvent, Result, err, error, Err, PduEvent, Result, err, error,
matrix::{Event, event::gen_event_id}, matrix::{Event, event::gen_event_id},
utils::{self, hash::sha256}, utils::{self, hash::sha256},
warn, warn,
@@ -33,7 +33,7 @@ pub(crate) async fn create_invite_route(
.await?; .await?;
if !services.server.supported_room_version(&body.room_version) { if !services.server.supported_room_version(&body.room_version) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: body.room_version.clone() }, ErrorKind::IncompatibleRoomVersion { room_version: body.room_version.clone() },
"Server does not support this room version.", "Server does not support this room version.",
)); ));
+2 -2
View File
@@ -1,7 +1,7 @@
use std::borrow::ToOwned; use std::borrow::ToOwned;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn}; use conduwuit::{Err, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn};
use conduwuit_service::Services; use conduwuit_service::Services;
use futures::StreamExt; use futures::StreamExt;
use ruma::{ use ruma::{
@@ -80,7 +80,7 @@ pub(crate) async fn create_join_event_template_route(
let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?; let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?;
if !body.ver.contains(&room_version_id) { if !body.ver.contains(&room_version_id) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id }, ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
"Room version not supported.", "Room version not supported.",
)); ));
+3 -3
View File
@@ -1,6 +1,6 @@
use RoomVersionId::*; use RoomVersionId::*;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Error, Result, debug_warn, info, matrix::pdu::PduBuilder, warn}; use conduwuit::{Err, Result, debug_warn, info, matrix::pdu::PduBuilder, warn};
use ruma::{ use ruma::{
RoomVersionId, RoomVersionId,
api::{client::error::ErrorKind, federation::knock::create_knock_event_template}, api::{client::error::ErrorKind, federation::knock::create_knock_event_template},
@@ -67,14 +67,14 @@ pub(crate) async fn create_knock_event_template_route(
let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?; let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?;
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6) { if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id }, ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
"Room version does not support knocking.", "Room version does not support knocking.",
)); ));
} }
if !body.ver.contains(&room_version_id) { if !body.ver.contains(&room_version_id) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id }, ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
"Your homeserver does not support the features required to knock on this room.", "Your homeserver does not support the features required to knock on this room.",
)); ));
+11 -5
View File
@@ -1,6 +1,6 @@
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{Error, Result}; use conduwuit::{Err, Result, err};
use ruma::{ use ruma::{
api::{ api::{
client::error::ErrorKind, client::error::ErrorKind,
@@ -25,7 +25,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
.config .config
.allow_public_room_directory_over_federation .allow_public_room_directory_over_federation
{ {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room directory is not public")); return Err!(BadRequest(ErrorKind::forbidden(), "Room directory is not public"));
} }
let response = crate::client::get_public_rooms_filtered_helper( let response = crate::client::get_public_rooms_filtered_helper(
@@ -38,7 +38,10 @@ pub(crate) async fn get_public_rooms_filtered_route(
) )
.await .await
.map_err(|_| { .map_err(|_| {
Error::BadRequest(ErrorKind::Unknown, "Failed to return this server's public room list.") err!(BadRequest(
ErrorKind::Unknown,
"Failed to return this server's public room list."
))
})?; })?;
Ok(get_public_rooms_filtered::v1::Response { Ok(get_public_rooms_filtered::v1::Response {
@@ -62,7 +65,7 @@ pub(crate) async fn get_public_rooms_route(
.globals .globals
.allow_public_room_directory_over_federation() .allow_public_room_directory_over_federation()
{ {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room directory is not public")); return Err!(BadRequest(ErrorKind::forbidden(), "Room directory is not public"));
} }
let response = crate::client::get_public_rooms_filtered_helper( let response = crate::client::get_public_rooms_filtered_helper(
@@ -75,7 +78,10 @@ pub(crate) async fn get_public_rooms_route(
) )
.await .await
.map_err(|_| { .map_err(|_| {
Error::BadRequest(ErrorKind::Unknown, "Failed to return this server's public room list.") err!(BadRequest(
ErrorKind::Unknown,
"Failed to return this server's public room list."
))
})?; })?;
Ok(get_public_rooms::v1::Response { Ok(get_public_rooms::v1::Response {
+5 -6
View File
@@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result, err}; use conduwuit::{Err, Result, err};
use futures::StreamExt; use futures::StreamExt;
use get_profile_information::v1::ProfileField; use get_profile_information::v1::ProfileField;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
@@ -67,17 +67,16 @@ pub(crate) async fn get_profile_information_route(
.config .config
.allow_inbound_profile_lookup_federation_requests .allow_inbound_profile_lookup_federation_requests
{ {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::forbidden(), ErrorKind::forbidden(),
"Profile lookup over federation is not allowed on this homeserver.", "Profile lookup over federation is not allowed on this homeserver.",
)); ));
} }
if !services.globals.server_is_ours(body.user_id.server_name()) { if !services.globals.server_is_ours(body.user_id.server_name()) {
return Err(Error::BadRequest( return Err!(
ErrorKind::InvalidParam, BadRequest(ErrorKind::InvalidParam, "User does not belong to this server.",)
"User does not belong to this server.", );
));
} }
let mut displayname = None; let mut displayname = None;
+64 -214
View File
@@ -1,33 +1,27 @@
use std::{ use std::{collections::BTreeMap, net::IpAddr, time::Instant};
collections::{BTreeMap, HashMap, HashSet},
net::IpAddr,
time::{Duration, Instant},
};
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug, debug_warn, err, error, Err, Error, Result, debug, debug_warn, err, error,
result::LogErr, result::LogErr,
state_res::lexicographical_topological_sort,
trace, trace,
utils::{ utils::{
IterStream, ReadyExt, millis_since_unix_epoch, IterStream, ReadyExt, millis_since_unix_epoch,
stream::{BroadbandExt, TryBroadbandExt, automatic_width}, stream::{BroadbandExt, TryBroadbandExt, automatic_width},
}, },
warn,
}; };
use conduwuit_service::{ use conduwuit_service::{
Services, Services,
sending::{EDU_LIMIT, PDU_LIMIT}, sending::{EDU_LIMIT, PDU_LIMIT},
}; };
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt}; use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
use http::StatusCode;
use itertools::Itertools; use itertools::Itertools;
use ruma::{ use ruma::{
CanonicalJsonObject, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, CanonicalJsonObject, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, ServerName, UserId,
RoomId, ServerName, UserId,
api::{ api::{
client::error::{ErrorKind, ErrorKind::LimitExceeded}, client::error::ErrorKind,
federation::transactions::{ federation::transactions::{
edu::{ edu::{
DeviceListUpdateContent, DirectDeviceContent, Edu, PresenceContent, DeviceListUpdateContent, DirectDeviceContent, Edu, PresenceContent,
@@ -38,16 +32,9 @@ use ruma::{
}, },
}, },
events::receipt::{ReceiptEvent, ReceiptEventContent, ReceiptType}, events::receipt::{ReceiptEvent, ReceiptEventContent, ReceiptType},
int,
serde::Raw, serde::Raw,
to_device::DeviceIdOrAllDevices, to_device::DeviceIdOrAllDevices,
uint,
}; };
use service::transactions::{
FederationTxnState, TransactionError, TxnKey, WrappedTransactionResponse,
};
use tokio::sync::watch::{Receiver, Sender};
use tracing::instrument;
use crate::Ruma; use crate::Ruma;
@@ -57,6 +44,15 @@ type Pdu = (OwnedRoomId, OwnedEventId, CanonicalJsonObject);
/// # `PUT /_matrix/federation/v1/send/{txnId}` /// # `PUT /_matrix/federation/v1/send/{txnId}`
/// ///
/// Push EDUs and PDUs to this server. /// Push EDUs and PDUs to this server.
#[tracing::instrument(
name = "txn",
level = "debug",
skip_all,
fields(
%client,
origin = body.origin().as_str()
),
)]
pub(crate) async fn send_transaction_message_route( pub(crate) async fn send_transaction_message_route(
State(services): State<crate::State>, State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
@@ -80,73 +76,16 @@ pub(crate) async fn send_transaction_message_route(
))); )));
} }
let txn_key = (body.origin().to_owned(), body.transaction_id.clone());
// Atomically check cache, join active, or start new transaction
match services
.transactions
.get_or_start_federation_txn(txn_key.clone())?
{
| FederationTxnState::Cached(response) => {
// Already responded
Ok(response)
},
| FederationTxnState::Active(receiver) => {
// Another thread is processing
wait_for_result(receiver).await
},
| FederationTxnState::Started { receiver, sender } => {
// We're the first, spawn the processing task
services
.server
.runtime()
.spawn(process_inbound_transaction(services, body, client, txn_key, sender));
// and wait for it
wait_for_result(receiver).await
},
}
}
async fn wait_for_result(
mut recv: Receiver<WrappedTransactionResponse>,
) -> Result<send_transaction_message::v1::Response> {
if tokio::time::timeout(Duration::from_secs(50), recv.changed())
.await
.is_err()
{
// Took too long, return 429 to encourage the sender to try again
return Err(Error::BadRequest(
LimitExceeded { retry_after: None },
"Transaction is being still being processed. Please try again later.",
));
}
let value = recv.borrow_and_update();
match value.clone() {
| Some(Ok(response)) => Ok(response),
| Some(Err(err)) => Err(transaction_error_to_response(&err)),
| None => Err(Error::Request(
ErrorKind::Unknown,
"Transaction processing failed unexpectedly".into(),
StatusCode::INTERNAL_SERVER_ERROR,
)),
}
}
#[instrument(
skip_all,
fields(
id = ?body.transaction_id.as_str(),
origin = ?body.origin()
)
)]
async fn process_inbound_transaction(
services: crate::State,
body: Ruma<send_transaction_message::v1::Request>,
client: IpAddr,
txn_key: TxnKey,
sender: Sender<WrappedTransactionResponse>,
) {
let txn_start_time = Instant::now(); let txn_start_time = Instant::now();
trace!(
pdus = body.pdus.len(),
edus = body.edus.len(),
elapsed = ?txn_start_time.elapsed(),
id = %body.transaction_id,
origin = %body.origin(),
"Starting txn",
);
let pdus = body let pdus = body
.pdus .pdus
.iter() .iter()
@@ -163,79 +102,40 @@ async fn process_inbound_transaction(
.filter_map(Result::ok) .filter_map(Result::ok)
.stream(); .stream();
debug!(pdus = body.pdus.len(), edus = body.edus.len(), "Processing transaction",); let results = handle(&services, &client, body.origin(), txn_start_time, pdus, edus).await?;
let results = match handle(&services, &client, body.origin(), pdus, edus).await {
| Ok(results) => results,
| Err(err) => {
fail_federation_txn(services, &txn_key, &sender, err);
return;
},
};
for (id, result) in &results {
if let Err(e) = result {
if matches!(e, Error::BadRequest(ErrorKind::NotFound, _)) {
debug_warn!("Incoming PDU failed {id}: {e:?}");
}
}
}
debug!( debug!(
pdus = body.pdus.len(), pdus = body.pdus.len(),
edus = body.edus.len(), edus = body.edus.len(),
elapsed = ?txn_start_time.elapsed(), elapsed = ?txn_start_time.elapsed(),
"Finished processing transaction" id = %body.transaction_id,
origin = %body.origin(),
"Finished txn",
); );
for (id, result) in &results {
if let Err(e) = result {
if matches!(e, Error::BadRequest { kind: ErrorKind::NotFound, .. }) {
warn!("Incoming PDU failed {id}: {e:?}");
}
}
}
let response = send_transaction_message::v1::Response { Ok(send_transaction_message::v1::Response {
pdus: results pdus: results
.into_iter() .into_iter()
.map(|(e, r)| (e, r.map_err(error::sanitized_message))) .map(|(e, r)| (e, r.map_err(error::sanitized_message)))
.collect(), .collect(),
}; })
services
.transactions
.finish_federation_txn(txn_key, sender, response);
} }
/// Handles a failed federation transaction by sending the error through
/// the channel and cleaning up the transaction state. This allows waiters to
/// receive an appropriate error response.
fn fail_federation_txn(
services: crate::State,
txn_key: &TxnKey,
sender: &Sender<WrappedTransactionResponse>,
err: TransactionError,
) {
debug!("Transaction failed: {err}");
// Remove from active state so the transaction can be retried
services.transactions.remove_federation_txn(txn_key);
// Send the error to any waiters
if let Err(e) = sender.send(Some(Err(err))) {
debug_warn!("Failed to send transaction error to receivers: {e}");
}
}
/// Converts a TransactionError into an appropriate HTTP error response.
fn transaction_error_to_response(err: &TransactionError) -> Error {
match err {
| TransactionError::ShuttingDown => Error::Request(
ErrorKind::Unknown,
"Server is shutting down, please retry later".into(),
StatusCode::SERVICE_UNAVAILABLE,
),
}
}
async fn handle( async fn handle(
services: &Services, services: &Services,
client: &IpAddr, client: &IpAddr,
origin: &ServerName, origin: &ServerName,
started: Instant,
pdus: impl Stream<Item = Pdu> + Send, pdus: impl Stream<Item = Pdu> + Send,
edus: impl Stream<Item = Edu> + Send, edus: impl Stream<Item = Edu> + Send,
) -> std::result::Result<ResolvedMap, TransactionError> { ) -> Result<ResolvedMap> {
// group pdus by room // group pdus by room
let pdus = pdus let pdus = pdus
.collect() .collect()
@@ -252,7 +152,7 @@ async fn handle(
.into_iter() .into_iter()
.try_stream() .try_stream()
.broad_and_then(|(room_id, pdus): (_, Vec<_>)| { .broad_and_then(|(room_id, pdus): (_, Vec<_>)| {
handle_room(services, client, origin, room_id, pdus.into_iter()) handle_room(services, client, origin, started, room_id, pdus.into_iter())
.map_ok(Vec::into_iter) .map_ok(Vec::into_iter)
.map_ok(IterStream::try_stream) .map_ok(IterStream::try_stream)
}) })
@@ -269,51 +169,14 @@ async fn handle(
Ok(results) Ok(results)
} }
/// Attempts to build a localised directed acyclic graph out of the given PDUs,
/// returning them in a topologically sorted order.
///
/// This is used to attempt to process PDUs in an order that respects their
/// dependencies, however it is ultimately the sender's responsibility to send
/// them in a processable order, so this is just a best effort attempt. It does
/// not account for power levels or other tie breaks.
async fn build_local_dag(
pdu_map: &HashMap<OwnedEventId, CanonicalJsonObject>,
) -> Result<Vec<OwnedEventId>> {
debug_assert!(pdu_map.len() >= 2, "needless call to build_local_dag with less than 2 PDUs");
let mut dag: HashMap<OwnedEventId, HashSet<OwnedEventId>> = HashMap::new();
for (event_id, value) in pdu_map {
let prev_events = value
.get("prev_events")
.expect("pdu must have prev_events")
.as_array()
.expect("prev_events must be an array")
.iter()
.map(|v| {
OwnedEventId::parse(v.as_str().expect("prev_events values must be strings"))
.expect("prev_events must be valid event IDs")
})
.collect::<HashSet<OwnedEventId>>();
dag.insert(event_id.clone(), prev_events);
}
lexicographical_topological_sort(&dag, &|_| async {
// Note: we don't bother fetching power levels because that would massively slow
// this function down. This is a best-effort attempt to order events correctly
// for processing, however ultimately that should be the sender's job.
Ok((int!(0), MilliSecondsSinceUnixEpoch(uint!(0))))
})
.await
.map_err(|e| err!("failed to resolve local graph: {e}"))
}
async fn handle_room( async fn handle_room(
services: &Services, services: &Services,
_client: &IpAddr, _client: &IpAddr,
origin: &ServerName, origin: &ServerName,
txn_start_time: Instant,
room_id: OwnedRoomId, room_id: OwnedRoomId,
pdus: impl Iterator<Item = Pdu> + Send, pdus: impl Iterator<Item = Pdu> + Send,
) -> std::result::Result<Vec<(OwnedEventId, Result)>, TransactionError> { ) -> Result<Vec<(OwnedEventId, Result)>> {
let _room_lock = services let _room_lock = services
.rooms .rooms
.event_handler .event_handler
@@ -322,40 +185,27 @@ async fn handle_room(
.await; .await;
let room_id = &room_id; let room_id = &room_id;
let pdu_map: HashMap<OwnedEventId, CanonicalJsonObject> = pdus pdus.try_stream()
.into_iter() .and_then(|(_, event_id, value)| async move {
.map(|(_, event_id, value)| (event_id, value)) services.server.check_running()?;
.collect(); let pdu_start_time = Instant::now();
// Try to sort PDUs by their dependencies, but fall back to arbitrary order on let result = services
// failure (e.g., cycles). This is best-effort; proper ordering is the sender's .rooms
// responsibility. .event_handler
let sorted_event_ids = if pdu_map.len() >= 2 { .handle_incoming_pdu(origin, room_id, &event_id, value, true)
build_local_dag(&pdu_map).await.unwrap_or_else(|e| { .await
debug_warn!("Failed to build local DAG for room {room_id}: {e}"); .map(|_| ());
pdu_map.keys().cloned().collect()
debug!(
pdu_elapsed = ?pdu_start_time.elapsed(),
txn_elapsed = ?txn_start_time.elapsed(),
"Finished PDU {event_id}",
);
Ok((event_id, result))
}) })
} else { .try_collect()
pdu_map.keys().cloned().collect() .await
};
let mut results = Vec::with_capacity(sorted_event_ids.len());
for event_id in sorted_event_ids {
let value = pdu_map
.get(&event_id)
.expect("sorted event IDs must be from the original map")
.clone();
services
.server
.check_running()
.map_err(|_| TransactionError::ShuttingDown)?;
let result = services
.rooms
.event_handler
.handle_incoming_pdu(origin, room_id, &event_id, value, true)
.await
.map(|_| ());
results.push((event_id, result));
}
Ok(results)
} }
async fn handle_edu(services: &Services, client: &IpAddr, origin: &ServerName, edu: Edu) { async fn handle_edu(services: &Services, client: &IpAddr, origin: &ServerName, edu: Edu) {
@@ -628,8 +478,8 @@ async fn handle_edu_direct_to_device(
// Check if this is a new transaction id // Check if this is a new transaction id
if services if services
.transactions .transaction_ids
.get_client_txn(sender, None, message_id) .existing_txnid(sender, None, message_id)
.await .await
.is_ok() .is_ok()
{ {
@@ -648,8 +498,8 @@ async fn handle_edu_direct_to_device(
// Save transaction id with empty data // Save transaction id with empty data
services services
.transactions .transaction_ids
.add_client_txnid(sender, None, message_id, &[]); .add_txnid(sender, None, message_id, &[]);
} }
async fn handle_edu_direct_to_device_user<Event: Send + Sync>( async fn handle_edu_direct_to_device_user<Event: Send + Sync>(
+6 -7
View File
@@ -1,7 +1,7 @@
use std::time::Duration; use std::time::Duration;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result}; use conduwuit::{Err, Result};
use futures::{FutureExt, StreamExt, TryFutureExt}; use futures::{FutureExt, StreamExt, TryFutureExt};
use ruma::api::{ use ruma::api::{
client::error::ErrorKind, client::error::ErrorKind,
@@ -24,7 +24,7 @@ pub(crate) async fn get_devices_route(
body: Ruma<get_devices::v1::Request>, body: Ruma<get_devices::v1::Request>,
) -> Result<get_devices::v1::Response> { ) -> Result<get_devices::v1::Response> {
if !services.globals.user_is_local(&body.user_id) { if !services.globals.user_is_local(&body.user_id) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Tried to access user from other server.", "Tried to access user from other server.",
)); ));
@@ -86,10 +86,9 @@ pub(crate) async fn get_keys_route(
.iter() .iter()
.any(|(u, _)| !services.globals.user_is_local(u)) .any(|(u, _)| !services.globals.user_is_local(u))
{ {
return Err(Error::BadRequest( return Err!(
ErrorKind::InvalidParam, BadRequest(ErrorKind::InvalidParam, "User does not belong to this server.",)
"User does not belong to this server.", );
));
} }
let result = get_keys_helper( let result = get_keys_helper(
@@ -121,7 +120,7 @@ pub(crate) async fn claim_keys_route(
.iter() .iter()
.any(|(u, _)| !services.globals.user_is_local(u)) .any(|(u, _)| !services.globals.user_is_local(u))
{ {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Tried to access user from other server.", "Tried to access user from other server.",
)); ));
+3 -3
View File
@@ -1,6 +1,6 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result}; use conduwuit::{Err, Result};
use ruma::api::{client::error::ErrorKind, federation::discovery::discover_homeserver}; use ruma::api::federation::discovery::discover_homeserver;
use crate::Ruma; use crate::Ruma;
@@ -14,7 +14,7 @@ pub(crate) async fn well_known_server(
Ok(discover_homeserver::Response { Ok(discover_homeserver::Response {
server: match services.server.config.well_known.server.as_ref() { server: match services.server.config.well_known.server.as_ref() {
| Some(server_name) => server_name.to_owned(), | Some(server_name) => server_name.to_owned(),
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), | None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")),
}, },
}) })
} }
+2 -1
View File
@@ -98,7 +98,8 @@ serde-saphyr.workspace = true
serde.workspace = true serde.workspace = true
smallvec.workspace = true smallvec.workspace = true
smallstr.workspace = true smallstr.workspace = true
thiserror.workspace = true snafu.workspace = true
paste.workspace = true
tikv-jemallocator.optional = true tikv-jemallocator.optional = true
tikv-jemallocator.workspace = true tikv-jemallocator.workspace = true
tikv-jemalloc-ctl.optional = true tikv-jemalloc-ctl.optional = true
-1
View File
@@ -174,7 +174,6 @@ pub fn check(config: &Config) -> Result {
if config.allow_registration if config.allow_registration
&& config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse && config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
&& config.registration_token.is_none() && config.registration_token.is_none()
&& config.registration_token_file.is_none()
{ {
warn!( warn!(
"Open registration is enabled via setting \ "Open registration is enabled via setting \
+23 -104
View File
@@ -368,31 +368,6 @@ pub struct Config {
#[serde(default = "default_max_fetch_prev_events")] #[serde(default = "default_max_fetch_prev_events")]
pub max_fetch_prev_events: u16, pub max_fetch_prev_events: u16,
/// How many incoming federation transactions the server is willing to be
/// processing at any given time before it becomes overloaded and starts
/// rejecting further transactions until some slots become available.
///
/// Setting this value too low or too high may result in unstable
/// federation, and setting it too high may cause runaway resource usage.
///
/// default: 150
#[serde(default = "default_max_concurrent_inbound_transactions")]
pub max_concurrent_inbound_transactions: usize,
/// Maximum age (in seconds) for cached federation transaction responses.
/// Entries older than this will be removed during cleanup.
///
/// default: 7200 (2 hours)
#[serde(default = "default_transaction_id_cache_max_age_secs")]
pub transaction_id_cache_max_age_secs: u64,
/// Maximum number of cached federation transaction responses.
/// When the cache exceeds this limit, older entries will be removed.
///
/// default: 8192
#[serde(default = "default_transaction_id_cache_max_entries")]
pub transaction_id_cache_max_entries: usize,
/// Default/base connection timeout (seconds). This is used only by URL /// Default/base connection timeout (seconds). This is used only by URL
/// previews and update/news endpoint checks. /// previews and update/news endpoint checks.
/// ///
@@ -609,25 +584,19 @@ pub struct Config {
pub yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse: bool, pub yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse: bool,
/// A static registration token that new users will have to provide when /// A static registration token that new users will have to provide when
/// creating an account. This token does not supersede tokens from other /// creating an account. If unset and `allow_registration` is true,
/// sources, such as the `!admin token` command or the /// you must set
/// `registration_token_file` configuration option. /// `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
/// to true to allow open registration without any conditions.
///
/// If you do not want to set a static token, the `!admin token` commands
/// may also be used to manage registration tokens.
/// ///
/// example: "o&^uCtes4HPf0Vu@F20jQeeWE7" /// example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
/// ///
/// display: sensitive /// display: sensitive
pub registration_token: Option<String>, pub registration_token: Option<String>,
/// A path to a file containing static registration tokens, one per line.
/// Tokens in this file do not supersede tokens from other sources, such as
/// the `!admin token` command or the `registration_token` configuration
/// option.
///
/// The file will be read once, when Continuwuity starts. It is not
/// currently reread when the server configuration is reloaded. If the file
/// cannot be read, Continuwuity will fail to start.
pub registration_token_file: Option<PathBuf>,
/// The public site key for reCaptcha. If this is provided, reCaptcha /// The public site key for reCaptcha. If this is provided, reCaptcha
/// becomes required during registration. If both captcha *and* /// becomes required during registration. If both captcha *and*
/// registration token are enabled, both will be required during /// registration token are enabled, both will be required during
@@ -684,6 +653,12 @@ pub struct Config {
#[serde(default)] #[serde(default)]
pub allow_public_room_directory_over_federation: bool, pub allow_public_room_directory_over_federation: bool,
/// Set this to true to allow your server's public room directory to be
/// queried without client authentication (access token) through the Client
/// APIs. Set this to false to protect against /publicRooms spiders.
#[serde(default)]
pub allow_public_room_directory_without_auth: bool,
/// Allow guests/unauthenticated users to access TURN credentials. /// Allow guests/unauthenticated users to access TURN credentials.
/// ///
/// This is the equivalent of Synapse's `turn_allow_guests` config option. /// This is the equivalent of Synapse's `turn_allow_guests` config option.
@@ -2074,16 +2049,6 @@ pub struct Config {
pub allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure: pub allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure:
bool, bool,
/// Forcibly disables first-run mode.
///
/// This is intended to be used for Complement testing to allow the test
/// suite to register users, because first-run mode interferes with open
/// registration.
///
/// display: hidden
#[serde(default)]
pub force_disable_first_run_mode: bool,
/// display: nested /// display: nested
#[serde(default)] #[serde(default)]
pub ldap: LdapConfig, pub ldap: LdapConfig,
@@ -2096,12 +2061,6 @@ pub struct Config {
/// display: nested /// display: nested
#[serde(default)] #[serde(default)]
pub blurhashing: BlurhashConfig, pub blurhashing: BlurhashConfig,
/// Configuration for MatrixRTC (MSC4143) transport discovery.
/// display: nested
#[serde(default)]
pub matrix_rtc: MatrixRtcConfig,
#[serde(flatten)] #[serde(flatten)]
#[allow(clippy::zero_sized_map_values)] #[allow(clippy::zero_sized_map_values)]
// this is a catchall, the map shouldn't be zero at runtime // this is a catchall, the map shouldn't be zero at runtime
@@ -2167,16 +2126,17 @@ pub struct WellKnownConfig {
/// listed. /// listed.
pub support_mxid: Option<OwnedUserId>, pub support_mxid: Option<OwnedUserId>,
/// **DEPRECATED**: Use `[global.matrix_rtc].foci` instead.
///
/// A list of MatrixRTC foci URLs which will be served as part of the /// A list of MatrixRTC foci URLs which will be served as part of the
/// MSC4143 client endpoint at /.well-known/matrix/client. /// MSC4143 client endpoint at /.well-known/matrix/client. If you're
/// setting up livekit, you'd want something like:
/// rtc_focus_server_urls = [
/// { type = "livekit", livekit_service_url = "https://livekit.example.com" },
/// ]
/// ///
/// This option is deprecated and will be removed in a future release. /// To disable, set this to be an empty vector (`[]`).
/// Please migrate to the new `[global.matrix_rtc]` config section.
/// ///
/// default: [] /// default: []
#[serde(default)] #[serde(default = "default_rtc_focus_urls")]
pub rtc_focus_server_urls: Vec<RtcFocusInfo>, pub rtc_focus_server_urls: Vec<RtcFocusInfo>,
} }
@@ -2205,43 +2165,6 @@ pub struct BlurhashConfig {
pub blurhash_max_raw_size: u64, pub blurhash_max_raw_size: u64,
} }
#[derive(Clone, Debug, Deserialize, Default)]
#[config_example_generator(filename = "conduwuit-example.toml", section = "global.matrix_rtc")]
pub struct MatrixRtcConfig {
/// A list of MatrixRTC foci (transports) which will be served via the
/// MSC4143 RTC transports endpoint at
/// `/_matrix/client/v1/rtc/transports`. If you're setting up livekit,
/// you'd want something like:
/// ```toml
/// [global.matrix_rtc]
/// foci = [
/// { type = "livekit", livekit_service_url = "https://livekit.example.com" },
/// ]
/// ```
///
/// To disable, set this to an empty list (`[]`).
///
/// default: []
#[serde(default)]
pub foci: Vec<RtcFocusInfo>,
}
impl MatrixRtcConfig {
/// Returns the effective foci, falling back to the deprecated
/// `rtc_focus_server_urls` if the new config is empty.
#[must_use]
pub fn effective_foci<'a>(
&'a self,
deprecated_foci: &'a [RtcFocusInfo],
) -> &'a [RtcFocusInfo] {
if !self.foci.is_empty() {
&self.foci
} else {
deprecated_foci
}
}
}
#[derive(Clone, Debug, Default, Deserialize)] #[derive(Clone, Debug, Default, Deserialize)]
#[config_example_generator(filename = "conduwuit-example.toml", section = "global.ldap")] #[config_example_generator(filename = "conduwuit-example.toml", section = "global.ldap")]
pub struct LdapConfig { pub struct LdapConfig {
@@ -2435,7 +2358,6 @@ const DEPRECATED_KEYS: &[&str] = &[
"well_known_support_email", "well_known_support_email",
"well_known_support_mxid", "well_known_support_mxid",
"registration_token_file", "registration_token_file",
"well_known.rtc_focus_server_urls",
]; ];
impl Config { impl Config {
@@ -2618,12 +2540,6 @@ fn default_pusher_idle_timeout() -> u64 { 15 }
fn default_max_fetch_prev_events() -> u16 { 192_u16 } fn default_max_fetch_prev_events() -> u16 { 192_u16 }
fn default_max_concurrent_inbound_transactions() -> usize { 150 }
fn default_transaction_id_cache_max_age_secs() -> u64 { 60 * 60 * 2 }
fn default_transaction_id_cache_max_entries() -> usize { 8192 }
fn default_tracing_flame_filter() -> String { fn default_tracing_flame_filter() -> String {
cfg!(debug_assertions) cfg!(debug_assertions)
.then_some("trace,h2=off") .then_some("trace,h2=off")
@@ -2719,6 +2635,9 @@ fn default_rocksdb_stats_level() -> u8 { 1 }
#[inline] #[inline]
pub fn default_default_room_version() -> RoomVersionId { RoomVersionId::V11 } pub fn default_default_room_version() -> RoomVersionId { RoomVersionId::V11 }
#[must_use]
pub fn default_rtc_focus_urls() -> Vec<RtcFocusInfo> { vec![] }
fn default_ip_range_denylist() -> Vec<String> { fn default_ip_range_denylist() -> Vec<String> {
vec![ vec![
"127.0.0.0/8".to_owned(), "127.0.0.0/8".to_owned(),
+129 -30
View File
@@ -45,63 +45,162 @@ macro_rules! Err {
macro_rules! err { macro_rules! err {
(Request(Forbidden($level:ident!($($args:tt)+)))) => {{ (Request(Forbidden($level:ident!($($args:tt)+)))) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::Request( $crate::error::Error::Request {
$crate::ruma::api::client::error::ErrorKind::forbidden(), kind: $crate::ruma::api::client::error::ErrorKind::forbidden(),
$crate::err_log!(buf, $level, $($args)+), message: $crate::err_log!(buf, $level, $($args)+),
$crate::http::StatusCode::BAD_REQUEST code: $crate::http::StatusCode::BAD_REQUEST,
) backtrace: Some($crate::snafu::Backtrace::capture()),
}
}}; }};
(Request(Forbidden($($args:tt)+))) => { (Request(Forbidden($($args:tt)+))) => {
$crate::error::Error::Request( {
$crate::ruma::api::client::error::ErrorKind::forbidden(), let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::format_maybe!($($args)+), $crate::error::Error::Request {
$crate::http::StatusCode::BAD_REQUEST kind: $crate::ruma::api::client::error::ErrorKind::forbidden(),
) message,
code: $crate::http::StatusCode::BAD_REQUEST,
backtrace: Some($crate::snafu::Backtrace::capture()),
}
}
};
(Request(NotFound($level:ident!($($args:tt)+)))) => {{
let mut buf = String::new();
$crate::error::Error::Request {
kind: $crate::ruma::api::client::error::ErrorKind::NotFound,
message: $crate::err_log!(buf, $level, $($args)+),
code: $crate::http::StatusCode::BAD_REQUEST,
backtrace: None,
}
}};
(Request(NotFound($($args:tt)+))) => {
{
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::error::Error::Request {
kind: $crate::ruma::api::client::error::ErrorKind::NotFound,
message,
code: $crate::http::StatusCode::BAD_REQUEST,
backtrace: None,
}
}
}; };
(Request($variant:ident($level:ident!($($args:tt)+)))) => {{ (Request($variant:ident($level:ident!($($args:tt)+)))) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::Request( $crate::error::Error::Request {
$crate::ruma::api::client::error::ErrorKind::$variant, kind: $crate::ruma::api::client::error::ErrorKind::$variant,
$crate::err_log!(buf, $level, $($args)+), message: $crate::err_log!(buf, $level, $($args)+),
$crate::http::StatusCode::BAD_REQUEST code: $crate::http::StatusCode::BAD_REQUEST,
) backtrace: Some($crate::snafu::Backtrace::capture()),
}
}}; }};
(Request($variant:ident($($args:tt)+))) => { (Request($variant:ident($($args:tt)+))) => {
$crate::error::Error::Request( {
$crate::ruma::api::client::error::ErrorKind::$variant, let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::format_maybe!($($args)+), $crate::error::Error::Request {
$crate::http::StatusCode::BAD_REQUEST kind: $crate::ruma::api::client::error::ErrorKind::$variant,
) message,
code: $crate::http::StatusCode::BAD_REQUEST,
backtrace: Some($crate::snafu::Backtrace::capture()),
}
}
}; };
(Config($item:literal, $($args:tt)+)) => {{ (Config($item:literal, $($args:tt)+)) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::Config($item, $crate::err_log!(buf, error, config = %$item, $($args)+)) $crate::error::ConfigSnafu {
directive: $item,
message: $crate::err_log!(buf, error, config = %$item, $($args)+),
}.build()
}}; }};
(BadRequest(ErrorKind::NotFound, $($args:tt)+)) => {
{
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::error::Error::Request {
kind: $crate::ruma::api::client::error::ErrorKind::NotFound,
message,
code: $crate::http::StatusCode::BAD_REQUEST,
backtrace: None,
}
}
};
(BadRequest($kind:expr, $($args:tt)+)) => {
{
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::error::BadRequestSnafu {
kind: $kind,
message,
}.build()
}
};
(FeatureDisabled($($args:tt)+)) => {
{
let feature: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::error::FeatureDisabledSnafu { feature }.build()
}
};
(Federation($server:expr, $error:expr $(,)?)) => {
{
$crate::error::FederationSnafu {
server: $server,
error: $error,
}.build()
}
};
(InconsistentRoomState($message:expr, $room_id:expr $(,)?)) => {
{
$crate::error::InconsistentRoomStateSnafu {
message: $message,
room_id: $room_id,
}.build()
}
};
(Uiaa($info:expr $(,)?)) => {
{
$crate::error::UiaaSnafu {
info: $info,
}.build()
}
};
($variant:ident($level:ident!($($args:tt)+))) => {{ ($variant:ident($level:ident!($($args:tt)+))) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::$variant($crate::err_log!(buf, $level, $($args)+)) $crate::paste::paste! {
$crate::error::[<$variant Snafu>] {
message: $crate::err_log!(buf, $level, $($args)+),
}.build()
}
}}; }};
($variant:ident($($args:ident),+)) => {
$crate::error::Error::$variant($($args),+)
};
($variant:ident($($args:tt)+)) => { ($variant:ident($($args:tt)+)) => {
$crate::error::Error::$variant($crate::format_maybe!($($args)+)) $crate::paste::paste! {
{
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::error::[<$variant Snafu>] { message }.build()
}
}
}; };
($level:ident!($($args:tt)+)) => {{ ($level:ident!($($args:tt)+)) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::Err($crate::err_log!(buf, $level, $($args)+)) let message: std::borrow::Cow<'static, str> = $crate::err_log!(buf, $level, $($args)+);
$crate::error::ErrSnafu { message }.build()
}}; }};
($($args:tt)+) => { ($($args:tt)+) => {
$crate::error::Error::Err($crate::format_maybe!($($args)+)) {
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::error::ErrSnafu { message }.build()
}
}; };
} }
@@ -134,7 +233,7 @@ macro_rules! err_log {
}; };
($crate::error::visit)(&mut $out, LEVEL, &__CALLSITE, &mut valueset_all!(__CALLSITE.metadata().fields(), $($fields)+)); ($crate::error::visit)(&mut $out, LEVEL, &__CALLSITE, &mut valueset_all!(__CALLSITE.metadata().fields(), $($fields)+));
($out).into() std::borrow::Cow::<'static, str>::from($out)
}} }}
} }
+448 -140
View File
@@ -6,151 +6,391 @@ mod serde;
use std::{any::Any, borrow::Cow, convert::Infallible, sync::PoisonError}; use std::{any::Any, borrow::Cow, convert::Infallible, sync::PoisonError};
use snafu::{IntoError, prelude::*};
pub use self::{err::visit, log::*}; pub use self::{err::visit, log::*};
#[derive(thiserror::Error)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum Error { pub enum Error {
#[error("PANIC!")] #[snafu(display("PANIC!"))]
PanicAny(Box<dyn Any + Send>), PanicAny {
#[error("PANIC! {0}")] panic: Box<dyn Any + Send>,
Panic(&'static str, Box<dyn Any + Send + 'static>), backtrace: snafu::Backtrace,
},
#[snafu(display("PANIC! {message}"))]
Panic {
message: &'static str,
panic: Box<dyn Any + Send + 'static>,
backtrace: snafu::Backtrace,
},
// std // std
#[error(transparent)] #[snafu(display("Format error: {source}"))]
Fmt(#[from] std::fmt::Error), Fmt {
#[error(transparent)] source: std::fmt::Error,
FromUtf8(#[from] std::string::FromUtf8Error), backtrace: snafu::Backtrace,
#[error("I/O error: {0}")] },
Io(#[from] std::io::Error),
#[error(transparent)] #[snafu(display("UTF-8 conversion error: {source}"))]
ParseFloat(#[from] std::num::ParseFloatError), FromUtf8 {
#[error(transparent)] source: std::string::FromUtf8Error,
ParseInt(#[from] std::num::ParseIntError), backtrace: snafu::Backtrace,
#[error(transparent)] },
Std(#[from] Box<dyn std::error::Error + Send>),
#[error(transparent)] #[snafu(display("I/O error: {source}"))]
ThreadAccessError(#[from] std::thread::AccessError), Io {
#[error(transparent)] source: std::io::Error,
TryFromInt(#[from] std::num::TryFromIntError), backtrace: snafu::Backtrace,
#[error(transparent)] },
TryFromSlice(#[from] std::array::TryFromSliceError),
#[error(transparent)] #[snafu(display("Parse float error: {source}"))]
Utf8(#[from] std::str::Utf8Error), ParseFloat {
source: std::num::ParseFloatError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Parse int error: {source}"))]
ParseInt {
source: std::num::ParseIntError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Error: {source}"))]
Std {
source: Box<dyn std::error::Error + Send>,
backtrace: snafu::Backtrace,
},
#[snafu(display("Thread access error: {source}"))]
ThreadAccessError {
source: std::thread::AccessError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Integer conversion error: {source}"))]
TryFromInt {
source: std::num::TryFromIntError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Slice conversion error: {source}"))]
TryFromSlice {
source: std::array::TryFromSliceError,
backtrace: snafu::Backtrace,
},
#[snafu(display("UTF-8 error: {source}"))]
Utf8 {
source: std::str::Utf8Error,
backtrace: snafu::Backtrace,
},
// third-party // third-party
#[error(transparent)] #[snafu(display("Capacity error: {source}"))]
CapacityError(#[from] arrayvec::CapacityError), CapacityError {
#[error(transparent)] source: arrayvec::CapacityError,
CargoToml(#[from] cargo_toml::Error), backtrace: snafu::Backtrace,
#[error(transparent)] },
Clap(#[from] clap::error::Error),
#[error(transparent)] #[snafu(display("Cargo.toml error: {source}"))]
Extension(#[from] axum::extract::rejection::ExtensionRejection), CargoToml {
#[error(transparent)] source: cargo_toml::Error,
Figment(#[from] figment::error::Error), backtrace: snafu::Backtrace,
#[error(transparent)] },
Http(#[from] http::Error),
#[error(transparent)] #[snafu(display("Clap error: {source}"))]
HttpHeader(#[from] http::header::InvalidHeaderValue), Clap {
#[error("Join error: {0}")] source: clap::error::Error,
JoinError(#[from] tokio::task::JoinError), backtrace: snafu::Backtrace,
#[error(transparent)] },
Json(#[from] serde_json::Error),
#[error(transparent)] #[snafu(display("Extension rejection: {source}"))]
JsParseInt(#[from] ruma::JsParseIntError), // js_int re-export Extension {
#[error(transparent)] source: axum::extract::rejection::ExtensionRejection,
JsTryFromInt(#[from] ruma::JsTryFromIntError), // js_int re-export backtrace: snafu::Backtrace,
#[error(transparent)] },
Path(#[from] axum::extract::rejection::PathRejection),
#[error("Mutex poisoned: {0}")] #[snafu(display("Figment error: {source}"))]
Poison(Cow<'static, str>), Figment {
#[error("Regex error: {0}")] source: figment::error::Error,
Regex(#[from] regex::Error), backtrace: snafu::Backtrace,
#[error("Request error: {0}")] },
Reqwest(#[from] reqwest::Error),
#[error("{0}")] #[snafu(display("HTTP error: {source}"))]
SerdeDe(Cow<'static, str>), Http {
#[error("{0}")] source: http::Error,
SerdeSer(Cow<'static, str>), backtrace: snafu::Backtrace,
#[error(transparent)] },
TomlDe(#[from] toml::de::Error),
#[error(transparent)] #[snafu(display("Invalid HTTP header value: {source}"))]
TomlSer(#[from] toml::ser::Error), HttpHeader {
#[error("Tracing filter error: {0}")] source: http::header::InvalidHeaderValue,
TracingFilter(#[from] tracing_subscriber::filter::ParseError), backtrace: snafu::Backtrace,
#[error("Tracing reload error: {0}")] },
TracingReload(#[from] tracing_subscriber::reload::Error),
#[error(transparent)] #[snafu(display("Join error: {source}"))]
TypedHeader(#[from] axum_extra::typed_header::TypedHeaderRejection), JoinError {
#[error(transparent)] source: tokio::task::JoinError,
YamlDe(#[from] serde_saphyr::Error), backtrace: snafu::Backtrace,
#[error(transparent)] },
YamlSer(#[from] serde_saphyr::ser_error::Error),
#[snafu(display("JSON error: {source}"))]
Json {
source: serde_json::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("JS parse int error: {source}"))]
JsParseInt {
source: ruma::JsParseIntError,
backtrace: snafu::Backtrace,
},
#[snafu(display("JS try from int error: {source}"))]
JsTryFromInt {
source: ruma::JsTryFromIntError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Path rejection: {source}"))]
Path {
source: axum::extract::rejection::PathRejection,
backtrace: snafu::Backtrace,
},
#[snafu(display("Mutex poisoned: {message}"))]
Poison {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
#[snafu(display("Regex error: {source}"))]
Regex {
source: regex::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("Request error: {source}"))]
Reqwest {
source: reqwest::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("{message}"))]
SerdeDe {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
#[snafu(display("{message}"))]
SerdeSer {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
#[snafu(display("TOML deserialization error: {source}"))]
TomlDe {
source: toml::de::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("TOML serialization error: {source}"))]
TomlSer {
source: toml::ser::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("Tracing filter error: {source}"))]
TracingFilter {
source: tracing_subscriber::filter::ParseError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Tracing reload error: {source}"))]
TracingReload {
source: tracing_subscriber::reload::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("Typed header rejection: {source}"))]
TypedHeader {
source: axum_extra::typed_header::TypedHeaderRejection,
backtrace: snafu::Backtrace,
},
#[snafu(display("YAML deserialization error: {source}"))]
YamlDe {
source: serde_saphyr::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("YAML serialization error: {source}"))]
YamlSer {
source: serde_saphyr::ser_error::Error,
backtrace: snafu::Backtrace,
},
// ruma/conduwuit // ruma/conduwuit
#[error("Arithmetic operation failed: {0}")] #[snafu(display("Arithmetic operation failed: {message}"))]
Arithmetic(Cow<'static, str>), Arithmetic {
#[error("{0}: {1}")] message: Cow<'static, str>,
BadRequest(ruma::api::client::error::ErrorKind, &'static str), //TODO: remove backtrace: snafu::Backtrace,
#[error("{0}")] },
BadServerResponse(Cow<'static, str>),
#[error(transparent)] #[snafu(display("{kind}: {message}"))]
CanonicalJson(#[from] ruma::CanonicalJsonError), BadRequest {
#[error("There was a problem with the '{0}' directive in your configuration: {1}")] kind: ruma::api::client::error::ErrorKind,
Config(&'static str, Cow<'static, str>), message: Cow<'static, str>,
#[error("{0}")] backtrace: snafu::Backtrace,
Conflict(Cow<'static, str>), // This is only needed for when a room alias already exists },
#[error(transparent)]
ContentDisposition(#[from] ruma::http_headers::ContentDispositionParseError), #[snafu(display("{message}"))]
#[error("{0}")] BadServerResponse {
Database(Cow<'static, str>), message: Cow<'static, str>,
#[error("Feature '{0}' is not available on this server.")] backtrace: snafu::Backtrace,
FeatureDisabled(Cow<'static, str>), },
#[error("Remote server {0} responded with: {1}")]
Federation(ruma::OwnedServerName, ruma::api::client::error::Error), #[snafu(display("Canonical JSON error: {source}"))]
#[error("{0} in {1}")] CanonicalJson {
InconsistentRoomState(&'static str, ruma::OwnedRoomId), source: ruma::CanonicalJsonError,
#[error(transparent)] backtrace: snafu::Backtrace,
IntoHttp(#[from] ruma::api::error::IntoHttpError), },
#[error("{0}")]
Ldap(Cow<'static, str>), #[snafu(display(
#[error(transparent)] "There was a problem with the '{directive}' directive in your configuration: {message}"
Mxc(#[from] ruma::MxcUriError), ))]
#[error(transparent)] Config {
Mxid(#[from] ruma::IdParseError), directive: &'static str,
#[error("from {0}: {1}")] message: Cow<'static, str>,
Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError), backtrace: snafu::Backtrace,
#[error("{0}: {1}")] },
Request(ruma::api::client::error::ErrorKind, Cow<'static, str>, http::StatusCode),
#[error(transparent)] #[snafu(display("{message}"))]
Ruma(#[from] ruma::api::client::error::Error), Conflict {
#[error(transparent)] message: Cow<'static, str>,
Signatures(#[from] ruma::signatures::Error), backtrace: snafu::Backtrace,
#[error(transparent)] },
StateRes(#[from] crate::state_res::Error),
#[error("uiaa")] #[snafu(display("Content disposition error: {source}"))]
Uiaa(ruma::api::client::uiaa::UiaaInfo), ContentDisposition {
source: ruma::http_headers::ContentDispositionParseError,
backtrace: snafu::Backtrace,
},
#[snafu(display("{message}"))]
Database {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
#[snafu(display("Feature '{feature}' is not available on this server."))]
FeatureDisabled {
feature: Cow<'static, str>,
},
#[snafu(display("Remote server {server} responded with: {error}"))]
Federation {
server: ruma::OwnedServerName,
error: ruma::api::client::error::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("{message} in {room_id}"))]
InconsistentRoomState {
message: &'static str,
room_id: ruma::OwnedRoomId,
backtrace: snafu::Backtrace,
},
#[snafu(display("HTTP conversion error: {source}"))]
IntoHttp {
source: ruma::api::error::IntoHttpError,
backtrace: snafu::Backtrace,
},
#[snafu(display("{message}"))]
Ldap {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
#[snafu(display("MXC URI error: {source}"))]
Mxc {
source: ruma::MxcUriError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Matrix ID parse error: {source}"))]
Mxid {
source: ruma::IdParseError,
backtrace: snafu::Backtrace,
},
#[snafu(display("from {server}: {error}"))]
Redaction {
server: ruma::OwnedServerName,
error: ruma::canonical_json::RedactionError,
backtrace: snafu::Backtrace,
},
#[snafu(display("{kind}: {message}"))]
Request {
kind: ruma::api::client::error::ErrorKind,
message: Cow<'static, str>,
code: http::StatusCode,
backtrace: Option<snafu::Backtrace>,
},
#[snafu(display("Ruma error: {source}"))]
Ruma {
source: ruma::api::client::error::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("Signature error: {source}"))]
Signatures {
source: ruma::signatures::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("State resolution error: {source}"))]
#[snafu(context(false))]
StateRes {
source: crate::state_res::Error,
},
#[snafu(display("uiaa"))]
Uiaa {
info: ruma::api::client::uiaa::UiaaInfo,
},
// unique / untyped // unique / untyped
#[error("{0}")] #[snafu(display("{message}"))]
Err(Cow<'static, str>), Err {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
} }
impl Error { impl Error {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn from_errno() -> Self { Self::Io(std::io::Error::last_os_error()) } pub fn from_errno() -> Self { IoSnafu {}.into_error(std::io::Error::last_os_error()) }
//#[deprecated] //#[deprecated]
#[must_use]
pub fn bad_database(message: &'static str) -> Self { pub fn bad_database(message: &'static str) -> Self {
crate::err!(Database(error!("{message}"))) let message: Cow<'static, str> = message.into();
DatabaseSnafu { message }.build()
} }
/// Sanitizes public-facing errors that can leak sensitive information. /// Sanitizes public-facing errors that can leak sensitive information.
pub fn sanitized_message(&self) -> String { pub fn sanitized_message(&self) -> String {
match self { match self {
| Self::Database(..) => String::from("Database error occurred."), | Self::Database { .. } => String::from("Database error occurred."),
| Self::Io(..) => String::from("I/O error occurred."), | Self::Io { .. } => String::from("I/O error occurred."),
| _ => self.message(), | _ => self.message(),
} }
} }
@@ -158,8 +398,8 @@ impl Error {
/// Generate the error message string. /// Generate the error message string.
pub fn message(&self) -> String { pub fn message(&self) -> String {
match self { match self {
| Self::Federation(origin, error) => format!("Answer from {origin}: {error}"), | Self::Federation { server, error, .. } => format!("Answer from {server}: {error}"),
| Self::Ruma(error) => response::ruma_error_message(error), | Self::Ruma { source, .. } => response::ruma_error_message(source),
| _ => format!("{self}"), | _ => format!("{self}"),
} }
} }
@@ -170,10 +410,10 @@ impl Error {
use ruma::api::client::error::ErrorKind::{FeatureDisabled, Unknown}; use ruma::api::client::error::ErrorKind::{FeatureDisabled, Unknown};
match self { match self {
| Self::Federation(_, error) | Self::Ruma(error) => | Self::Federation { error, .. } => response::ruma_error_kind(error).clone(),
response::ruma_error_kind(error).clone(), | Self::Ruma { source, .. } => response::ruma_error_kind(source).clone(),
| Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(), | Self::BadRequest { kind, .. } | Self::Request { kind, .. } => kind.clone(),
| Self::FeatureDisabled(..) => FeatureDisabled, | Self::FeatureDisabled { .. } => FeatureDisabled,
| _ => Unknown, | _ => Unknown,
} }
} }
@@ -184,14 +424,15 @@ impl Error {
use http::StatusCode; use http::StatusCode;
match self { match self {
| Self::Federation(_, error) | Self::Ruma(error) => error.status_code, | Self::Federation { error, .. } => error.status_code,
| Self::Request(kind, _, code) => response::status_code(kind, *code), | Self::Ruma { source, .. } => source.status_code,
| Self::BadRequest(kind, ..) => response::bad_request_code(kind), | Self::Request { kind, code, .. } => response::status_code(kind, *code),
| Self::FeatureDisabled(..) => response::bad_request_code(&self.kind()), | Self::BadRequest { kind, .. } => response::bad_request_code(kind),
| Self::Reqwest(error) => error.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR), | Self::FeatureDisabled { .. } => response::bad_request_code(&self.kind()),
| Self::Conflict(_) => StatusCode::CONFLICT, | Self::Reqwest { source, .. } =>
| Self::Io(error) => response::io_error_code(error.kind()), source.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
| Self::Uiaa(_) => StatusCode::UNAUTHORIZED, | Self::Conflict { .. } => StatusCode::CONFLICT,
| Self::Io { source, .. } => response::io_error_code(source.kind()),
| _ => StatusCode::INTERNAL_SERVER_ERROR, | _ => StatusCode::INTERNAL_SERVER_ERROR,
} }
} }
@@ -204,16 +445,46 @@ impl Error {
pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND } pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND }
} }
impl std::fmt::Debug for Error { // Debug is already derived by Snafu
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message()) /// Macro to reduce boilerplate for From implementations using Snafu context
} macro_rules! impl_from_snafu {
($source_ty:ty => $context:ident) => {
impl From<$source_ty> for Error {
fn from(source: $source_ty) -> Self { $context.into_error(source) }
}
};
}
/// Macro for From impls that format messages into ErrSnafu or other
/// message-based contexts
macro_rules! impl_from_message {
($source_ty:ty => $context:ident, $msg:expr) => {
impl From<$source_ty> for Error {
fn from(source: $source_ty) -> Self {
let message: Cow<'static, str> = format!($msg, source).into();
$context { message }.build()
}
}
};
}
/// Macro for From impls with constant messages (no formatting)
macro_rules! impl_from_const_message {
($source_ty:ty => $context:ident, $msg:expr) => {
impl From<$source_ty> for Error {
fn from(_source: $source_ty) -> Self {
let message: Cow<'static, str> = $msg.into();
$context { message }.build()
}
}
};
} }
impl<T> From<PoisonError<T>> for Error { impl<T> From<PoisonError<T>> for Error {
#[cold] #[cold]
#[inline(never)] #[inline(never)]
fn from(e: PoisonError<T>) -> Self { Self::Poison(e.to_string().into()) } fn from(e: PoisonError<T>) -> Self { PoisonSnafu { message: e.to_string() }.build() }
} }
#[allow(clippy::fallible_impl_from)] #[allow(clippy::fallible_impl_from)]
@@ -225,6 +496,43 @@ impl From<Infallible> for Error {
} }
} }
// Implementations using the macro
impl_from_snafu!(std::io::Error => IoSnafu);
impl_from_snafu!(std::string::FromUtf8Error => FromUtf8Snafu);
impl_from_snafu!(regex::Error => RegexSnafu);
impl_from_snafu!(ruma::http_headers::ContentDispositionParseError => ContentDispositionSnafu);
impl_from_snafu!(ruma::api::error::IntoHttpError => IntoHttpSnafu);
impl_from_snafu!(ruma::JsTryFromIntError => JsTryFromIntSnafu);
impl_from_snafu!(ruma::CanonicalJsonError => CanonicalJsonSnafu);
impl_from_snafu!(axum::extract::rejection::PathRejection => PathSnafu);
impl_from_snafu!(clap::error::Error => ClapSnafu);
impl_from_snafu!(ruma::MxcUriError => MxcSnafu);
impl_from_snafu!(serde_saphyr::ser_error::Error => YamlSerSnafu);
impl_from_snafu!(toml::de::Error => TomlDeSnafu);
impl_from_snafu!(http::header::InvalidHeaderValue => HttpHeaderSnafu);
impl_from_snafu!(serde_json::Error => JsonSnafu);
// Custom implementations using message formatting
impl_from_const_message!(std::fmt::Error => ErrSnafu, "formatting error");
impl_from_message!(std::str::Utf8Error => ErrSnafu, "UTF-8 error: {}");
impl_from_message!(std::num::TryFromIntError => ArithmeticSnafu, "integer conversion error: {}");
impl_from_message!(tracing_subscriber::reload::Error => ErrSnafu, "tracing reload error: {}");
impl_from_message!(reqwest::Error => ErrSnafu, "HTTP client error: {}");
impl_from_message!(ruma::signatures::Error => ErrSnafu, "Signature error: {}");
impl_from_message!(ruma::IdParseError => ErrSnafu, "ID parse error: {}");
impl_from_message!(std::num::ParseIntError => ErrSnafu, "Integer parse error: {}");
impl_from_message!(std::array::TryFromSliceError => ErrSnafu, "Slice conversion error: {}");
impl_from_message!(tokio::task::JoinError => ErrSnafu, "Task join error: {}");
impl_from_message!(serde_saphyr::Error => ErrSnafu, "YAML error: {}");
// Generic implementation for CapacityError
impl<T> From<arrayvec::CapacityError<T>> for Error {
fn from(_source: arrayvec::CapacityError<T>) -> Self {
let message: Cow<'static, str> = "capacity error: buffer is full".into();
ErrSnafu { message }.build()
}
}
#[cold] #[cold]
#[inline(never)] #[inline(never)]
pub fn infallible(_e: &Infallible) { pub fn infallible(_e: &Infallible) {
+8 -5
View File
@@ -15,13 +15,16 @@ impl Error {
#[must_use] #[must_use]
#[inline] #[inline]
pub fn from_panic(e: Box<dyn Any + Send>) -> Self { Self::Panic(debug::panic_str(&e), e) } pub fn from_panic(e: Box<dyn Any + Send>) -> Self {
use super::PanicSnafu;
PanicSnafu { message: debug::panic_str(&e), panic: e }.build()
}
#[inline] #[inline]
pub fn into_panic(self) -> Box<dyn Any + Send + 'static> { pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {
match self { match self {
| Self::Panic(_, e) | Self::PanicAny(e) => e, | Self::Panic { panic, .. } | Self::PanicAny { panic, .. } => panic,
| Self::JoinError(e) => e.into_panic(), | Self::JoinError { source, .. } => source.into_panic(),
| _ => Box::new(self), | _ => Box::new(self),
} }
} }
@@ -37,8 +40,8 @@ impl Error {
#[inline] #[inline]
pub fn is_panic(&self) -> bool { pub fn is_panic(&self) -> bool {
match &self { match &self {
| Self::Panic(..) | Self::PanicAny(..) => true, | Self::Panic { .. } | Self::PanicAny { .. } => true,
| Self::JoinError(e) => e.is_panic(), | Self::JoinError { source, .. } => source.is_panic(),
| _ => false, | _ => false,
} }
} }
+2 -2
View File
@@ -47,8 +47,8 @@ impl axum::response::IntoResponse for Error {
impl From<Error> for UiaaResponse { impl From<Error> for UiaaResponse {
#[inline] #[inline]
fn from(error: Error) -> Self { fn from(error: Error) -> Self {
if let Error::Uiaa(uiaainfo) = error { if let Error::Uiaa { info, .. } = error {
return Self::AuthResponse(uiaainfo); return Self::AuthResponse(info);
} }
let body = ErrorBody::Standard { let body = ErrorBody::Standard {
+8 -2
View File
@@ -5,9 +5,15 @@ use serde::{de, ser};
use crate::Error; use crate::Error;
impl de::Error for Error { impl de::Error for Error {
fn custom<T: Display + ToString>(msg: T) -> Self { Self::SerdeDe(msg.to_string().into()) } fn custom<T: Display + ToString>(msg: T) -> Self {
let message: std::borrow::Cow<'static, str> = msg.to_string().into();
super::SerdeDeSnafu { message }.build()
}
} }
impl ser::Error for Error { impl ser::Error for Error {
fn custom<T: Display + ToString>(msg: T) -> Self { Self::SerdeSer(msg.to_string().into()) } fn custom<T: Display + ToString>(msg: T) -> Self {
let message: std::borrow::Cow<'static, str> = msg.to_string().into();
super::SerdeSerSnafu { message }.build()
}
} }
-9
View File
@@ -14,7 +14,6 @@ static SEMANTIC: &str = env!("CARGO_PKG_VERSION");
static VERSION: OnceLock<String> = OnceLock::new(); static VERSION: OnceLock<String> = OnceLock::new();
static VERSION_UA: OnceLock<String> = OnceLock::new(); static VERSION_UA: OnceLock<String> = OnceLock::new();
static USER_AGENT: OnceLock<String> = OnceLock::new(); static USER_AGENT: OnceLock<String> = OnceLock::new();
static USER_AGENT_MEDIA: OnceLock<String> = OnceLock::new();
#[inline] #[inline]
#[must_use] #[must_use]
@@ -22,22 +21,14 @@ pub fn name() -> &'static str { BRANDING }
#[inline] #[inline]
pub fn version() -> &'static str { VERSION.get_or_init(init_version) } pub fn version() -> &'static str { VERSION.get_or_init(init_version) }
#[inline] #[inline]
pub fn version_ua() -> &'static str { VERSION_UA.get_or_init(init_version_ua) } pub fn version_ua() -> &'static str { VERSION_UA.get_or_init(init_version_ua) }
#[inline] #[inline]
pub fn user_agent() -> &'static str { USER_AGENT.get_or_init(init_user_agent) } pub fn user_agent() -> &'static str { USER_AGENT.get_or_init(init_user_agent) }
#[inline]
pub fn user_agent_media() -> &'static str { USER_AGENT_MEDIA.get_or_init(init_user_agent_media) }
fn init_user_agent() -> String { format!("{}/{} (bot; +{WEBSITE})", name(), version_ua()) } fn init_user_agent() -> String { format!("{}/{} (bot; +{WEBSITE})", name(), version_ua()) }
fn init_user_agent_media() -> String {
format!("{}/{} (embedbot; facebookexternalhit/1.1; +{WEBSITE})", name(), version_ua())
}
fn init_version_ua() -> String { fn init_version_ua() -> String {
conduwuit_build_metadata::version_tag() conduwuit_build_metadata::version_tag()
.map_or_else(|| SEMANTIC.to_owned(), |extra| format!("{SEMANTIC}+{extra}")) .map_or_else(|| SEMANTIC.to_owned(), |extra| format!("{SEMANTIC}+{extra}"))
+10 -3
View File
@@ -1,7 +1,7 @@
use ruma::{RoomVersionId, canonical_json::redact_content_in_place}; use ruma::{RoomVersionId, canonical_json::redact_content_in_place};
use serde_json::{Value as JsonValue, json, value::to_raw_value}; use serde_json::{Value as JsonValue, json, value::to_raw_value};
use crate::{Error, Result, err, implement}; use crate::{Result, err, implement};
#[implement(super::Pdu)] #[implement(super::Pdu)]
pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> Result { pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> Result {
@@ -10,8 +10,15 @@ pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) ->
let mut content = serde_json::from_str(self.content.get()) let mut content = serde_json::from_str(self.content.get())
.map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))?; .map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))?;
redact_content_in_place(&mut content, room_version_id, self.kind.to_string()) redact_content_in_place(&mut content, room_version_id, self.kind.to_string()).map_err(
.map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?; |error| {
crate::error::RedactionSnafu {
server: self.sender.server_name().to_owned(),
error,
}
.build()
},
)?;
let reason = serde_json::to_value(reason).expect("Failed to preserialize reason"); let reason = serde_json::to_value(reason).expect("Failed to preserialize reason");
+7 -5
View File
@@ -27,7 +27,7 @@ use serde_json::{
use crate::{ use crate::{
matrix::{Event, Pdu, pdu::EventHash}, matrix::{Event, Pdu, pdu::EventHash},
state_res::{self as state_res, Error, Result, StateMap}, state_res::{self as state_res, Error, Result, StateMap, error::NotFoundSnafu},
}; };
static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0); static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
@@ -170,10 +170,12 @@ struct TestStore<E: Event>(HashMap<OwnedEventId, E>);
#[allow(unused)] #[allow(unused)]
impl<E: Event + Clone> TestStore<E> { impl<E: Event + Clone> TestStore<E> {
fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result<E> { fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result<E> {
self.0 self.0.get(event_id).cloned().ok_or_else(|| {
.get(event_id) NotFoundSnafu {
.cloned() message: format!("{} not found", event_id),
.ok_or_else(|| Error::NotFound(format!("{} not found", event_id))) }
.build()
})
} }
/// Returns the events that correspond to the `event_ids` sorted in the same /// Returns the events that correspond to the `event_ids` sorted in the same
+27 -10
View File
@@ -1,23 +1,40 @@
use serde_json::Error as JsonError; use serde_json::Error as JsonError;
use thiserror::Error; use snafu::{IntoError, prelude::*};
/// Represents the various errors that arise when resolving state. /// Represents the various errors that arise when resolving state.
#[derive(Error, Debug)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// A deserialization error. /// A deserialization error.
#[error(transparent)] #[snafu(display("JSON error: {source}"))]
SerdeJson(#[from] JsonError), SerdeJson {
source: JsonError,
backtrace: snafu::Backtrace,
},
/// The given option or version is unsupported. /// The given option or version is unsupported.
#[error("Unsupported room version: {0}")] #[snafu(display("Unsupported room version: {version}"))]
Unsupported(String), Unsupported {
version: String,
backtrace: snafu::Backtrace,
},
/// The given event was not found. /// The given event was not found.
#[error("Not found error: {0}")] #[snafu(display("Not found error: {message}"))]
NotFound(String), NotFound {
message: String,
backtrace: snafu::Backtrace,
},
/// Invalid fields in the given PDU. /// Invalid fields in the given PDU.
#[error("Invalid PDU: {0}")] #[snafu(display("Invalid PDU: {message}"))]
InvalidPdu(String), InvalidPdu {
message: String,
backtrace: snafu::Backtrace,
},
}
impl From<serde_json::Error> for Error {
fn from(source: serde_json::Error) -> Self { SerdeJsonSnafu.into_error(source) }
} }
+4 -3
View File
@@ -24,6 +24,7 @@ use serde_json::{from_str as from_json_str, value::RawValue as RawJsonValue};
use super::{ use super::{
Error, Event, Result, StateEventType, StateKey, TimelineEventType, Error, Event, Result, StateEventType, StateKey, TimelineEventType,
error::InvalidPduSnafu,
power_levels::{ power_levels::{
deserialize_power_levels, deserialize_power_levels_content_fields, deserialize_power_levels, deserialize_power_levels_content_fields,
deserialize_power_levels_content_invite, deserialize_power_levels_content_redact, deserialize_power_levels_content_invite, deserialize_power_levels_content_redact,
@@ -383,8 +384,8 @@ where
return Ok(false); return Ok(false);
} }
let target_user = let target_user = <&UserId>::try_from(state_key)
<&UserId>::try_from(state_key).map_err(|e| Error::InvalidPdu(format!("{e}")))?; .map_err(|e| InvalidPduSnafu { message: format!("{e}") }.build())?;
let user_for_join_auth = content let user_for_join_auth = content
.join_authorised_via_users_server .join_authorised_via_users_server
@@ -461,7 +462,7 @@ where
?sender_membership_event_content, ?sender_membership_event_content,
"Sender membership event content missing membership field" "Sender membership event content missing membership field"
); );
return Err(Error::InvalidPdu("Missing membership field".to_owned())); return Err(InvalidPduSnafu { message: "Missing membership field" }.build());
}; };
let membership_state = membership_state.deserialize()?; let membership_state = membership_state.deserialize()?;
+41 -29
View File
@@ -29,18 +29,18 @@ use ruma::{
}; };
use serde_json::from_str as from_json_str; use serde_json::from_str as from_json_str;
pub(crate) use self::error::Error; pub(crate) use self::error::{Error, InvalidPduSnafu, NotFoundSnafu};
use self::power_levels::PowerLevelsContentFields; use self::power_levels::PowerLevelsContentFields;
pub use self::{ pub use self::{
event_auth::{auth_check, auth_types_for_event}, event_auth::{auth_check, auth_types_for_event},
room_version::RoomVersion, room_version::RoomVersion,
}; };
use super::{Event, StateKey};
use crate::{ use crate::{
debug, debug_error, err, debug, debug_error,
matrix::{Event, StateKey},
state_res::room_version::StateResolutionVersion, state_res::room_version::StateResolutionVersion,
trace, trace,
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, WidebandExt}, utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt},
warn, warn,
}; };
@@ -118,7 +118,10 @@ where
let csg = calculate_conflicted_subgraph(&conflicting, event_fetch) let csg = calculate_conflicted_subgraph(&conflicting, event_fetch)
.await .await
.ok_or_else(|| { .ok_or_else(|| {
Error::InvalidPdu("Failed to calculate conflicted subgraph".to_owned()) InvalidPduSnafu {
message: "Failed to calculate conflicted subgraph",
}
.build()
})?; })?;
debug!(count = csg.len(), "conflicted subgraph"); debug!(count = csg.len(), "conflicted subgraph");
trace!(set = ?csg, "conflicted subgraph"); trace!(set = ?csg, "conflicted subgraph");
@@ -149,10 +152,11 @@ where
let control_events: Vec<_> = all_conflicted let control_events: Vec<_> = all_conflicted
.iter() .iter()
.stream() .stream()
.wide_filter_map(async |id| { .broad_filter_map(async |id| {
is_power_event_id(id, &event_fetch) event_fetch(id.clone())
.await .await
.then_some(id.clone()) .filter(|event| is_power_event(&event))
.map(|_| id.clone())
}) })
.collect() .collect()
.await; .await;
@@ -314,7 +318,10 @@ where
trace!(event_id = event_id.as_str(), "fetching event for its auth events"); trace!(event_id = event_id.as_str(), "fetching event for its auth events");
let evt = fetch_event(event_id.clone()).await; let evt = fetch_event(event_id.clone()).await;
if evt.is_none() { if evt.is_none() {
err!("could not fetch event {} to calculate conflicted subgraph", event_id); tracing::error!(
"could not fetch event {} to calculate conflicted subgraph",
event_id
);
path.pop(); path.pop();
continue; continue;
} }
@@ -402,11 +409,11 @@ where
let fetcher = async |event_id: OwnedEventId| { let fetcher = async |event_id: OwnedEventId| {
let pl = *event_to_pl let pl = *event_to_pl
.get(&event_id) .get(&event_id)
.ok_or_else(|| Error::NotFound(String::new()))?; .ok_or_else(|| NotFoundSnafu { message: "" }.build())?;
let ev = fetch_event(event_id) let ev = fetch_event(event_id)
.await .await
.ok_or_else(|| Error::NotFound(String::new()))?; .ok_or_else(|| NotFoundSnafu { message: "" }.build())?;
Ok((pl, ev.origin_server_ts())) Ok((pl, ev.origin_server_ts()))
}; };
@@ -612,9 +619,12 @@ where
let events_to_check: Vec<_> = events_to_check let events_to_check: Vec<_> = events_to_check
.map(Result::Ok) .map(Result::Ok)
.broad_and_then(async |event_id| { .broad_and_then(async |event_id| {
fetch_event(event_id.to_owned()) fetch_event(event_id.to_owned()).await.ok_or_else(|| {
.await NotFoundSnafu {
.ok_or_else(|| Error::NotFound(format!("Failed to find {event_id}"))) message: format!("Failed to find {event_id}"),
}
.build()
})
}) })
.try_collect() .try_collect()
.boxed() .boxed()
@@ -653,7 +663,7 @@ where
trace!(event_id = event.event_id().as_str(), "checking event"); trace!(event_id = event.event_id().as_str(), "checking event");
let state_key = event let state_key = event
.state_key() .state_key()
.ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?; .ok_or_else(|| InvalidPduSnafu { message: "State event had no state key" }.build())?;
let auth_types = auth_types_for_event( let auth_types = auth_types_for_event(
event.event_type(), event.event_type(),
@@ -669,13 +679,14 @@ where
trace!("room version uses hashed IDs, manually fetching create event"); trace!("room version uses hashed IDs, manually fetching create event");
let create_event_id_raw = event.room_id_or_hash().as_str().replace('!', "$"); let create_event_id_raw = event.room_id_or_hash().as_str().replace('!', "$");
let create_event_id = EventId::parse(&create_event_id_raw).map_err(|e| { let create_event_id = EventId::parse(&create_event_id_raw).map_err(|e| {
Error::InvalidPdu(format!( InvalidPduSnafu {
"Failed to parse create event ID from room ID/hash: {e}" message: format!("Failed to parse create event ID from room ID/hash: {e}"),
)) }
.build()
})?;
let create_event = fetch_event(create_event_id.into()).await.ok_or_else(|| {
NotFoundSnafu { message: "Failed to find create event" }.build()
})?; })?;
let create_event = fetch_event(create_event_id.into())
.await
.ok_or_else(|| Error::NotFound("Failed to find create event".into()))?;
auth_state.insert(create_event.event_type().with_state_key(""), create_event); auth_state.insert(create_event.event_type().with_state_key(""), create_event);
} }
for aid in event.auth_events() { for aid in event.auth_events() {
@@ -686,7 +697,7 @@ where
auth_state.insert( auth_state.insert(
ev.event_type() ev.event_type()
.with_state_key(ev.state_key().ok_or_else(|| { .with_state_key(ev.state_key().ok_or_else(|| {
Error::InvalidPdu("State event had no state key".to_owned()) InvalidPduSnafu { message: "State event had no state key" }.build()
})?), })?),
ev.clone(), ev.clone(),
); );
@@ -801,13 +812,13 @@ where
let event = fetch_event(p.clone()) let event = fetch_event(p.clone())
.await .await
.ok_or_else(|| Error::NotFound(format!("Failed to find {p}")))?; .ok_or_else(|| NotFoundSnafu { message: format!("Failed to find {p}") }.build())?;
pl = None; pl = None;
for aid in event.auth_events() { for aid in event.auth_events() {
let ev = fetch_event(aid.to_owned()) let ev = fetch_event(aid.to_owned()).await.ok_or_else(|| {
.await NotFoundSnafu { message: format!("Failed to find {aid}") }.build()
.ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?; })?;
if is_type_and_key(&ev, &TimelineEventType::RoomPowerLevels, "") { if is_type_and_key(&ev, &TimelineEventType::RoomPowerLevels, "") {
pl = Some(aid.to_owned()); pl = Some(aid.to_owned());
@@ -869,9 +880,9 @@ where
event = None; event = None;
for aid in sort_ev.auth_events() { for aid in sort_ev.auth_events() {
let aev = fetch_event(aid.to_owned()) let aev = fetch_event(aid.to_owned()).await.ok_or_else(|| {
.await NotFoundSnafu { message: format!("Failed to find {aid}") }.build()
.ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?; })?;
if is_type_and_key(&aev, &TimelineEventType::RoomPowerLevels, "") { if is_type_and_key(&aev, &TimelineEventType::RoomPowerLevels, "") {
event = Some(aev); event = Some(aev);
@@ -915,6 +926,7 @@ async fn add_event_and_auth_chain_to_graph<E, F, Fut>(
} }
} }
#[allow(dead_code)]
async fn is_power_event_id<E, F, Fut>(event_id: &EventId, fetch: &F) -> bool async fn is_power_event_id<E, F, Fut>(event_id: &EventId, fetch: &F) -> bool
where where
F: Fn(OwnedEventId) -> Fut + Sync, F: Fn(OwnedEventId) -> Fut + Sync,
+6 -2
View File
@@ -1,6 +1,6 @@
use ruma::RoomVersionId; use ruma::RoomVersionId;
use super::{Error, Result}; use super::{Result, error::UnsupportedSnafu};
#[derive(Debug)] #[derive(Debug)]
#[allow(clippy::exhaustive_enums)] #[allow(clippy::exhaustive_enums)]
@@ -163,7 +163,11 @@ impl RoomVersion {
| RoomVersionId::V10 => Self::V10, | RoomVersionId::V10 => Self::V10,
| RoomVersionId::V11 => Self::V11, | RoomVersionId::V11 => Self::V11,
| RoomVersionId::V12 => Self::V12, | RoomVersionId::V12 => Self::V12,
| ver => return Err(Error::Unsupported(format!("found version `{ver}`"))), | ver =>
return Err(UnsupportedSnafu {
version: format!("found version `{ver}`"),
}
.build()),
}) })
} }
} }
+2 -2
View File
@@ -22,7 +22,7 @@ use serde_json::{
value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value}, value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value},
}; };
use super::auth_types_for_event; use super::{auth_types_for_event, error::NotFoundSnafu};
use crate::{ use crate::{
Result, RoomVersion, info, Result, RoomVersion, info,
matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash}, matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash},
@@ -232,7 +232,7 @@ impl<E: Event + Clone> TestStore<E> {
self.0 self.0
.get(event_id) .get(event_id)
.cloned() .cloned()
.ok_or_else(|| super::Error::NotFound(format!("{event_id} not found"))) .ok_or_else(|| NotFoundSnafu { message: format!("{event_id} not found") }.build())
.map_err(Into::into) .map_err(Into::into)
} }
+2
View File
@@ -14,9 +14,11 @@ pub mod utils;
pub use ::arrayvec; pub use ::arrayvec;
pub use ::http; pub use ::http;
pub use ::paste;
pub use ::ruma; pub use ::ruma;
pub use ::smallstr; pub use ::smallstr;
pub use ::smallvec; pub use ::smallvec;
pub use ::snafu;
pub use ::toml; pub use ::toml;
pub use ::tracing; pub use ::tracing;
pub use config::Config; pub use config::Config;
-1
View File
@@ -11,7 +11,6 @@ pub mod json;
pub mod math; pub mod math;
pub mod mutex_map; pub mod mutex_map;
pub mod rand; pub mod rand;
pub mod response;
pub mod result; pub mod result;
pub mod set; pub mod set;
pub mod stream; pub mod stream;
-51
View File
@@ -1,51 +0,0 @@
use futures::StreamExt;
use num_traits::ToPrimitive;
use crate::Err;
/// Reads the response body while enforcing a maximum size limit to prevent
/// memory exhaustion.
pub async fn limit_read(response: reqwest::Response, max_size: u64) -> crate::Result<Vec<u8>> {
if response.content_length().is_some_and(|len| len > max_size) {
return Err!(BadServerResponse("Response too large"));
}
let mut data = Vec::new();
let mut reader = response.bytes_stream();
while let Some(chunk) = reader.next().await {
let chunk = chunk?;
data.extend_from_slice(&chunk);
if data.len() > max_size.to_usize().expect("max_size must fit in usize") {
return Err!(BadServerResponse("Response too large"));
}
}
Ok(data)
}
/// Reads the response body as text while enforcing a maximum size limit to
/// prevent memory exhaustion.
pub async fn limit_read_text(
response: reqwest::Response,
max_size: u64,
) -> crate::Result<String> {
let text = String::from_utf8(limit_read(response, max_size).await?)?;
Ok(text)
}
#[allow(async_fn_in_trait)]
pub trait LimitReadExt {
async fn limit_read(self, max_size: u64) -> crate::Result<Vec<u8>>;
async fn limit_read_text(self, max_size: u64) -> crate::Result<String>;
}
impl LimitReadExt for reqwest::Response {
async fn limit_read(self, max_size: u64) -> crate::Result<Vec<u8>> {
limit_read(self, max_size).await
}
async fn limit_read_text(self, max_size: u64) -> crate::Result<String> {
limit_read_text(self, max_size).await
}
}
+9 -7
View File
@@ -3,17 +3,19 @@ use futures::{
stream::{Stream, TryStream}, stream::{Stream, TryStream},
}; };
use crate::{Error, Result};
pub trait IterStream<I: IntoIterator + Send> { pub trait IterStream<I: IntoIterator + Send> {
/// Convert an Iterator into a Stream /// Convert an Iterator into a Stream
fn stream(self) -> impl Stream<Item = <I as IntoIterator>::Item> + Send; fn stream(self) -> impl Stream<Item = <I as IntoIterator>::Item> + Send;
/// Convert an Iterator into a TryStream with a generic error type /// Convert an Iterator into a TryStream
fn try_stream<E>( fn try_stream(
self, self,
) -> impl TryStream< ) -> impl TryStream<
Ok = <I as IntoIterator>::Item, Ok = <I as IntoIterator>::Item,
Error = E, Error = Error,
Item = Result<<I as IntoIterator>::Item, E>, Item = Result<<I as IntoIterator>::Item, Error>,
> + Send; > + Send;
} }
@@ -26,12 +28,12 @@ where
fn stream(self) -> impl Stream<Item = <I as IntoIterator>::Item> + Send { stream::iter(self) } fn stream(self) -> impl Stream<Item = <I as IntoIterator>::Item> + Send { stream::iter(self) }
#[inline] #[inline]
fn try_stream<E>( fn try_stream(
self, self,
) -> impl TryStream< ) -> impl TryStream<
Ok = <I as IntoIterator>::Item, Ok = <I as IntoIterator>::Item,
Error = E, Error = Error,
Item = Result<<I as IntoIterator>::Item, E>, Item = Result<<I as IntoIterator>::Item, Error>,
> + Send { > + Send {
self.stream().map(Ok) self.stream().map(Ok)
} }
+1 -2
View File
@@ -1,10 +1,9 @@
//! Synchronous combinator extensions to futures::TryStream //! Synchronous combinator extensions to futures::TryStream
use std::result::Result;
use futures::{TryFuture, TryStream, TryStreamExt}; use futures::{TryFuture, TryStream, TryStreamExt};
use super::automatic_width; use super::automatic_width;
use crate::Result;
/// Concurrency extensions to augment futures::TryStreamExt. broad_ combinators /// Concurrency extensions to augment futures::TryStreamExt. broad_ combinators
/// produce out-of-order /// produce out-of-order
+5 -1
View File
@@ -2,6 +2,8 @@
use std::{cell::Cell, fmt::Debug, path::PathBuf, sync::LazyLock}; use std::{cell::Cell, fmt::Debug, path::PathBuf, sync::LazyLock};
use snafu::IntoError;
use crate::{Result, is_equal_to}; use crate::{Result, is_equal_to};
type Id = usize; type Id = usize;
@@ -142,7 +144,9 @@ pub fn getcpu() -> Result<usize> {
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
#[inline] #[inline]
pub fn getcpu() -> Result<usize> { Err(crate::Error::Io(std::io::ErrorKind::Unsupported.into())) } pub fn getcpu() -> Result<usize> {
Err(crate::error::IoSnafu.into_error(std::io::ErrorKind::Unsupported.into()))
}
fn query_cores_available() -> impl Iterator<Item = Id> { fn query_cores_available() -> impl Iterator<Item = Id> {
core_affinity::get_core_ids() core_affinity::get_core_ids()
+12 -7
View File
@@ -255,7 +255,10 @@ impl<'a, 'de: 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
| "$serde_json::private::RawValue" => visitor.visit_map(self), | "$serde_json::private::RawValue" => visitor.visit_map(self),
| "Cbor" => visitor | "Cbor" => visitor
.visit_newtype_struct(&mut minicbor_serde::Deserializer::new(self.record_trail())) .visit_newtype_struct(&mut minicbor_serde::Deserializer::new(self.record_trail()))
.map_err(|e| Self::Error::SerdeDe(e.to_string().into())), .map_err(|e| {
let message: std::borrow::Cow<'static, str> = e.to_string().into();
conduwuit_core::error::SerdeDeSnafu { message }.build()
}),
| _ => visitor.visit_newtype_struct(self), | _ => visitor.visit_newtype_struct(self),
} }
@@ -313,9 +316,10 @@ impl<'a, 'de: 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
let end = self.pos.saturating_add(BYTES).min(self.buf.len()); let end = self.pos.saturating_add(BYTES).min(self.buf.len());
let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?; let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?;
let bytes = bytes let bytes = bytes.into_inner().map_err(|_| {
.into_inner() let message: std::borrow::Cow<'static, str> = "i64 buffer underflow".into();
.map_err(|_| Self::Error::SerdeDe("i64 buffer underflow".into()))?; conduwuit_core::error::SerdeDeSnafu { message }.build()
})?;
self.inc_pos(BYTES); self.inc_pos(BYTES);
visitor.visit_i64(i64::from_be_bytes(bytes)) visitor.visit_i64(i64::from_be_bytes(bytes))
@@ -345,9 +349,10 @@ impl<'a, 'de: 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
let end = self.pos.saturating_add(BYTES).min(self.buf.len()); let end = self.pos.saturating_add(BYTES).min(self.buf.len());
let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?; let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?;
let bytes = bytes let bytes = bytes.into_inner().map_err(|_| {
.into_inner() let message: std::borrow::Cow<'static, str> = "u64 buffer underflow".into();
.map_err(|_| Self::Error::SerdeDe("u64 buffer underflow".into()))?; conduwuit_core::error::SerdeDeSnafu { message }.build()
})?;
self.inc_pos(BYTES); self.inc_pos(BYTES);
visitor.visit_u64(u64::from_be_bytes(bytes)) visitor.visit_u64(u64::from_be_bytes(bytes))
-4
View File
@@ -362,10 +362,6 @@ pub(super) static MAPS: &[Descriptor] = &[
name: "userid_blurhash", name: "userid_blurhash",
..descriptor::RANDOM_SMALL ..descriptor::RANDOM_SMALL
}, },
Descriptor {
name: "userid_dehydrateddevice",
..descriptor::RANDOM_SMALL
},
Descriptor { Descriptor {
name: "userid_devicelistversion", name: "userid_devicelistversion",
..descriptor::RANDOM_SMALL ..descriptor::RANDOM_SMALL
+4 -1
View File
@@ -199,7 +199,10 @@ impl<W: Write> ser::Serializer for &mut Serializer<'_, W> {
value value
.serialize(&mut Serializer::new(&mut Writer::new(&mut self.out))) .serialize(&mut Serializer::new(&mut Writer::new(&mut self.out)))
.map_err(|e| Self::Error::SerdeSer(e.to_string().into())) .map_err(|e| {
let message: std::borrow::Cow<'static, str> = e.to_string().into();
conduwuit_core::error::SerdeSerSnafu { message }.build()
})
}, },
| _ => unhandled!("Unrecognized serialization Newtype {name:?}"), | _ => unhandled!("Unrecognized serialization Newtype {name:?}"),
} }
+7 -2
View File
@@ -1,4 +1,4 @@
use std::sync::Arc; use std::{borrow::Cow, sync::Arc};
use axum::{Router, response::IntoResponse}; use axum::{Router, response::IntoResponse};
use conduwuit::Error; use conduwuit::Error;
@@ -18,5 +18,10 @@ pub(crate) fn build(services: &Arc<Services>) -> (Router, Guard) {
} }
async fn not_found(_uri: Uri) -> impl IntoResponse { async fn not_found(_uri: Uri) -> impl IntoResponse {
Error::Request(ErrorKind::Unrecognized, "Not Found".into(), StatusCode::NOT_FOUND) Error::Request {
kind: ErrorKind::Unrecognized,
message: Cow::Borrowed("Not Found"),
code: StatusCode::NOT_FOUND,
backtrace: None,
}
} }
+1 -15
View File
@@ -530,12 +530,7 @@ impl Service {
Ok(()) Ok(())
} }
pub async fn is_admin_command<E>( pub async fn is_admin_command<E>(&self, event: &E, body: &str) -> Option<InvocationSource>
&self,
event: &E,
body: &str,
sent_locally: bool,
) -> Option<InvocationSource>
where where
E: Event + Send + Sync, E: Event + Send + Sync,
{ {
@@ -585,15 +580,6 @@ impl Service {
return None; return None;
} }
// Escaped commands must be sent locally (via client API), not via federation
if !sent_locally {
conduwuit::warn!(
"Ignoring escaped admin command from {} that arrived via federation",
event.sender()
);
return None;
}
// Looks good // Looks good
Some(InvocationSource::EscapedCommand) Some(InvocationSource::EscapedCommand)
} }
+2 -2
View File
@@ -18,7 +18,7 @@
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use async_trait::async_trait; use async_trait::async_trait;
use conduwuit::{Result, Server, debug, error, utils::response::LimitReadExt, warn}; use conduwuit::{Result, Server, debug, error, warn};
use database::{Deserialized, Map}; use database::{Deserialized, Map};
use ruma::events::{Mentions, room::message::RoomMessageEventContent}; use ruma::events::{Mentions, room::message::RoomMessageEventContent};
use serde::Deserialize; use serde::Deserialize;
@@ -137,7 +137,7 @@ impl Service {
.get(CHECK_FOR_ANNOUNCEMENTS_URL) .get(CHECK_FOR_ANNOUNCEMENTS_URL)
.send() .send()
.await? .await?
.limit_read_text(1024 * 1024) .text()
.await?; .await?;
let response = serde_json::from_str::<CheckForAnnouncementsResponse>(&response)?; let response = serde_json::from_str::<CheckForAnnouncementsResponse>(&response)?;
+4 -4
View File
@@ -147,11 +147,11 @@ impl Service {
// same appservice) // same appservice)
if let Ok(existing) = self.find_from_token(&registration.as_token).await { if let Ok(existing) = self.find_from_token(&registration.as_token).await {
if existing.registration.id != registration.id { if existing.registration.id != registration.id {
return Err(err!(Request(InvalidParam( return Err!(Request(InvalidParam(
"Cannot register appservice: Token is already used by appservice '{}'. \ "Cannot register appservice: Token is already used by appservice '{}'. \
Please generate a different token.", Please generate a different token.",
existing.registration.id existing.registration.id
)))); )));
} }
} }
@@ -163,10 +163,10 @@ impl Service {
.await .await
.is_ok() .is_ok()
{ {
return Err(err!(Request(InvalidParam( return Err!(Request(InvalidParam(
"Cannot register appservice: The provided token is already in use by a user \ "Cannot register appservice: The provided token is already in use by a user \
device. Please generate a different token for the appservice." device. Please generate a different token for the appservice."
)))); )));
} }
self.db self.db
+1 -1
View File
@@ -39,7 +39,7 @@ impl crate::Service for Service {
let url_preview_user_agent = config let url_preview_user_agent = config
.url_preview_user_agent .url_preview_user_agent
.clone() .clone()
.unwrap_or_else(|| conduwuit::version::user_agent_media().to_owned()); .unwrap_or_else(|| conduwuit::version::user_agent().to_owned());
Ok(Arc::new(Self { Ok(Arc::new(Self {
default: base(config)? default: base(config)?

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