Compare commits

..

3 Commits

Author SHA1 Message Date
Jade Ellis f2e588b294 fix: Stop forcing the appservice token into the URL 2026-01-05 18:14:16 +00:00
Jade Ellis 7ec81c25f1 fix: Use correct token handlers for Ruma 2026-01-05 18:12:20 +00:00
Jade Ellis 61f61826bb fix: Apply timeouts in more places 2026-01-05 18:09:18 +00:00
115 changed files with 639 additions and 1755 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
name: Renovate
runs-on: ubuntu-latest
container:
image: ghcr.io/renovatebot/renovate:42.70.2@sha256:3c2ac1b94fa92ef2fa4d1a0493f2c3ba564454720a32fdbcac2db2846ff1ee47
image: ghcr.io/renovatebot/renovate:42.11.0@sha256:656c1e5b808279eac16c37b89562fb4c699e02fc7e219244f4a1fc2f0a7ce367
options: --tmpfs /tmp:exec
steps:
- name: Checkout
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
persist-credentials: true
token: ${{ secrets.FORGEJO_TOKEN }}
- uses: https://github.com/cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0
- uses: https://github.com/cachix/install-nix-action@7ab6e7fd29da88e74b1e314a4ae9ac6b5cda3801 # v31.8.0
with:
nix_path: nixpkgs=channel:nixos-unstable
Generated
+18 -40
View File
@@ -1632,16 +1632,6 @@ dependencies = [
"litrs",
]
[[package]]
name = "draupnir-antispam"
version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
dependencies = [
"ruma-common",
"serde",
"serde_json",
]
[[package]]
name = "dtor"
version = "0.1.0"
@@ -1760,7 +1750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -2415,7 +2405,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
"socket2 0.6.1",
"socket2 0.5.10",
"tokio",
"tower-service",
"tracing",
@@ -2992,16 +2982,6 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "meowlnir-antispam"
version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
dependencies = [
"ruma-common",
"serde",
"serde_json",
]
[[package]]
name = "mime"
version = "0.3.17"
@@ -3141,7 +3121,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -3769,7 +3749,7 @@ dependencies = [
"quinn-udp",
"rustc-hash",
"rustls",
"socket2 0.6.1",
"socket2 0.5.10",
"thiserror 2.0.17",
"tokio",
"tracing",
@@ -3806,9 +3786,9 @@ dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2 0.6.1",
"socket2 0.5.10",
"tracing",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -4085,13 +4065,11 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
[[package]]
name = "ruma"
version = "0.10.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
dependencies = [
"assign",
"draupnir-antispam",
"js_int",
"js_option",
"meowlnir-antispam",
"ruma-appservice-api",
"ruma-client-api",
"ruma-common",
@@ -4107,7 +4085,7 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.10.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
dependencies = [
"js_int",
"ruma-common",
@@ -4119,7 +4097,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.18.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
dependencies = [
"as_variant",
"assign",
@@ -4142,7 +4120,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
dependencies = [
"as_variant",
"base64 0.22.1",
@@ -4174,7 +4152,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.28.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
dependencies = [
"as_variant",
"indexmap",
@@ -4199,7 +4177,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
dependencies = [
"bytes",
"headers",
@@ -4221,7 +4199,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.9.5"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
dependencies = [
"js_int",
"thiserror 2.0.17",
@@ -4230,7 +4208,7 @@ dependencies = [
[[package]]
name = "ruma-identity-service-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
dependencies = [
"js_int",
"ruma-common",
@@ -4240,7 +4218,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
dependencies = [
"cfg-if",
"proc-macro-crate",
@@ -4255,7 +4233,7 @@ dependencies = [
[[package]]
name = "ruma-push-gateway-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
dependencies = [
"js_int",
"ruma-common",
@@ -4267,7 +4245,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.15.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
dependencies = [
"base64 0.22.1",
"ed25519-dalek",
@@ -4347,7 +4325,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
+73 -66
View File
@@ -1,17 +1,26 @@
#cargo-features = ["profile-rustflags"]
[workspace]
resolver = "2"
members = ["src/*", "xtask/*"]
default-members = ["src/*"]
[workspace.package]
authors = ["Continuwuity Team and contributors <team@continuwuity.org>"]
description = "A Matrix homeserver written in Rust, the official continuation of the conduwuit homeserver."
authors = [
"June Clementine Strawberry <june@girlboss.ceo>",
"strawberry <strawberry@puppygock.gay>", # woof
"Jason Volk <jason@zemos.net>",
]
categories = ["network-programming"]
description = "a very cool Matrix chat homeserver written in Rust"
edition = "2024"
homepage = "https://continuwuity.org/"
keywords = ["chat", "matrix", "networking", "server", "uwu"]
license = "Apache-2.0"
# See also `rust-toolchain.toml`
readme = "README.md"
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
rust-version = "1.86.0"
version = "0.5.1"
[workspace.metadata.crane]
@@ -24,11 +33,11 @@ features = ["serde"]
[workspace.dependencies.smallvec]
version = "1.14.0"
features = [
"const_generics",
"const_new",
"serde",
"union",
"write",
"const_generics",
"const_new",
"serde",
"union",
"write",
]
[workspace.dependencies.smallstr]
@@ -87,13 +96,13 @@ version = "1.11.1"
version = "0.7.9"
default-features = false
features = [
"form",
"http1",
"http2",
"json",
"matched-path",
"tokio",
"tracing",
"form",
"http1",
"http2",
"json",
"matched-path",
"tokio",
"tracing",
]
[workspace.dependencies.axum-extra]
@@ -140,10 +149,10 @@ features = ["aws_lc_rs"]
version = "0.12.15"
default-features = false
features = [
"rustls-tls-native-roots",
"socks",
"hickory-dns",
"http2",
"rustls-tls-native-roots",
"socks",
"hickory-dns",
"http2",
]
[workspace.dependencies.serde]
@@ -179,18 +188,18 @@ default-features = false
version = "0.25.5"
default-features = false
features = [
"jpeg",
"png",
"gif",
"webp",
"jpeg",
"png",
"gif",
"webp",
]
[workspace.dependencies.blurhash]
version = "0.2.3"
default-features = false
features = [
"fast-linear-to-srgb",
"image",
"fast-linear-to-srgb",
"image",
]
# logging
@@ -220,13 +229,13 @@ default-features = false
version = "4.5.35"
default-features = false
features = [
"derive",
"env",
"error-context",
"help",
"std",
"string",
"usage",
"derive",
"env",
"error-context",
"help",
"std",
"string",
"usage",
]
[workspace.dependencies.futures]
@@ -238,15 +247,15 @@ features = ["std", "async-await"]
version = "1.44.2"
default-features = false
features = [
"fs",
"net",
"macros",
"sync",
"signal",
"time",
"rt-multi-thread",
"io-util",
"tracing",
"fs",
"net",
"macros",
"sync",
"signal",
"time",
"rt-multi-thread",
"io-util",
"tracing",
]
[workspace.dependencies.tokio-metrics]
@@ -271,18 +280,18 @@ default-features = false
version = "1.6.0"
default-features = false
features = [
"server",
"http1",
"http2",
"server",
"http1",
"http2",
]
[workspace.dependencies.hyper-util]
version = "=0.1.17"
default-features = false
features = [
"server-auto",
"server-graceful",
"tokio",
"server-auto",
"server-graceful",
"tokio",
]
# to support multiple variations of setting a config option
@@ -301,9 +310,9 @@ features = ["env", "toml"]
version = "0.25.1"
default-features = false
features = [
"serde",
"system-config",
"tokio",
"serde",
"system-config",
"tokio",
]
# Used for conduwuit::Error type
@@ -342,7 +351,7 @@ version = "0.1.2"
# Used for matrix spec type definitions and helpers
[workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
rev = "79abd5d331bca596b7f37e367a9f2cebccd9f64d"
rev = "27abe0dcd33fd4056efc94bab3582646b31b6ce9"
features = [
"compat",
"rand",
@@ -372,13 +381,13 @@ features = [
"unstable-msc4095",
"unstable-msc4121",
"unstable-msc4125",
"unstable-msc4155",
"unstable-msc4155",
"unstable-msc4186",
"unstable-msc4203", # sending to-device events to appservices
"unstable-msc4210", # remove legacy mentions
"unstable-extensible-events",
"unstable-pdu",
"unstable-msc4155"
"unstable-msc4155"
]
[workspace.dependencies.rust-rocksdb]
@@ -386,11 +395,11 @@ git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1"
rev = "61d9d23872197e9ace4a477f2617d5c9f50ecb23"
default-features = false
features = [
"multi-threaded-cf",
"mt_static",
"lz4",
"zstd",
"bzip2",
"multi-threaded-cf",
"mt_static",
"lz4",
"zstd",
"bzip2",
]
[workspace.dependencies.sha2]
@@ -449,16 +458,16 @@ git = "https://forgejo.ellis.link/continuwuation/jemallocator"
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
default-features = false
features = [
"background_threads_runtime_support",
"unprefixed_malloc_on_supported_platforms",
"background_threads_runtime_support",
"unprefixed_malloc_on_supported_platforms",
]
[workspace.dependencies.tikv-jemallocator]
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
default-features = false
features = [
"background_threads_runtime_support",
"unprefixed_malloc_on_supported_platforms",
"background_threads_runtime_support",
"unprefixed_malloc_on_supported_platforms",
]
[workspace.dependencies.tikv-jemalloc-ctl]
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
@@ -482,9 +491,9 @@ default-features = false
version = "0.1.2"
default-features = false
features = [
"static",
"gcc",
"light",
"static",
"gcc",
"light",
]
[workspace.dependencies.rustyline-async]
@@ -839,8 +848,6 @@ unknown_lints = "allow"
###################
cargo = { level = "warn", priority = -1 }
# Nobody except for us should be consuming these crates, they don't need metadata
cargo_common_metadata = { level = "allow"}
## some sadness
multiple_crate_versions = { level = "allow", priority = 1 }
-2
View File
@@ -1,2 +0,0 @@
Added support for invite and join anti-spam via Draupnir and Meowlnir, similar to that of synapse-http-antispam.
Contributed by @nex.
-1
View File
@@ -1 +0,0 @@
Implemented account locking functionality, to complement user suspension. Contributed by @nex.
-1
View File
@@ -1 +0,0 @@
Added admin command to forcefully log out all of a user's existing sessions. Contributed by @nex.
-1
View File
@@ -1 +0,0 @@
Implemented toggling the ability for an account to log in without mutating any of its data. Contributed by @nex.
-1
View File
@@ -1 +0,0 @@
Added support for issuing additional registration tokens, stored in the database, which supplement the existing registration token hardcoded in the config file. These tokens may optionally expire after a certain number of uses or after a certain amount of time has passed. Additionally, the `registration_token_file` configuration option is superseded by this feature and **has been removed**.
+27 -43
View File
@@ -389,7 +389,15 @@
#
#appservice_idle_timeout = 300
# Notification gateway pusher idle connection pool timeout.
# Notification gateway pusher request connection timeout (seconds).
#
#pusher_conn_timeout = 15
# Notification gateway pusher total request timeout (seconds).
#
#pusher_timeout = 60
# Notification gateway pusher idle connection pool timeout (seconds).
#
#pusher_idle_timeout = 15
@@ -421,7 +429,7 @@
# `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
#
# If you would like registration only via token reg, please configure
# `registration_token`.
# `registration_token` or `registration_token_file`.
#
#allow_registration = false
@@ -452,13 +460,22 @@
# `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.
# YOU NEED TO EDIT THIS OR USE registration_token_file.
#
# example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
#
#registration_token =
# Path to a file on the system that gets read for additional registration
# tokens. Multiple tokens can be added if you separate them with
# whitespace
#
# continuwuity must be able to access the file, and it must not be empty
#
# example: "/etc/continuwuity/.reg_token"
#
#registration_token_file =
# The public site key for reCaptcha. If this is provided, reCaptcha
# becomes required during registration. If both captcha *and*
# registration token are enabled, both will be required during
@@ -1446,6 +1463,11 @@
#
#url_preview_max_spider_size = 256000
# Total request timeout for URL previews (seconds). This includes
# connection, request, and response body reading time.
#
#url_preview_timeout = 120
# Option to decide whether you would like to run the domain allowlist
# checks (contains and explicit) on the root domain or not. Does not apply
# to URL contains allowlist. Defaults to false.
@@ -1638,7 +1660,7 @@
# Enable the tokio-console. This option is only relevant to developers.
#
# For more information, see:
# For more information, see:
# https://continuwuity.org/development.html#debugging-with-tokio-console
#
#tokio_console = false
@@ -1914,41 +1936,3 @@
# example: "(objectClass=conduwuitAdmin)" or "(uid={username})"
#
#admin_filter = ""
[global.antispam.meowlnir]
# The base URL on which to contact Meowlnir (before /_meowlnir/antispam).
#
# Example: "http://127.0.0.1:29339"
#
#base_url =
# The authentication secret defined in antispam->secret. Required for
# continuwuity to talk to Meowlnir.
#
#secret =
# The management room for which to send requests
#
#management_room =
# If enabled run all federated join attempts (both federated and local)
# through the Meowlnir anti-spam checks.
#
# By default, only join attempts for rooms with the `fi.mau.spam_checker`
# restricted join rule are checked.
#
#check_all_joins = false
[global.antispam.draupnir]
# The base URL on which to contact Draupnir (before /api/).
#
# Example: "http://127.0.0.1:29339"
#
#base_url =
# The authentication secret defined in
# web->synapseHTTPAntispam->authorization
#
#secret =
+1 -1
View File
@@ -52,7 +52,7 @@ ENV BINSTALL_VERSION=1.16.6
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree
ENV LDDTREE_VERSION=0.4.0
ENV LDDTREE_VERSION=0.3.7
# renovate: datasource=crate depName=timelord-cli
ENV TIMELORD_VERSION=3.0.1
+1 -1
View File
@@ -22,7 +22,7 @@ ENV BINSTALL_VERSION=1.16.6
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree
ENV LDDTREE_VERSION=0.4.0
ENV LDDTREE_VERSION=0.3.7
# Install unpackaged tools
RUN <<EOF
+9 -27
View File
@@ -11,10 +11,10 @@ OCI images for Continuwuity are available in the registries listed below.
| Registry | Image | Notes |
| --------------- | --------------------------------------------------------------- | -----------------------|
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:latest](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/latest) | Latest tagged image. |
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:main](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/main) | Main branch image. |
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:latest-maxperf](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/latest-maxperf) | Performance optimised version. |
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:main-maxperf](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/main-maxperf) | Performance optimised version. |
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:latest][fj] | Latest tagged image. |
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:main][fj] | Main branch image. |
[fj]: https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity
Use
@@ -24,15 +24,6 @@ docker image pull $LINK
to pull it to your machine.
#### Mirrors
Images are mirrored to multiple locations automatically, on a schedule:
- `ghcr.io/continuwuity/continuwuity`
- `docker.io/jadedblueeyes/continuwuity`
- `registry.gitlab.com/continuwuity/continuwuity`
- `git.nexy7574.co.uk/mirrored/continuwuity` (releases only, no `main`)
### Run
When you have the image, you can simply run it with
@@ -58,7 +49,7 @@ If you just want to test Continuwuity for a short time, you can use the `--rm`
flag, which cleans up everything related to your container after you stop
it.
### Docker Compose
### Docker-compose
If the `docker run` command is not suitable for you or your setup, you can also use one
of the provided `docker-compose` files.
@@ -167,19 +158,8 @@ docker buildx build --load --tag continuwuity:latest -f docker/Dockerfile .
# Example: Build for specific platforms and push to a registry.
# docker buildx build --platform linux/amd64,linux/arm64 --tag registry.io/org/continuwuity:latest -f docker/Dockerfile . --push
# Example: Build binary optimised for the current CPU (standard release profile)
# docker buildx build --load \
# --tag continuwuity:latest \
# --build-arg TARGET_CPU=native \
# -f docker/Dockerfile .
# Example: Build maxperf variant (release-max-perf profile with LTO)
# Optimised for runtime performance and smaller binary size, but requires longer build time
# docker buildx build --load \
# --tag continuwuity:latest-maxperf \
# --build-arg TARGET_CPU=native \
# --build-arg RUST_PROFILE=release-max-perf \
# -f docker/Dockerfile .
# Example: Build binary optimized for the current CPU
# docker buildx build --load --tag continuwuity:latest --build-arg TARGET_CPU=native -f docker/Dockerfile .
```
Refer to the Docker Buildx documentation for more advanced build options.
@@ -218,3 +198,5 @@ Alternatively, you can use Continuwuity's built-in delegation file capability. S
## Voice communication
See the [TURN](../turn.md) page.
[nix-buildlayeredimage]: https://ryantm.github.io/nixpkgs/builders/images/dockertools/#ssec-pkgs-dockerTools-buildLayeredImage
+18 -28
View File
@@ -8,39 +8,29 @@
## Installing Continuwuity
### Prebuilt binary
### Static prebuilt binary
Download the binary for your architecture (x86_64 or aarch64) -
run the `uname -m` to check which you need.
You may simply download the binary that fits your machine architecture (x86_64
or aarch64). Run `uname -m` to see what you need.
Prebuilt binaries are available from:
- **Tagged releases**: [Latest release page](https://forgejo.ellis.link/continuwuation/continuwuity/releases/latest)
- **Development builds**: CI artifacts from the `main` branch
(includes Debian/Ubuntu packages)
You can download prebuilt fully static musl binaries from the latest tagged
release [here](https://forgejo.ellis.link/continuwuation/continuwuity/releases/latest) or
from the `main` CI branch workflow artifact output. These also include Debian/Ubuntu
packages.
When browsing CI artifacts, `ci-bins` contains binaries organised
by commit hash, while `releases` contains tagged versions. Sort
by last modified date to find the most recent builds.
You can download these directly using curl. The `ci-bins` are CI workflow binaries organized by commit
hash/revision, and `releases` are tagged releases. Sort by descending last
modified date to find the latest.
The binaries require jemalloc and io_uring on the host system. Currently
we can't cross-build static binaries - contributions are welcome here.
These binaries have jemalloc and io_uring statically linked and included with
them, so no additional dynamic dependencies need to be installed.
#### Performance-optimised builds
For x86_64 systems with CPUs from the last ~15 years, use the
`-haswell-` optimised binaries for best performance. These
binaries enable hardware-accelerated CRC32 checksumming in
RocksDB, which significantly improves database performance.
The haswell instruction set provides an excellent balance of
compatibility and speed.
If you're using Docker instead, equivalent performance-optimised
images are available with the `-maxperf` suffix (e.g.
`forgejo.ellis.link/continuwuation/continuwuity:latest-maxperf`).
These images use the `release-max-perf`
build profile with
[link-time optimisation (LTO)](https://doc.rust-lang.org/cargo/reference/profiles.html#lto)
and, for amd64, target the haswell CPU architecture.
For the **best** performance: if you are using an `x86_64` CPU made in the last ~15 years,
we recommend using the `-haswell-` optimized binaries. These set
`-march=haswell`, which provides the most compatible and highest performance with
optimized binaries. The database backend, RocksDB, benefits most from this as it
uses hardware-accelerated CRC32 hashing/checksumming, which is critical
for performance.
### Compiling
+2 -2
View File
@@ -128,7 +128,7 @@ Keep in mind the frequency that the log will be reached, and the relevancy to a
```rs
// Good
error!(
error = ?err,
error = %err,
room_id = %room_id,
"Failed to send event to room"
);
@@ -264,7 +264,7 @@ pub async fn send_federation_request(
warn!(
destination = %destination,
attempt = attempt,
error = ?err,
error = %err,
retry_delay_ms = retry_delay.as_millis(),
"Federation request failed, retrying"
);
+1 -1
View File
@@ -8,7 +8,7 @@ This document contains the help content for the `continuwuity` command-line prog
## `continuwuity`
A Matrix homeserver written in Rust, the official continuation of the conduwuit homeserver.
a very cool Matrix chat homeserver written in Rust
**Usage:** `continuwuity [OPTIONS]`
+2 -2
View File
@@ -4,7 +4,7 @@
Name: continuwuity
Version: {{{ git_repo_version }}}
Release: 1%{?dist}
Summary: A Matrix homeserver written in Rust.
Summary: Very cool Matrix chat homeserver written in Rust
License: Apache-2.0 AND MIT
@@ -23,7 +23,7 @@ Requires: glibc
Requires: libstdc++
%global _description %{expand:
A Matrix homeserver written in Rust, the official continuation of the conduwuit homeserver.}
A cool hard fork of Conduit, a Matrix homeserver written in Rust}
%description %{_description}
+2
View File
@@ -1,7 +1,9 @@
[package]
name = "conduwuit_admin"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
+5 -21
View File
@@ -2,17 +2,10 @@ use clap::Parser;
use conduwuit::Result;
use crate::{
appservice::{self, AppserviceCommand},
check::{self, CheckCommand},
context::Context,
debug::{self, DebugCommand},
federation::{self, FederationCommand},
media::{self, MediaCommand},
query::{self, QueryCommand},
room::{self, RoomCommand},
server::{self, ServerCommand},
token::{self, TokenCommand},
user::{self, UserCommand},
appservice, appservice::AppserviceCommand, check, check::CheckCommand, context::Context,
debug, debug::DebugCommand, federation, federation::FederationCommand, media,
media::MediaCommand, query, query::QueryCommand, room, room::RoomCommand, server,
server::ServerCommand, user, user::UserCommand,
};
#[derive(Debug, Parser)]
@@ -26,10 +19,6 @@ pub enum AdminCommand {
/// - Commands for managing local users
Users(UserCommand),
#[command(subcommand)]
/// - Commands for managing registration tokens
Token(TokenCommand),
#[command(subcommand)]
/// - Commands for managing rooms
Rooms(RoomCommand),
@@ -59,7 +48,7 @@ pub enum AdminCommand {
Query(QueryCommand),
}
#[tracing::instrument(skip_all, name = "command", level = "info")]
#[tracing::instrument(skip_all, name = "command")]
pub(super) async fn process(command: AdminCommand, context: &Context<'_>) -> Result {
use AdminCommand::*;
@@ -75,11 +64,6 @@ pub(super) async fn process(command: AdminCommand, context: &Context<'_>) -> Res
context.bail_restricted()?;
user::process(command, context).await
},
| Token(command) => {
// token commands are all restricted
context.bail_restricted()?;
token::process(command, context).await
},
| Rooms(command) => room::process(command, context).await,
| Federation(command) => federation::process(command, context).await,
| Server(command) => server::process(command, context).await,
+3 -3
View File
@@ -456,7 +456,7 @@ pub(super) async fn verify_pdu(&self, event_id: OwnedEventId) -> Result {
}
#[admin_command]
#[tracing::instrument(skip(self), level = "info")]
#[tracing::instrument(skip(self))]
pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
self.bail_restricted()?;
@@ -483,7 +483,7 @@ pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
}
#[admin_command]
#[tracing::instrument(skip(self), level = "info")]
#[tracing::instrument(skip(self))]
pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
self.bail_restricted()?;
@@ -510,7 +510,7 @@ pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
}
#[admin_command]
#[tracing::instrument(skip(self), level = "info")]
#[tracing::instrument(skip(self))]
pub(super) async fn force_set_room_state_from_server(
&self,
room_id: OwnedRoomId,
-1
View File
@@ -17,7 +17,6 @@ pub(crate) mod media;
pub(crate) mod query;
pub(crate) mod room;
pub(crate) mod server;
pub(crate) mod token;
pub(crate) mod user;
extern crate conduwuit_api as api;
+1 -1
View File
@@ -37,7 +37,7 @@ pub(super) fn dispatch(services: Arc<Services>, command: CommandInput) -> Proces
Box::pin(handle_command(services, command))
}
#[tracing::instrument(skip_all, name = "admin", level = "info")]
#[tracing::instrument(skip_all, name = "admin")]
async fn handle_command(services: Arc<Services>, command: CommandInput) -> ProcessorResult {
AssertUnwindSafe(Box::pin(process_command(services, &command)))
.catch_unwind()
+3 -3
View File
@@ -98,7 +98,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
{
| Ok((room_id, servers)) => {
debug!(
%room_id,
?room_id,
?servers,
"Got federation response fetching room ID for room {room}"
);
@@ -240,7 +240,7 @@ async fn ban_list_of_rooms(&self) -> Result {
{
| Ok((room_id, servers)) => {
debug!(
%room_id,
?room_id,
?servers,
"Got federation response fetching room ID for \
{room}",
@@ -397,7 +397,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
{
| Ok((room_id, servers)) => {
debug!(
%room_id,
?room_id,
?servers,
"Got federation response fetching room ID for room {room}"
);
-76
View File
@@ -1,76 +0,0 @@
use conduwuit::{Err, Result, utils};
use conduwuit_macros::admin_command;
use futures::StreamExt;
use service::registration_tokens::TokenExpires;
#[admin_command]
pub(super) async fn issue_token(&self, expires: super::TokenExpires) -> Result {
let expires = {
if expires.immortal {
None
} else if let Some(max_uses) = expires.max_uses {
Some(TokenExpires::AfterUses(max_uses))
} else if expires.once {
Some(TokenExpires::AfterUses(1))
} else if let Some(max_age) = expires
.max_age
.as_deref()
.map(|max_age| utils::time::timepoint_from_now(utils::time::parse_duration(max_age)?))
.transpose()?
{
Some(TokenExpires::AfterTime(max_age))
} else {
unreachable!();
}
};
let (token, info) = self
.services
.registration_tokens
.issue_token(self.sender_or_service_user().into(), expires);
self.write_str(&format!(
"New registration token issued: `{token}`. {}.",
if let Some(expires) = info.expires {
format!("{expires}")
} else {
"Never expires".to_owned()
}
))
.await
}
#[admin_command]
pub(super) async fn revoke_token(&self, token: String) -> Result {
let Some(token) = self
.services
.registration_tokens
.validate_token(token)
.await
else {
return Err!("This token does not exist or has already expired.");
};
self.services.registration_tokens.revoke_token(token)?;
self.write_str("Token revoked successfully.").await
}
#[admin_command]
pub(super) async fn list_tokens(&self) -> Result {
let tokens: Vec<_> = self
.services
.registration_tokens
.iterate_tokens()
.collect()
.await;
self.write_str(&format!("Found {} registration tokens:\n", tokens.len()))
.await?;
for token in tokens {
self.write_str(&format!("- {token}\n")).await?;
}
Ok(())
}
-51
View File
@@ -1,51 +0,0 @@
mod commands;
use clap::{Args, Subcommand};
use conduwuit::Result;
use crate::admin_command_dispatch;
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
pub enum TokenCommand {
/// - Issue a new registration token
#[clap(name = "issue")]
IssueToken {
/// When this token will expire.
#[command(flatten)]
expires: TokenExpires,
},
/// - Revoke a registration token
#[clap(name = "revoke")]
RevokeToken {
/// The token to revoke.
token: String,
},
/// - List all registration tokens
#[clap(name = "list")]
ListTokens,
}
#[derive(Debug, Args)]
#[group(required = true, multiple = false)]
pub struct TokenExpires {
/// The maximum number of times this token is allowed to be used before it
/// expires.
#[arg(long)]
max_uses: Option<u64>,
/// The maximum age of this token (e.g. 30s, 5m, 7d). It will expire after
/// this much time has passed.
#[arg(long)]
max_age: Option<String>,
/// This token will never expire.
#[arg(long)]
immortal: bool,
/// A shortcut for `--max-uses 1`.
#[arg(long)]
once: bool,
}
+2 -130
View File
@@ -238,7 +238,6 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
#[admin_command]
pub(super) async fn suspend(&self, user_id: String) -> Result {
self.bail_restricted()?;
let user_id = parse_local_user_id(self.services, &user_id)?;
if user_id == self.services.globals.server_user {
@@ -263,7 +262,6 @@ pub(super) async fn suspend(&self, user_id: String) -> Result {
#[admin_command]
pub(super) async fn unsuspend(&self, user_id: String) -> Result {
self.bail_restricted()?;
let user_id = parse_local_user_id(self.services, &user_id)?;
if user_id == self.services.globals.server_user {
@@ -280,12 +278,7 @@ pub(super) async fn unsuspend(&self, user_id: String) -> Result {
}
#[admin_command]
pub(super) async fn reset_password(
&self,
logout: bool,
username: String,
password: Option<String>,
) -> Result {
pub(super) async fn reset_password(&self, username: String, password: Option<String>) -> Result {
let user_id = parse_local_user_id(self.services, &username)?;
if user_id == self.services.globals.server_user {
@@ -308,18 +301,7 @@ pub(super) async fn reset_password(
write!(self, "Successfully reset the password for user {user_id}: `{new_password}`")
},
}
.await?;
if logout {
self.services
.users
.all_device_ids(&user_id)
.for_each(|device_id| self.services.users.remove_device(&user_id, device_id))
.await;
write!(self, "\nAll existing sessions have been logged out.").await?;
}
Ok(())
.await
}
#[admin_command]
@@ -992,113 +974,3 @@ pub(super) async fn force_leave_remote_room(
self.write_str(&format!("{user_id} successfully left {room_id} via remote server."))
.await
}
#[admin_command]
pub(super) async fn lock(&self, user_id: String) -> Result {
self.bail_restricted()?;
let user_id = parse_local_user_id(self.services, &user_id)?;
assert!(
self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user"
);
if user_id == self.services.globals.server_user {
return Err!("Not allowed to lock the server service account.",);
}
if !self.services.users.exists(&user_id).await {
return Err!("User {user_id} does not exist.");
}
if self.services.users.is_admin(&user_id).await {
return Err!("Admin users cannot be locked.");
}
self.services
.users
.lock_account(&user_id, self.sender_or_service_user())
.await;
self.write_str(&format!("User {user_id} has been locked."))
.await
}
#[admin_command]
pub(super) async fn unlock(&self, user_id: String) -> Result {
self.bail_restricted()?;
let user_id = parse_local_user_id(self.services, &user_id)?;
assert!(
self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user"
);
self.services.users.unlock_account(&user_id).await;
self.write_str(&format!("User {user_id} has been unlocked."))
.await
}
#[admin_command]
pub(super) async fn logout(&self, user_id: String) -> Result {
self.bail_restricted()?;
let user_id = parse_local_user_id(self.services, &user_id)?;
assert!(
self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user"
);
if user_id == self.services.globals.server_user {
return Err!("Not allowed to log out the server service account.",);
}
if !self.services.users.exists(&user_id).await {
return Err!("User {user_id} does not exist.");
}
if self.services.users.is_admin(&user_id).await {
return Err!("You cannot forcefully log out admin users.");
}
self.services
.users
.all_device_ids(&user_id)
.for_each(|device_id| self.services.users.remove_device(&user_id, device_id))
.await;
self.write_str(&format!("User {user_id} has been logged out from all devices."))
.await
}
#[admin_command]
pub(super) async fn disable_login(&self, user_id: String) -> Result {
self.bail_restricted()?;
let user_id = parse_local_user_id(self.services, &user_id)?;
assert!(
self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user"
);
if user_id == self.services.globals.server_user {
return Err!("Not allowed to disable login for the server service account.",);
}
if !self.services.users.exists(&user_id).await {
return Err!("User {user_id} does not exist.");
}
if self.services.users.is_admin(&user_id).await {
return Err!("Admin users cannot have their login disallowed.");
}
self.services.users.disable_login(&user_id);
self.write_str(&format!(
"{user_id} can no longer log in. Their existing sessions remain unaffected."
))
.await
}
#[admin_command]
pub(super) async fn enable_login(&self, user_id: String) -> Result {
self.bail_restricted()?;
let user_id = parse_local_user_id(self.services, &user_id)?;
assert!(
self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user"
);
if !self.services.users.exists(&user_id).await {
return Err!("User {user_id} does not exist.");
}
self.services.users.enable_login(&user_id);
self.write_str(&format!("{user_id} can now log in.")).await
}
-51
View File
@@ -20,9 +20,6 @@ pub enum UserCommand {
/// - Reset user password
ResetPassword {
/// Log out existing sessions
#[arg(short, long)]
logout: bool,
/// Username of the user for whom the password should be reset
username: String,
/// New password for the user, if unspecified one is generated
@@ -62,18 +59,6 @@ pub enum UserCommand {
force: bool,
},
/// - Forcefully log a user out of all of their devices.
///
/// This will invalidate all access tokens for the specified user,
/// effectively logging them out from all sessions.
/// Note that this is destructive and may result in data loss for the user,
/// such as encryption keys. Use with caution. Can only be used in the admin
/// room.
Logout {
/// Username of the user to log out
user_id: String,
},
/// - Suspend a user
///
/// Suspended users are able to log in, sync, and read messages, but are not
@@ -96,42 +81,6 @@ pub enum UserCommand {
user_id: String,
},
/// - Lock a user
///
/// Locked users are unable to use their accounts beyond logging out. This
/// is akin to a temporary deactivation that does not change the user's
/// password. This can be used to quickly prevent a user from accessing
/// their account.
Lock {
/// Username of the user to lock
user_id: String,
},
/// - Unlock a user
///
/// Reverses the effects of the `lock` command, allowing the user to use
/// their account again.
Unlock {
/// Username of the user to unlock
user_id: String,
},
/// - Enable login for a user
EnableLogin {
/// Username of the user to enable login for
user_id: String,
},
/// - Disable login for a user
///
/// Disables login for the specified user without deactivating or locking
/// their account. This prevents the user from obtaining new access tokens,
/// but does not invalidate existing sessions.
DisableLogin {
/// Username of the user to disable login for
user_id: String,
},
/// - List local users in the database
#[clap(alias = "list")]
ListUsers,
+2
View File
@@ -1,7 +1,9 @@
[package]
name = "conduwuit_api"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
+17 -43
View File
@@ -49,7 +49,7 @@ const RANDOM_USER_ID_LENGTH: usize = 10;
///
/// Note: This will not reserve the username, so the username might become
/// invalid when trying to register
#[tracing::instrument(skip_all, fields(%client), name = "register_available", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "register_available")]
pub(crate) async fn get_register_available_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -138,7 +138,7 @@ pub(crate) async fn get_register_available_route(
/// - If `inhibit_login` is false: Creates a device and returns device id and
/// access_token
#[allow(clippy::doc_markdown)]
#[tracing::instrument(skip_all, fields(%client), name = "register", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "register")]
pub(crate) async fn register_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -179,18 +179,13 @@ pub(crate) async fn register_route(
},
}
return Err!(Request(Forbidden(
"This server is not accepting registrations at this time."
)));
return Err!(Request(Forbidden("Registration has been disabled.")));
}
if is_guest
&& (!services.config.allow_guest_registration
|| (services.config.allow_registration
&& services
.registration_tokens
.get_config_file_token()
.is_some()))
&& services.globals.registration_token.is_some()))
{
info!(
"Guest registration disabled / registration enabled with token configured, \
@@ -208,9 +203,7 @@ pub(crate) async fn register_route(
rejecting registration. Guest's initial device name: \"{}\"",
body.initial_device_display_name.as_deref().unwrap_or("")
);
return Err!(Request(Forbidden(
"This server is not accepting registrations at this time."
)));
return Err!(Request(Forbidden("Registration is temporarily disabled.")));
}
let user_id = match (body.username.as_ref(), is_guest) {
@@ -308,13 +301,7 @@ pub(crate) async fn register_route(
let skip_auth = body.appservice_info.is_some() || is_guest;
// Populate required UIAA flows
if services
.registration_tokens
.iterate_tokens()
.next()
.await
.is_some()
{
if services.globals.registration_token.is_some() {
// Registration token required
uiaainfo.flows.push(AuthFlow {
stages: vec![AuthType::RegistrationToken],
@@ -336,19 +323,7 @@ pub(crate) async fn register_route(
}
if uiaainfo.flows.is_empty() && !skip_auth {
// Registration isn't _disabled_, but there's no captcha configured and no
// registration tokens currently set. Bail out by default unless open
// registration was explicitly enabled.
if !services
.config
.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
{
return Err!(Request(Forbidden(
"This server is not accepting registrations at this time."
)));
}
// We have open registration enabled (😧), provide a dummy stage
// No registration token necessary, but clients must still go through the flow
uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
completed: Vec::new(),
@@ -628,7 +603,7 @@ pub(crate) async fn register_route(
/// last seen ts)
/// - Forgets to-device events
/// - Triggers device list updates
#[tracing::instrument(skip_all, fields(%client), name = "change_password", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "change_password")]
pub(crate) async fn change_password_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -752,7 +727,7 @@ pub(crate) async fn whoami_route(
/// - Forgets all to-device events
/// - Triggers device list updates
/// - Removes ability to log in again
#[tracing::instrument(skip_all, fields(%client), name = "deactivate", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "deactivate")]
pub(crate) async fn deactivate_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -871,20 +846,19 @@ pub(crate) async fn request_3pid_management_token_via_msisdn_route(
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
///
/// Checks if the provided registration token is valid at the time of checking.
/// Checks if the provided registration token is valid at the time of checking
///
/// Currently does not have any ratelimiting, and this isn't very practical as
/// there is only one registration token allowed.
pub(crate) async fn check_registration_token_validity(
State(services): State<crate::State>,
body: Ruma<check_registration_token_validity::v1::Request>,
) -> Result<check_registration_token_validity::v1::Response> {
// TODO: ratelimit this pretty heavily
let Some(reg_token) = services.globals.registration_token.clone() else {
return Err!(Request(Forbidden("Server does not allow token registration")));
};
let valid = services
.registration_tokens
.validate_token(body.token.clone())
.await
.is_some();
Ok(check_registration_token_validity::v1::Response { valid })
Ok(check_registration_token_validity::v1::Response { valid: reg_token == body.token })
}
/// Runs through all the deactivation steps:
+1 -1
View File
@@ -102,7 +102,7 @@ pub(crate) async fn get_alias_route(
};
let servers = room_available_servers(&services, &room_id, &room_alias, servers).await;
debug!(%room_alias, %room_id, "available servers: {servers:?}");
debug!(?room_alias, ?room_id, "available servers: {servers:?}");
Ok(get_alias::v3::Response::new(room_id, servers))
}
+1 -1
View File
@@ -74,7 +74,7 @@ pub(crate) async fn get_context_route(
}
if !visible {
debug_warn!(req_evt = %event_id, ?base_id, %room_id, "Event requested by {sender_user} but is not allowed to see it, returning 404");
debug_warn!(req_evt = ?event_id, ?base_id, ?room_id, "Event requested by {sender_user} but is not allowed to see it, returning 404");
return Err!(Request(NotFound("Event not found.")));
}
+1 -1
View File
@@ -49,7 +49,7 @@ pub(crate) async fn get_device_route(
/// # `PUT /_matrix/client/r0/devices/{deviceId}`
///
/// Updates the metadata on a given device of the sender user.
#[tracing::instrument(skip_all, fields(%client), name = "update_device", level = "debug")]
#[tracing::instrument(skip_all, fields(%client), name = "update_device")]
pub(crate) async fn update_device_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
+3 -3
View File
@@ -44,7 +44,7 @@ use crate::Ruma;
/// Lists the public rooms on this server.
///
/// - Rooms are ordered by the number of joined members
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
pub(crate) async fn get_public_rooms_filtered_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -80,7 +80,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
/// Lists the public rooms on this server.
///
/// - Rooms are ordered by the number of joined members
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
pub(crate) async fn get_public_rooms_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -116,7 +116,7 @@ pub(crate) async fn get_public_rooms_route(
/// # `PUT /_matrix/client/r0/directory/list/room/{roomId}`
///
/// Sets the visibility of a given room in the room directory.
#[tracing::instrument(skip_all, fields(%client), name = "room_directory", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "room_directory")]
pub(crate) async fn set_room_visibility_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
+4 -4
View File
@@ -52,7 +52,7 @@ pub(crate) async fn upload_keys_route(
.deserialize()
.inspect_err(|e| {
debug_warn!(
%key_id,
?key_id,
?one_time_key,
"Invalid one time key JSON submitted by client, skipping: {e}"
);
@@ -94,8 +94,8 @@ pub(crate) async fn upload_keys_route(
{
if existing_keys.json().get() == device_keys.json().get() {
debug!(
%sender_user,
%sender_device,
?sender_user,
?sender_device,
?device_keys,
"Ignoring user uploaded keys as they are an exact copy already in the \
database"
@@ -338,7 +338,7 @@ pub(crate) async fn upload_signatures_route(
for (user_id, keys) in &body.signed_keys {
for (key_id, key) in keys {
let Ok(key) = serde_json::to_value(key)
.inspect_err(|e| debug_warn!(%key_id, "Invalid \"key\" JSON: {e}"))
.inspect_err(|e| debug_warn!(?key_id, "Invalid \"key\" JSON: {e}"))
else {
continue;
};
+1 -14
View File
@@ -3,7 +3,6 @@ use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Result, debug_error, err, info,
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
warn,
};
use futures::FutureExt;
use ruma::{
@@ -22,7 +21,7 @@ use crate::Ruma;
/// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
///
/// Tries to send an invite event into the room.
#[tracing::instrument(skip_all, fields(%client), name = "invite", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "invite")]
pub(crate) async fn invite_user_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -125,18 +124,6 @@ pub(crate) async fn invite_helper(
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
}
if let Err(e) = services
.antispam
.user_may_invite(sender_user.to_owned(), recipient_user.to_owned(), room_id.to_owned())
.await
{
warn!(
"Invite from {} to {} in room {} blocked by antispam: {e:?}",
sender_user, recipient_user, room_id
);
return Err!(Request(Forbidden("Invite blocked by antispam service.")));
}
if !services.globals.user_is_local(recipient_user) {
let (pdu, pdu_json, invite_room_state) = {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
+54 -89
View File
@@ -33,7 +33,7 @@ use ruma::{
events::{
StateEventType,
room::{
join_rules::{AllowRule, JoinRule},
join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
},
},
@@ -59,7 +59,7 @@ use crate::Ruma;
/// rules locally
/// - If the server does not know about the room: asks other servers over
/// federation
#[tracing::instrument(skip_all, fields(%client), name = "join", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "join")]
pub(crate) async fn join_room_by_id_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -131,7 +131,7 @@ pub(crate) async fn join_room_by_id_route(
/// - If the server does not know about the room: use the server name query
/// param if specified. if not specified, asks other servers over federation
/// via room alias server name and room ID server name
#[tracing::instrument(skip_all, fields(%client), name = "join", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "join")]
pub(crate) async fn join_room_by_id_or_alias_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -288,23 +288,6 @@ pub async fn join_room_by_id_helper(
return Ok(join_room_by_id::v3::Response { room_id: room_id.into() });
}
if let Err(e) = services
.antispam
.user_may_join_room(
sender_user.to_owned(),
room_id.to_owned(),
services
.rooms
.state_cache
.is_invited(sender_user, room_id)
.await,
)
.await
{
warn!("Antispam prevented user {} from joining room {}: {}", sender_user, room_id, e);
return Err!(Request(Forbidden("You are not allowed to join this room.")));
}
let server_in_room = services
.rooms
.state_cache
@@ -338,17 +321,6 @@ pub async fn join_room_by_id_helper(
)));
}
if services.antispam.check_all_joins() {
if let Err(e) = services
.antispam
.meowlnir_accept_make_join(room_id.to_owned(), sender_user.to_owned())
.await
{
warn!("Antispam prevented user {} from joining room {}: {}", sender_user, room_id, e);
return Err!(Request(Forbidden("Antispam rejected join request.")));
}
}
if server_in_room {
join_room_by_id_helper_local(
services,
@@ -375,10 +347,11 @@ pub async fn join_room_by_id_helper(
.boxed()
.await?;
}
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote", level = "info")]
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote")]
async fn join_room_by_id_helper_remote(
services: &Services,
sender_user: &UserId,
@@ -736,7 +709,7 @@ async fn join_room_by_id_helper_remote(
Ok(())
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_local", level = "info")]
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_local")]
async fn join_room_by_id_helper_local(
services: &Services,
sender_user: &UserId,
@@ -747,51 +720,45 @@ async fn join_room_by_id_helper_local(
state_lock: RoomMutexGuard,
) -> Result {
debug_info!("We can join locally");
let join_rules = services.rooms.state_accessor.get_join_rules(room_id).await;
let mut restricted_join_authorized = None;
match join_rules {
| JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted) => {
for restriction in restricted.allow {
match restriction {
| AllowRule::RoomMembership(membership) => {
if services
.rooms
.state_cache
.is_joined(sender_user, &membership.room_id)
.await
{
restricted_join_authorized = Some(true);
break;
}
},
| AllowRule::UnstableSpamChecker => {
match services
.antispam
.meowlnir_accept_make_join(room_id.to_owned(), sender_user.to_owned())
.await
{
| Ok(()) => {
restricted_join_authorized = Some(true);
break;
},
| Err(_) =>
return Err!(Request(Forbidden(
"Antispam rejected join request."
))),
}
},
| _ => {},
}
}
},
| _ => {},
}
let join_authorized_via_users_server = if restricted_join_authorized.is_none() {
None
} else {
match restricted_join_authorized.unwrap() {
| true => services
let join_rules_event_content = services
.rooms
.state_accessor
.room_state_get_content::<RoomJoinRulesEventContent>(
room_id,
&StateEventType::RoomJoinRules,
"",
)
.await;
let restriction_rooms = match join_rules_event_content {
| Ok(RoomJoinRulesEventContent {
join_rule: JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted),
}) => restricted
.allow
.into_iter()
.filter_map(|a| match a {
| AllowRule::RoomMembership(r) => Some(r.room_id),
| _ => None,
})
.collect(),
| _ => Vec::new(),
};
let join_authorized_via_users_server: Option<OwnedUserId> = {
if restriction_rooms
.iter()
.stream()
.any(|restriction_room_id| {
trace!("Checking if {sender_user} is joined to {restriction_room_id}");
services
.rooms
.state_cache
.is_joined(sender_user, restriction_room_id)
})
.await
{
services
.rooms
.state_cache
.local_users_in_room(room_id)
@@ -807,14 +774,10 @@ async fn join_room_by_id_helper_local(
.boxed()
.next()
.await
.map(ToOwned::to_owned),
| false => {
warn!(
"Join authorization failed for restricted join in room {room_id} for user \
{sender_user}"
);
return Err!(Request(Forbidden("You are not authorized to join this room.")));
},
.map(ToOwned::to_owned)
} else {
trace!("No restriction rooms are joined by {sender_user}");
None
}
};
@@ -842,14 +805,16 @@ async fn join_room_by_id_helper_local(
return Ok(());
};
if servers.is_empty() || servers.len() == 1 && services.globals.server_is_ours(&servers[0]) {
if restriction_rooms.is_empty()
&& (servers.is_empty()
|| servers.len() == 1 && services.globals.server_is_ours(&servers[0]))
{
return Err(error);
}
warn!(
?error,
servers = %servers.len(),
"Could not join restricted room locally, attempting remote join",
"We couldn't do the join locally, maybe federation can help to satisfy the restricted \
join requirements"
);
let Ok((make_join_response, remote_server)) =
make_join_request(services, sender_user, room_id, servers).await
+1 -1
View File
@@ -44,7 +44,7 @@ use crate::Ruma;
/// # `POST /_matrix/client/*/knock/{roomIdOrAlias}`
///
/// Tries to knock the room to ask permission to join for the sender user.
#[tracing::instrument(skip_all, fields(%client), name = "knock", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "knock")]
pub(crate) async fn knock_room_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
+1 -14
View File
@@ -178,20 +178,7 @@ pub async fn leave_room(
.rooms
.state_cache
.left_state(user_id, room_id)
.await
.inspect_err(|err| {
// `left_state` may return an Err if the user _is_ in the room they're
// trying to leave, but the membership cache is incorrect and
// they're cached as being joined. In this situation
// we save a `None` to the `roomuserid_leftcount` table, which generates
// and sends a dummy leave to the client.
warn!(
?err,
"Trying to leave room not cached as leave, sending dummy leave \
event to client"
);
})
.unwrap_or_default()
.await?
},
}
};
+1 -1
View File
@@ -63,7 +63,7 @@ pub(crate) async fn joined_rooms_route(
///
/// Performs automatic deactivation if `auto_deactivate_banned_room_attempts` is
/// enabled
#[tracing::instrument(skip(services), level = "info")]
#[tracing::instrument(skip(services))]
pub(crate) async fn banned_room_check(
services: &Services,
user_id: &UserId,
+1 -1
View File
@@ -115,7 +115,7 @@ async fn paginate_relations_with_filter(
.user_can_see_event(sender_user, room_id, target)
.await
{
debug_warn!(req_evt = %target, %room_id, "Event relations requested by {sender_user} but is not allowed to see it, returning 404");
debug_warn!(req_evt = ?target, ?room_id, "Event relations requested by {sender_user} but is not allowed to see it, returning 404");
return Err!(Request(NotFound("Event not found.")));
}
+3 -3
View File
@@ -29,7 +29,7 @@ struct Report {
/// # `POST /_matrix/client/v3/rooms/{roomId}/report`
///
/// Reports an abusive room to homeserver admins
#[tracing::instrument(skip_all, fields(%client), name = "report_room", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "report_room")]
pub(crate) async fn report_room_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -85,7 +85,7 @@ pub(crate) async fn report_room_route(
/// # `POST /_matrix/client/v3/rooms/{roomId}/report/{eventId}`
///
/// Reports an inappropriate event to homeserver admins
#[tracing::instrument(skip_all, fields(%client), name = "report_event", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "report_event")]
pub(crate) async fn report_event_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -133,7 +133,7 @@ pub(crate) async fn report_event_route(
Ok(report_content::v3::Response {})
}
#[tracing::instrument(skip_all, fields(%client), name = "report_user", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "report_user")]
pub(crate) async fn report_user_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
+4 -4
View File
@@ -492,7 +492,7 @@ pub(crate) async fn create_room_route(
.boxed()
.await
{
warn!(?e, "Failed to send invite");
warn!(%e, "Failed to send invite");
}
}
@@ -627,7 +627,7 @@ async fn room_alias_check(
.map_err(|e| {
err!(Request(InvalidParam(debug_error!(
?e,
%room_alias_name,
?room_alias_name,
"Failed to parse room alias.",
))))
})?;
@@ -711,7 +711,7 @@ fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<Own
}
})
.inspect(|full_room_id| {
debug_info!(%full_room_id, "Full custom room ID");
debug_info!(?full_room_id, "Full custom room ID");
})
.inspect_err(|e| warn!(?e, %custom_room_id, "Failed to create room with custom room ID",))
.inspect_err(|e| warn!(?e, ?custom_room_id, "Failed to create room with custom room ID",))
}
+3 -6
View File
@@ -46,7 +46,7 @@ pub(crate) async fn get_room_summary_legacy(
/// # `GET /_matrix/client/v1/room_summary/{roomIdOrAlias}`
///
/// Returns a short description of the state of a room.
#[tracing::instrument(skip_all, fields(%client), name = "room_summary", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "room_summary")]
pub(crate) async fn get_room_summary(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -116,10 +116,7 @@ async fn local_room_summary_response(
room_id: &RoomId,
sender_user: Option<&UserId>,
) -> Result<get_summary::msc3266::Response> {
trace!(
sender_user = sender_user.map(tracing::field::display),
"Sending local room summary response for {room_id:?}"
);
trace!(?sender_user, "Sending local room summary response for {room_id:?}");
let (join_rule, world_readable, guest_can_join) = join3(
services.rooms.state_accessor.get_join_rules(room_id),
services.rooms.state_accessor.is_world_readable(room_id),
@@ -231,7 +228,7 @@ async fn remote_room_summary_hierarchy_response(
servers: &[OwnedServerName],
sender_user: Option<&UserId>,
) -> Result<SpaceHierarchyParentSummary> {
trace!(sender_user = ?sender_user.map(tracing::field::display), ?servers, "Sending remote room summary response for {room_id:?}");
trace!(?sender_user, ?servers, "Sending remote room summary response for {room_id:?}");
if !services.config.allow_federation {
return Err!(Request(Forbidden("Federation is disabled.")));
}
+7 -18
View File
@@ -5,7 +5,6 @@ use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Error, Result, debug, err, info,
utils::{self, ReadyExt, hash},
warn,
};
use conduwuit_core::{debug_error, debug_warn};
use conduwuit_service::{Services, uiaa::SESSION_ID_LENGTH};
@@ -13,7 +12,6 @@ use futures::StreamExt;
use ruma::{
OwnedUserId, UserId,
api::client::{
error::ErrorKind,
session::{
get_login_token,
get_login_types::{
@@ -37,7 +35,7 @@ use crate::Ruma;
///
/// Get the supported login types of this server. One of these should be used as
/// the `type` field when logging in.
#[tracing::instrument(skip_all, fields(%client), name = "login", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "login")]
pub(crate) async fn get_login_types_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -55,7 +53,7 @@ pub(crate) async fn get_login_types_route(
/// Authenticates the given user by its ID and its password.
///
/// Returns the user ID if successful, and an error otherwise.
#[tracing::instrument(skip_all, fields(%user_id), name = "password", level = "debug")]
#[tracing::instrument(skip_all, fields(%user_id), name = "password")]
pub(crate) async fn password_login(
services: &Services,
user_id: &UserId,
@@ -98,7 +96,7 @@ pub(crate) async fn password_login(
///
/// Creates the user if the user is found in the LDAP and do not already have an
/// account.
#[tracing::instrument(skip_all, fields(%user_id), name = "ldap", level = "debug")]
#[tracing::instrument(skip_all, fields(%user_id), name = "ldap")]
pub(super) async fn ldap_login(
services: &Services,
user_id: &UserId,
@@ -186,15 +184,6 @@ pub(crate) async fn handle_login(
return Err!(Request(Unknown("User ID does not belong to this homeserver")));
}
if services.users.is_locked(&user_id).await? {
return Err(Error::BadRequest(ErrorKind::UserLocked, "This account has been locked."));
}
if services.users.is_login_disabled(&user_id).await {
warn!(%user_id, "user attempted to log in with a login-disabled account");
return Err!(Request(Forbidden("This account is not permitted to log in.")));
}
if cfg!(feature = "ldap") && services.config.ldap.enable {
match Box::pin(ldap_login(services, &user_id, &lowercased_user_id, password)).await {
| Ok(user_id) => Ok(user_id),
@@ -223,7 +212,7 @@ pub(crate) async fn handle_login(
/// Note: You can use [`GET
/// /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see
/// supported login types.
#[tracing::instrument(skip_all, fields(%client), name = "login", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "login")]
pub(crate) async fn login_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -356,7 +345,7 @@ pub(crate) async fn login_route(
/// to log in with the m.login.token flow.
///
/// <https://spec.matrix.org/v1.13/client-server-api/#post_matrixclientv1loginget_token>
#[tracing::instrument(skip_all, fields(%client), name = "login_token", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "login_token")]
pub(crate) async fn login_token_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -424,7 +413,7 @@ pub(crate) async fn login_token_route(
/// last seen ts)
/// - Forgets to-device events
/// - Triggers device list updates
#[tracing::instrument(skip_all, fields(%client), name = "logout", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "logout")]
pub(crate) async fn logout_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -451,7 +440,7 @@ pub(crate) async fn logout_route(
/// Note: This is equivalent to calling [`GET
/// /_matrix/client/r0/logout`](fn.logout_route.html) from each device of this
/// user.
#[tracing::instrument(skip_all, fields(%client), name = "logout", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "logout")]
pub(crate) async fn logout_all_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
+7 -7
View File
@@ -140,8 +140,8 @@ pub(crate) async fn get_state_events_for_key_route(
.await
.map_err(|_| {
err!(Request(NotFound(debug_warn!(
room_id = %body.room_id,
event_type = %body.event_type,
room_id = ?body.room_id,
event_type = ?body.event_type,
"State event not found in room.",
))))
})?;
@@ -226,7 +226,7 @@ async fn allowed_to_send_state_event(
match event_type {
| StateEventType::RoomCreate => {
return Err!(Request(BadJson(debug_warn!(
%room_id,
?room_id,
"You cannot update m.room.create after a room has been created."
))));
},
@@ -237,7 +237,7 @@ async fn allowed_to_send_state_event(
| Ok(acl_content) => {
if acl_content.allow_is_empty() {
return Err!(Request(BadJson(debug_warn!(
%room_id,
?room_id,
"Sending an ACL event with an empty allow key will permanently \
brick the room for non-conduwuit's as this equates to no servers \
being allowed to participate in this room."
@@ -246,7 +246,7 @@ async fn allowed_to_send_state_event(
if acl_content.deny_contains("*") && acl_content.allow_contains("*") {
return Err!(Request(BadJson(debug_warn!(
%room_id,
?room_id,
"Sending an ACL event with a deny and allow key value of \"*\" will \
permanently brick the room for non-conduwuit's as this equates to \
no servers being allowed to participate in this room."
@@ -258,7 +258,7 @@ async fn allowed_to_send_state_event(
&& !acl_content.allow_contains(services.globals.server_name().as_str())
{
return Err!(Request(BadJson(debug_warn!(
%room_id,
?room_id,
"Sending an ACL event with a deny key value of \"*\" and without \
your own server name in the allow key will result in you being \
unable to participate in this room."
@@ -270,7 +270,7 @@ async fn allowed_to_send_state_event(
&& !acl_content.allow_contains(services.globals.server_name().as_str())
{
return Err!(Request(BadJson(debug_warn!(
%room_id,
?room_id,
"Sending an ACL event for an allow key without \"*\" and without \
your own server name in the allow key will result in you being \
unable to participate in this room."
+5 -5
View File
@@ -50,8 +50,8 @@ use crate::client::{
level = "debug",
skip_all,
fields(
room_id = %room_id,
syncing_user = %sync_context.syncing_user,
room_id = ?room_id,
syncing_user = ?sync_context.syncing_user,
),
)]
pub(super) async fn load_joined_room(
@@ -578,7 +578,7 @@ async fn build_notification_counts(
)
.await;
trace!(%notification_count, %highlight_count, "syncing new notification counts");
trace!(?notification_count, ?highlight_count, "syncing new notification counts");
Ok(Some(UnreadNotificationsCount {
notification_count: Some(notification_count),
@@ -692,8 +692,8 @@ async fn build_room_summary(
};
trace!(
%joined_member_count,
%invited_member_count,
?joined_member_count,
?invited_member_count,
heroes_length = heroes.as_ref().map(HashSet::len),
"syncing updated summary"
);
+2 -2
View File
@@ -307,8 +307,8 @@ async fn build_left_state_and_timeline(
}
trace!(
%timeline_start_count,
%timeline_end_count,
?timeline_start_count,
?timeline_end_count,
"syncing {} timeline events (limited = {}) and {} state events",
timeline.pdus.len(),
timeline.limited,
+1 -1
View File
@@ -275,7 +275,7 @@ pub(crate) async fn build_sync_events(
match joined_room {
| Ok((room, updates)) => Some((room_id, room, updates)),
| Err(err) => {
warn!(?err, %room_id, "error loading joined room");
warn!(?err, ?room_id, "error loading joined room {}", room_id);
None
},
}
+1 -1
View File
@@ -217,7 +217,7 @@ pub(super) async fn build_state_incremental<'a>(
the performance penalty is acceptable.
*/
trace!(%timeline_is_linear, %timeline.limited, "computing state for incremental sync");
trace!(?timeline_is_linear, ?timeline.limited, "computing state for incremental sync");
// fetch the shorteventids of state events in the timeline
let state_events_in_timeline: BTreeSet<ShortEventId> = services
+1 -1
View File
@@ -26,7 +26,7 @@ use crate::Ruma;
/// TODO: Implement pagination, currently this just returns everything
///
/// An implementation of [MSC2666](https://github.com/matrix-org/matrix-spec-proposals/pull/2666)
#[tracing::instrument(skip_all, fields(%client), name = "mutual_rooms", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "mutual_rooms")]
pub(crate) async fn get_mutual_rooms_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
+6 -24
View File
@@ -137,30 +137,12 @@ pub(super) async fn auth(
| (
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None,
Token::User((user_id, device_id)),
) => {
let is_locked = services.users.is_locked(&user_id).await.map_err(|e| {
err!(Request(Forbidden(warn!("Failed to check user lock status: {e}"))))
})?;
if is_locked {
// Only /logout and /logout/all are allowed for locked users
if !matches!(
metadata,
&ruma::api::client::session::logout::v3::Request::METADATA
| &ruma::api::client::session::logout_all::v3::Request::METADATA
) {
return Err(Error::BadRequest(
ErrorKind::UserLocked,
"This account has been locked.",
));
}
}
Ok(Auth {
origin: None,
sender_user: Some(user_id),
sender_device: Some(device_id),
appservice_info: None,
})
},
) => Ok(Auth {
origin: None,
sender_user: Some(user_id),
sender_device: Some(device_id),
appservice_info: None,
}),
| (AuthScheme::ServerSignatures, Token::None) =>
Ok(auth_server(services, request, json_body).await?),
| (
+3 -3
View File
@@ -40,7 +40,7 @@ pub(crate) async fn get_missing_events_route(
while i < queued_events.len() && events.len() < limit {
let Ok(pdu) = services.rooms.timeline.get_pdu(&queued_events[i]).await else {
debug!(
body.origin = body.origin.as_ref().map(tracing::field::display),
?body.origin,
"Event {} does not exist locally, skipping", &queued_events[i]
);
i = i.saturating_add(1);
@@ -59,7 +59,7 @@ pub(crate) async fn get_missing_events_route(
.await
{
debug!(
body.origin = body.origin.as_ref().map(tracing::field::display),
?body.origin,
"Server cannot see {:?} in {:?}, skipping", pdu.event_id, pdu.room_id
);
i = i.saturating_add(1);
@@ -68,7 +68,7 @@ pub(crate) async fn get_missing_events_route(
let Ok(event) = to_canonical_object(&pdu) else {
debug_error!(
body.origin = body.origin.as_ref().map(tracing::field::display),
?body.origin,
"Failed to convert PDU in database to canonical JSON: {pdu:?}"
);
i = i.saturating_add(1);
+1 -10
View File
@@ -19,7 +19,7 @@ use crate::Ruma;
/// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}`
///
/// Invites a remote user to a room.
#[tracing::instrument(skip_all, fields(%client), name = "invite", level = "info")]
#[tracing::instrument(skip_all, fields(%client), name = "invite")]
pub(crate) async fn create_invite_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -148,15 +148,6 @@ pub(crate) async fn create_invite_route(
return Err!(Request(Forbidden("This server does not allow room invites.")));
}
if let Err(e) = services
.antispam
.user_may_invite(sender_user.to_owned(), recipient_user.clone(), body.room_id.clone())
.await
{
warn!("Antispam rejected invite: {e:?}");
return Err!(Request(Forbidden("Invite rejected by antispam service.")));
}
let mut invite_state = body.invite_room_state.clone();
let mut event: JsonObject = serde_json::from_str(body.event.get())
+23 -51
View File
@@ -1,7 +1,7 @@
use std::borrow::ToOwned;
use axum::extract::State;
use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn};
use conduwuit::{
Err, Error, Result, debug_info, info, matrix::pdu::PduBuilder, utils::IterStream, warn,
};
use conduwuit_service::Services;
use futures::StreamExt;
use ruma::{
@@ -22,7 +22,7 @@ use crate::Ruma;
/// # `GET /_matrix/federation/v1/make_join/{roomId}/{userId}`
///
/// Creates a join template.
#[tracing::instrument(skip_all, fields(room_id = %body.room_id, user_id = %body.user_id, origin = %body.origin()), level = "info")]
#[tracing::instrument(skip_all, fields(room_id = %body.room_id, user_id = %body.user_id, origin = %body.origin()))]
pub(crate) async fn create_join_event_template_route(
State(services): State<crate::State>,
body: Ruma<prepare_join_event::v1::Request>,
@@ -122,16 +122,6 @@ pub(crate) async fn create_join_event_template_route(
None
}
};
if services.antispam.check_all_joins() && join_authorized_via_users_server.is_none() {
if services
.antispam
.meowlnir_accept_make_join(body.room_id.clone(), body.user_id.clone())
.await
.is_err()
{
return Err!(Request(Forbidden("Antispam rejected join request.")));
}
}
let (_pdu, mut pdu_json) = services
.rooms
@@ -146,6 +136,7 @@ pub(crate) async fn create_join_event_template_route(
&state_lock,
)
.await?;
drop(state_lock);
// room v3 and above removed the "event_id" field from remote PDU format
@@ -201,44 +192,25 @@ pub(crate) async fn user_can_perform_restricted_join(
return Ok(false);
}
for allow_rule in &r.allow {
match allow_rule {
| AllowRule::RoomMembership(membership) => {
if services
.rooms
.state_cache
.is_joined(user_id, &membership.room_id)
.await
{
debug!(
"User {} is allowed to join room {} via membership in room {}",
user_id, room_id, membership.room_id
);
return Ok(true);
}
},
| AllowRule::UnstableSpamChecker =>
return match services
.antispam
.meowlnir_accept_make_join(room_id.to_owned(), user_id.to_owned())
.await
{
| Ok(()) => Ok(true),
| Err(_) => Err!(Request(Forbidden("Antispam rejected join request."))),
},
| _ => {
debug_info!(
"Unsupported allow rule in restricted join for room {}: {:?}",
room_id,
allow_rule
);
},
}
if r.allow
.iter()
.filter_map(|rule| {
if let AllowRule::RoomMembership(membership) = rule {
Some(membership)
} else {
None
}
})
.stream()
.any(|m| services.rooms.state_cache.is_joined(user_id, &m.room_id))
.await
{
Ok(true)
} else {
Err!(Request(UnableToAuthorizeJoin(
"Joining user is not known to be in any required room."
)))
}
Err!(Request(UnableToAuthorizeJoin(
"Joining user is not known to be in any required room."
)))
}
pub(crate) fn maybe_strip_event_id(
+8 -6
View File
@@ -3,7 +3,9 @@ use std::{collections::BTreeMap, net::IpAddr, time::Instant};
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Error, Result, debug, debug_warn, err, error,
Err, Error, Result, debug,
debug::INFO_SPAN_LEVEL,
debug_warn, err, error,
result::LogErr,
trace,
utils::{
@@ -46,7 +48,7 @@ type Pdu = (OwnedRoomId, OwnedEventId, CanonicalJsonObject);
/// Push EDUs and PDUs to this server.
#[tracing::instrument(
name = "txn",
level = "debug",
level = INFO_SPAN_LEVEL,
skip_all,
fields(
%client,
@@ -81,8 +83,8 @@ pub(crate) async fn send_transaction_message_route(
pdus = body.pdus.len(),
edus = body.edus.len(),
elapsed = ?txn_start_time.elapsed(),
id = %body.transaction_id,
origin = %body.origin(),
id = ?body.transaction_id,
origin =?body.origin(),
"Starting txn",
);
@@ -108,8 +110,8 @@ pub(crate) async fn send_transaction_message_route(
pdus = body.pdus.len(),
edus = body.edus.len(),
elapsed = ?txn_start_time.elapsed(),
id = %body.transaction_id,
origin = %body.origin(),
id = ?body.transaction_id,
origin =?body.origin(),
"Finished txn",
);
for (id, result) in &results {
+1 -1
View File
@@ -26,7 +26,7 @@ use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
use crate::Ruma;
/// helper method for /send_join v1 and v2
#[tracing::instrument(skip(services, pdu, omit_members), fields(room_id = room_id.as_str(), origin = origin.as_str()), level = "info")]
#[tracing::instrument(skip(services, pdu, omit_members), fields(room_id = room_id.as_str(), origin = origin.as_str()))]
async fn create_join_event(
services: &Services,
origin: &ServerName,
+2
View File
@@ -1,7 +1,9 @@
[package]
name = "conduwuit_build_metadata"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
+2
View File
@@ -1,7 +1,9 @@
[package]
name = "conduwuit_core"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
+2 -2
View File
@@ -340,7 +340,7 @@ where
#[tracing::instrument(
name = "get",
level = "trace",
level = "trace"
skip_all,
fields(?key)
)]
@@ -357,7 +357,7 @@ where
#[tracing::instrument(
name = "xchg",
level = "trace",
level = "trace"
skip_all,
fields(?key, ?val)
)]
+36
View File
@@ -146,6 +146,22 @@ pub fn check(config: &Config) -> Result {
));
}
// check if we can read the token file path, and check if the file is empty
if config.registration_token_file.as_ref().is_some_and(|path| {
let Ok(token) = std::fs::read_to_string(path).inspect_err(|e| {
error!("Failed to read the registration token file: {e}");
}) else {
return true;
};
token == String::new()
}) {
return Err!(Config(
"registration_token_file",
"Registration token file was specified but is empty or failed to be read"
));
}
if config.max_request_size < 10_000_000 {
return Err!(Config(
"max_request_size",
@@ -171,9 +187,29 @@ pub fn check(config: &Config) -> Result {
));
}
if config.allow_registration
&& !config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
&& config.registration_token.is_none()
&& config.registration_token_file.is_none()
&& config.recaptcha_site_key.is_none()
{
return Err!(Config(
"registration_token",
"!! You have `allow_registration` enabled without a token or captcha configured \
which means you are allowing ANYONE to register on your continuwuity instance \
without any 2nd-step (e.g. registration token, captcha), which is FREQUENTLY \
abused by malicious actors. If this is not the intended behaviour, please set a \
registration token. For security and safety reasons, continuwuity will shut down. \
If you are extra sure this is the desired behaviour you want, please set the \
following config option to true:
`yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`"
));
}
if config.allow_registration
&& config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
&& config.registration_token.is_none()
&& config.registration_token_file.is_none()
{
warn!(
"Open registration is enabled via setting \
+1 -1
View File
@@ -59,7 +59,7 @@ impl Deref for Manager {
/// Update the active configuration, returning prior configuration.
#[implement(Manager)]
#[tracing::instrument(skip_all, level = "info")]
#[tracing::instrument(skip_all)]
pub fn update(&self, config: Config) -> Result<Arc<Config>> {
let config = Arc::new(config);
let new = Arc::into_raw(config);
+41 -65
View File
@@ -18,7 +18,7 @@ use figment::providers::{Env, Format, Toml};
pub use figment::{Figment, value::Value as FigmentValue};
use regex::RegexSet;
use ruma::{
OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
api::client::discovery::discover_support::ContactRole,
};
use serde::{Deserialize, de::IgnoredAny};
@@ -53,8 +53,7 @@ use crate::{Result, err, error::Error, utils::sys};
### For more information, see:
### https://continuwuity.org/configuration.html
"#,
ignore = "config_paths catchall well_known tls blurhashing \
allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure antispam"
ignore = "config_paths catchall well_known tls blurhashing allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure"
)]
pub struct Config {
// Paths to config file(s). Not supposed to be set manually in the config file,
@@ -501,7 +500,19 @@ pub struct Config {
#[serde(default = "default_appservice_idle_timeout")]
pub appservice_idle_timeout: u64,
/// Notification gateway pusher idle connection pool timeout.
/// Notification gateway pusher request connection timeout (seconds).
///
/// default: 15
#[serde(default = "default_pusher_conn_timeout")]
pub pusher_conn_timeout: u64,
/// Notification gateway pusher total request timeout (seconds).
///
/// default: 60
#[serde(default = "default_pusher_timeout")]
pub pusher_timeout: u64,
/// Notification gateway pusher idle connection pool timeout (seconds).
///
/// default: 15
#[serde(default = "default_pusher_idle_timeout")]
@@ -545,7 +556,7 @@ pub struct Config {
/// `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
///
/// If you would like registration only via token reg, please configure
/// `registration_token`.
/// `registration_token` or `registration_token_file`.
#[serde(default)]
pub allow_registration: bool,
@@ -576,14 +587,22 @@ pub struct Config {
/// `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.
/// YOU NEED TO EDIT THIS OR USE registration_token_file.
///
/// example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
///
/// display: sensitive
pub registration_token: Option<String>,
/// Path to a file on the system that gets read for additional registration
/// tokens. Multiple tokens can be added if you separate them with
/// whitespace
///
/// continuwuity must be able to access the file, and it must not be empty
///
/// example: "/etc/continuwuity/.reg_token"
pub registration_token_file: Option<PathBuf>,
/// The public site key for reCaptcha. If this is provided, reCaptcha
/// becomes required during registration. If both captcha *and*
/// registration token are enabled, both will be required during
@@ -1663,6 +1682,13 @@ pub struct Config {
#[serde(default = "default_url_preview_max_spider_size")]
pub url_preview_max_spider_size: usize,
/// Total request timeout for URL previews (seconds). This includes
/// connection, request, and response body reading time.
///
/// default: 120
#[serde(default = "default_url_preview_timeout")]
pub url_preview_timeout: u64,
/// Option to decide whether you would like to run the domain allowlist
/// checks (contains and explicit) on the root domain or not. Does not apply
/// to URL contains allowlist. Defaults to false.
@@ -1880,7 +1906,7 @@ pub struct Config {
/// Enable the tokio-console. This option is only relevant to developers.
///
/// For more information, see:
/// For more information, see:
/// https://continuwuity.org/development.html#debugging-with-tokio-console
#[serde(default)]
pub tokio_console: bool,
@@ -2017,10 +2043,6 @@ pub struct Config {
#[serde(default)]
pub ldap: LdapConfig,
/// Configuration for antispam support
#[serde(default)]
pub antispam: Option<Antispam>,
// external structure; separate section
#[serde(default)]
pub blurhashing: BlurhashConfig,
@@ -2237,58 +2259,7 @@ struct ListeningAddr {
addrs: Either<IpAddr, Vec<IpAddr>>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Antispam {
pub meowlnir: Option<MeowlnirConfig>,
pub draupnir: Option<DraupnirConfig>,
}
#[derive(Clone, Debug, Deserialize)]
#[config_example_generator(
filename = "conduwuit-example.toml",
section = "global.antispam.meowlnir"
)]
pub struct MeowlnirConfig {
/// The base URL on which to contact Meowlnir (before /_meowlnir/antispam).
///
/// Example: "http://127.0.0.1:29339"
pub base_url: Url,
/// The authentication secret defined in antispam->secret. Required for
/// continuwuity to talk to Meowlnir.
pub secret: String,
/// The management room for which to send requests
pub management_room: OwnedRoomId,
/// If enabled run all federated join attempts (both federated and local)
/// through the Meowlnir anti-spam checks.
///
/// By default, only join attempts for rooms with the `fi.mau.spam_checker`
/// restricted join rule are checked.
#[serde(default)]
pub check_all_joins: bool,
}
// TODO: the DraupnirConfig and MeowlnirConfig are basically identical.
// Maybe management_room could just become an Option<> and these structs merged?
#[derive(Clone, Debug, Deserialize)]
#[config_example_generator(
filename = "conduwuit-example.toml",
section = "global.antispam.draupnir"
)]
pub struct DraupnirConfig {
/// The base URL on which to contact Draupnir (before /api/).
///
/// Example: "http://127.0.0.1:29339"
pub base_url: Url,
/// The authentication secret defined in
/// web->synapseHTTPAntispam->authorization
pub secret: String,
}
const DEPRECATED_KEYS: &[&str] = &[
const DEPRECATED_KEYS: &[&str; 9] = &[
"cache_capacity",
"conduit_cache_capacity_modifier",
"max_concurrent_requests",
@@ -2298,7 +2269,6 @@ const DEPRECATED_KEYS: &[&str] = &[
"well_known_support_role",
"well_known_support_email",
"well_known_support_mxid",
"registration_token_file",
];
impl Config {
@@ -2473,6 +2443,10 @@ fn default_appservice_timeout() -> u64 { 35 }
fn default_appservice_idle_timeout() -> u64 { 300 }
fn default_pusher_conn_timeout() -> u64 { 15 }
fn default_pusher_timeout() -> u64 { 60 }
fn default_pusher_idle_timeout() -> u64 { 15 }
fn default_max_fetch_prev_events() -> u16 { 192_u16 }
@@ -2600,6 +2574,8 @@ fn default_url_preview_max_spider_size() -> usize {
256_000 // 256KB
}
fn default_url_preview_timeout() -> u64 { 120 }
fn default_new_user_displayname_suffix() -> String { "🏳️‍⚧️".to_owned() }
fn default_sentry_endpoint() -> Option<Url> { None }
+1 -3
View File
@@ -75,12 +75,10 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
| ThreepidDenied
| InviteBlocked
| WrongRoomKeysVersion { .. }
| UserSuspended
| Forbidden { .. } => StatusCode::FORBIDDEN,
// 401
| UnknownToken { .. } | MissingToken | Unauthorized | UserLocked =>
StatusCode::UNAUTHORIZED,
| UnknownToken { .. } | MissingToken | Unauthorized => StatusCode::UNAUTHORIZED,
// 400
| _ => StatusCode::BAD_REQUEST,
+10 -11
View File
@@ -532,8 +532,8 @@ where
if sender_power_level < invite_level {
warn!(
%sender,
has=%sender_power_level,
required=%invite_level,
has=?sender_power_level,
required=?invite_level,
"sender cannot send invites in this room"
);
return Ok(false);
@@ -605,8 +605,8 @@ where
if !check_redaction(room_version, incoming_event, sender_power_level, redact_level)? {
warn!(
%sender,
%sender_power_level,
%redact_level,
?sender_power_level,
?redact_level,
"redaction event was not allowed"
);
return Ok(false);
@@ -772,12 +772,11 @@ where
power_levels_event.as_ref().is_some(),
) || auth_user_pl >= invite_level;
trace!(
%auth_user_pl,
%auth_user_pl,
%invite_level,
%user_joined,
%okay_power,
passing=%(user_joined && okay_power),
auth_user_pl=?auth_user_pl,
invite_level=?invite_level,
user_joined=?user_joined,
okay_power=?okay_power,
passing=?(user_joined && okay_power),
"user for join auth is valid check details"
);
user_joined && okay_power
@@ -1212,7 +1211,7 @@ fn can_send_event(event: &impl Event, ple: Option<&impl Event>, user_level: Int)
{
warn!(
%user_level,
required=%event_type_power_level,
required=?event_type_power_level,
state_key=?event.state_key(),
sender=%event.sender(),
"state_key starts with @ but does not match sender",
+2
View File
@@ -1,7 +1,9 @@
[package]
name = "conduwuit_database"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
+1 -1
View File
@@ -7,7 +7,7 @@ use super::Engine;
use crate::util::map_err;
#[implement(Engine)]
#[tracing::instrument(skip(self), level = "info")]
#[tracing::instrument(skip(self))]
pub fn backup(&self) -> Result {
let mut engine = self.backup_engine()?;
let config = &self.ctx.server.config;
+1 -1
View File
@@ -4,7 +4,7 @@ use rocksdb::LogLevel;
#[tracing::instrument(
parent = None,
name = "rocksdb",
level = "trace",
level = "trace"
skip(msg),
)]
pub(crate) fn handle(level: LogLevel, msg: &str) {
+3 -3
View File
@@ -17,7 +17,7 @@ use super::{
use crate::{Context, or_else};
#[implement(Engine)]
#[tracing::instrument(skip_all, level = "info")]
#[tracing::instrument(skip_all)]
pub(crate) async fn open(ctx: Arc<Context>, desc: &[Descriptor]) -> Result<Arc<Self>> {
let server = &ctx.server;
let config = &server.config;
@@ -63,7 +63,7 @@ pub(crate) async fn open(ctx: Arc<Context>, desc: &[Descriptor]) -> Result<Arc<S
}
#[implement(Engine)]
#[tracing::instrument(name = "configure", skip_all, level = "debug")]
#[tracing::instrument(name = "configure", skip_all)]
fn configure_cfds(
ctx: &Arc<Context>,
db_opts: &Options,
@@ -119,7 +119,7 @@ fn configure_cfds(
}
#[implement(Engine)]
#[tracing::instrument(name = "discover", skip_all, level = "debug")]
#[tracing::instrument(name = "discover", skip_all)]
fn discover_cfs(path: &Path, opts: &Options) -> BTreeSet<String> {
Db::list_cf(opts, path)
.unwrap_or_default()
+1 -1
View File
@@ -26,7 +26,7 @@ pub struct Options {
#[implement(super::Map)]
#[tracing::instrument(
name = "compact",
level = "info",
level = "info"
skip(self),
fields(%self),
)]
-12
View File
@@ -141,10 +141,6 @@ pub(super) static MAPS: &[Descriptor] = &[
name: "referencedevents",
..descriptor::RANDOM
},
Descriptor {
name: "registrationtoken_info",
..descriptor::RANDOM_SMALL
},
Descriptor {
name: "roomid_invitedcount",
..descriptor::RANDOM_SMALL
@@ -390,14 +386,6 @@ pub(super) static MAPS: &[Descriptor] = &[
name: "userid_suspension",
..descriptor::RANDOM_SMALL
},
Descriptor {
name: "userid_lock",
..descriptor::RANDOM_SMALL
},
Descriptor {
name: "userid_logindisabled",
..descriptor::RANDOM_SMALL
},
Descriptor {
name: "userid_presenceid",
..descriptor::RANDOM_SMALL
+4 -4
View File
@@ -113,7 +113,7 @@ impl Drop for Pool {
}
#[implement(Pool)]
#[tracing::instrument(skip_all, level = "debug")]
#[tracing::instrument(skip_all)]
pub(crate) fn close(&self) {
let workers = take(&mut *self.workers.lock());
@@ -147,8 +147,8 @@ pub(crate) fn close(&self) {
.map(|result| result.map_err(Error::from_panic))
.enumerate()
.for_each(|(id, result)| match result {
| Ok(()) => trace!(%id, "worker joined"),
| Err(error) => error!(%id, "worker joined with error: {error}"),
| Ok(()) => trace!(?id, "worker joined"),
| Err(error) => error!(?id, "worker joined with error: {error}"),
});
}
@@ -297,7 +297,7 @@ fn worker_init(&self, id: usize) {
}
debug!(
%group,
?group,
affinity = ?affinity.collect::<Vec<_>>(),
"worker ready"
);
+9 -9
View File
@@ -105,8 +105,8 @@ pub(super) fn configure(server: &Arc<Server>) -> (usize, Vec<usize>, Vec<usize>)
.unwrap_or("None"),
?worker_counts,
?queue_sizes,
%total_workers,
stream_width = %stream::automatic_width(),
?total_workers,
stream_width = ?stream::automatic_width(),
"Frontend topology",
);
@@ -139,13 +139,13 @@ fn update_stream_width(server: &Arc<Server>, num_queues: usize, total_workers: u
let (old_width, new_width) = stream::set_width(req_width);
let (old_amp, new_amp) = stream::set_amplification(req_amp);
debug!(
scale = %config.stream_width_scale,
%num_queues,
%req_width,
%old_width,
%new_width,
%old_amp,
%new_amp,
scale = ?config.stream_width_scale,
?num_queues,
?req_width,
?old_width,
?new_width,
?old_amp,
?new_amp,
"Updated global stream width"
);
}
+2
View File
@@ -1,7 +1,9 @@
[package]
name = "conduwuit_macros"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
+6 -2
View File
@@ -2,12 +2,15 @@
name = "conduwuit"
default-run = "conduwuit"
authors.workspace = true
categories.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true
metadata.crane.workspace = true
@@ -20,13 +23,14 @@ crate-type = [
[package.metadata.deb]
name = "continuwuity"
maintainer = "Continuwuity Team and contributors <team@continuwuity.org>"
maintainer = "continuwuity developers <contact@continuwuity.org>"
copyright = "2024, continuwuity developers"
license-file = ["../../LICENSE", "3"]
depends = "$auto, ca-certificates"
breaks = ["conduwuit (<<0.5.0)"]
replaces = ["conduwuit (<<0.5.0)"]
extended-description = """\
A Matrix homeserver written in Rust, the official continuation of the conduwuit homeserver."""
a cool hard fork of Conduit, a Matrix homeserver written in Rust"""
section = "net"
priority = "optional"
conf-files = ["/etc/conduwuit/conduwuit.toml"]
+2 -3
View File
@@ -6,7 +6,6 @@ use conduwuit_core::{
debug_warn, err,
log::{ConsoleFormat, ConsoleWriter, LogLevelReloadHandles, capture, fmt_span},
result::UnwrapOrErr,
warn,
};
#[cfg(feature = "otlp_telemetry")]
use opentelemetry::trace::TracerProvider;
@@ -86,7 +85,7 @@ pub(crate) fn init(
let exporter = match config.otlp_protocol.as_str() {
| "grpc" => opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_protocol(opentelemetry_otlp::Protocol::Grpc) // TODO: build from env when 0.32 is released
.with_protocol(opentelemetry_otlp::Protocol::Grpc)
.build()
.expect("Failed to create OTLP gRPC exporter"),
| "http" => opentelemetry_otlp::SpanExporter::builder()
@@ -94,7 +93,7 @@ pub(crate) fn init(
.build()
.expect("Failed to create OTLP HTTP exporter"),
| protocol => {
warn!(
debug_warn!(
"Invalid OTLP protocol '{}', falling back to HTTP. Valid options are \
'http' or 'grpc'.",
protocol
+1 -2
View File
@@ -50,8 +50,7 @@ pub fn run_with_args(args: &Args) -> Result<()> {
#[tracing::instrument(
name = "main",
parent = None,
skip_all,
level = "info"
skip_all
)]
async fn async_main(server: &Arc<Server>) -> Result<(), Error> {
extern crate conduwuit_router as router;
+3 -3
View File
@@ -6,7 +6,7 @@ use tokio::signal;
use super::server::Server;
#[cfg(unix)]
#[tracing::instrument(skip_all, level = "info")]
#[tracing::instrument(skip_all)]
pub(super) async fn signal(server: Arc<Server>) {
use signal::unix;
use unix::SignalKind;
@@ -39,13 +39,13 @@ pub(super) async fn signal(server: Arc<Server>) {
};
if let Err(e) = result {
debug_error!(%sig, "signal: {e}");
debug_error!(?sig, "signal: {e}");
}
}
}
#[cfg(not(unix))]
#[tracing::instrument(skip_all, level = "info")]
#[tracing::instrument(skip_all)]
pub(super) async fn signal(server: Arc<Server>) {
loop {
tokio::select! {
+2
View File
@@ -1,7 +1,9 @@
[package]
name = "conduwuit_router"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
+4 -4
View File
@@ -102,13 +102,13 @@ fn handle_result(method: &Method, uri: &Uri, result: Response) -> Result<Respons
let reason = status.canonical_reason().unwrap_or("Unknown Reason");
if status.is_server_error() {
error!(%method, %uri, "{code} {reason}");
error!(method = ?method, uri = ?uri, "{code} {reason}");
} else if status.is_client_error() {
debug_error!(%method, %uri, "{code} {reason}");
debug_error!(method = ?method, uri = ?uri, "{code} {reason}");
} else if status.is_redirection() {
debug!(%method, %uri, "{code} {reason}");
debug!(method = ?method, uri = ?uri, "{code} {reason}");
} else {
trace!(%method, %uri, "{code} {reason}");
trace!(method = ?method, uri = ?uri, "{code} {reason}");
}
if status == StatusCode::METHOD_NOT_ALLOWED {
+5 -5
View File
@@ -19,7 +19,7 @@ use tokio::{
use crate::serve;
/// Main loop base
#[tracing::instrument(skip_all, level = "info")]
#[tracing::instrument(skip_all)]
pub(crate) async fn run(services: Arc<Services>) -> Result<()> {
let server = &services.server;
debug!("Start");
@@ -58,7 +58,7 @@ pub(crate) async fn run(services: Arc<Services>) -> Result<()> {
}
/// Async initializations
#[tracing::instrument(skip_all, level = "info")]
#[tracing::instrument(skip_all)]
pub(crate) async fn start(server: Arc<Server>) -> Result<Arc<Services>> {
debug!("Starting...");
@@ -73,7 +73,7 @@ pub(crate) async fn start(server: Arc<Server>) -> Result<Arc<Services>> {
}
/// Async destructions
#[tracing::instrument(skip_all, level = "info")]
#[tracing::instrument(skip_all)]
pub(crate) async fn stop(services: Arc<Services>) -> Result<()> {
debug!("Shutting down...");
@@ -108,7 +108,7 @@ pub(crate) async fn stop(services: Arc<Services>) -> Result<()> {
Ok(())
}
#[tracing::instrument(skip_all, level = "info")]
#[tracing::instrument(skip_all)]
async fn signal(server: Arc<Server>, tx: Sender<()>, handle: axum_server::Handle) {
server
.clone()
@@ -126,7 +126,7 @@ async fn handle_shutdown(server: Arc<Server>, tx: Sender<()>, handle: axum_serve
let timeout = Duration::from_secs(timeout);
debug!(
?timeout,
handle_active = %server.metrics.requests_handle_active.load(Ordering::Relaxed),
handle_active = ?server.metrics.requests_handle_active.load(Ordering::Relaxed),
"Notifying for graceful shutdown"
);
+2
View File
@@ -1,7 +1,9 @@
[package]
name = "conduwuit_service"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
+1 -1
View File
@@ -122,7 +122,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
let room_tag = self.services.server.config.admin_room_tag.as_str();
if !room_tag.is_empty() {
if let Err(e) = self.set_room_tag(&room_id, user_id, room_tag).await {
error!(%room_id, %user_id, %room_tag, "Failed to set tag for admin grant: {e}");
error!(?room_id, ?user_id, ?room_tag, "Failed to set tag for admin grant: {e}");
}
}
+1 -1
View File
@@ -96,7 +96,7 @@ impl crate::Service for Service {
}
if let Err(e) = self.check().await {
warn!(?e, "Failed to check for announcements");
warn!(%e, "Failed to check for announcements");
}
}
-182
View File
@@ -1,182 +0,0 @@
use std::{fmt::Debug, sync::Arc};
use async_trait::async_trait;
use conduwuit::{Result, config::Antispam, debug};
use ruma::{OwnedRoomId, OwnedUserId, draupnir_antispam, meowlnir_antispam};
use crate::{client, config, sending, service::Dep};
struct Services {
config: Dep<config::Service>,
client: Dep<client::Service>,
}
pub struct Service {
services: Services,
}
#[async_trait]
impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
services: Services {
client: args.depend::<client::Service>("client"),
config: args.depend::<config::Service>("config"),
},
}))
}
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
}
impl Service {
async fn send_antispam_request<T>(
&self,
base_url: &str,
secret: &str,
request: T,
) -> Result<T::IncomingResponse>
where
T: ruma::api::OutgoingRequest + Debug + Send,
{
sending::antispam::send_antispam_request(
&self.services.client.appservice,
base_url,
secret,
request,
)
.await
}
/// Checks with the antispam service whether `inviter` may invite `invitee`
/// to `room_id`.
///
/// If no antispam service is configured, this always returns `Ok(())`.
/// If an error is returned, the invite should be blocked - the antispam
/// service was unreachable, or refused the invite.
pub async fn user_may_invite(
&self,
inviter: OwnedUserId,
invitee: OwnedUserId,
room_id: OwnedRoomId,
) -> Result<()> {
if let Some(config) = &self.services.config.antispam {
let result = if let Some(meowlnir) = &config.meowlnir {
debug!(?room_id, ?inviter, ?invitee, "Asking meowlnir for user_may_invite");
self.send_antispam_request(
meowlnir.base_url.as_str(),
&meowlnir.secret,
meowlnir_antispam::user_may_invite::v1::Request::new(
meowlnir.management_room.clone(),
inviter,
invitee,
room_id,
),
)
.await
.inspect(|_| debug!("meowlnir allowed the invite"))
.inspect_err(|e| debug!("meowlnir denied the invite: {e:?}"))
.map(|_| ())
} else if let Some(draupnir) = &config.draupnir {
debug!(?room_id, ?inviter, ?invitee, "Asking draupnir for user_may_invite");
self.send_antispam_request(
draupnir.base_url.as_str(),
&draupnir.secret,
draupnir_antispam::user_may_invite::v1::Request::new(
room_id, inviter, invitee,
),
)
.await
.inspect(|_| debug!("draupnir allowed the invite"))
.inspect_err(|e| debug!("draupnir denied the invite: {e:?}"))
.map(|_| ())
} else {
Ok(())
};
return result;
}
Ok(())
}
/// Checks with the antispam service whether `user_id` may join `room_id`.
pub async fn user_may_join_room(
&self,
user_id: OwnedUserId,
room_id: OwnedRoomId,
is_invited: bool,
) -> Result<()> {
if let Some(config) = &self.services.config.antispam {
let result = if let Some(meowlnir) = &config.meowlnir {
debug!(?room_id, ?user_id, ?is_invited, "Asking meowlnir for user_may_join_room");
self.send_antispam_request(
meowlnir.base_url.as_str(),
&meowlnir.secret,
meowlnir_antispam::user_may_join_room::v1::Request::new(
meowlnir.management_room.clone(),
user_id,
room_id,
is_invited,
),
)
.await
.inspect(|_| debug!("meowlnir allowed the join"))
.inspect_err(|e| debug!("meowlnir denied the join: {e:?}"))
.map(|_| ())
} else if let Some(draupnir) = &config.draupnir {
debug!(?room_id, ?user_id, ?is_invited, "Asking draupnir for user_may_join_room");
self.send_antispam_request(
draupnir.base_url.as_str(),
&draupnir.secret,
draupnir_antispam::user_may_join_room::v1::Request::new(
user_id, room_id, is_invited,
),
)
.await
.inspect(|_| debug!("draupnir allowed the join"))
.inspect_err(|e| debug!("draupnir denied the join: {e:?}"))
.map(|_| ())
} else {
Ok(())
};
return result;
}
Ok(())
}
/// Checks with Meowlnir whether the incoming federated `make_join` request
/// should be allowed. Applies the `fi.mau.spam_checker` join rule.
pub async fn meowlnir_accept_make_join(
&self,
room_id: OwnedRoomId,
user_id: OwnedUserId,
) -> Result<()> {
if let Some(Antispam { meowlnir: Some(meowlnir), .. }) = &self.services.config.antispam {
debug!(?room_id, ?user_id, "Asking meowlnir for accept_make_join");
self.send_antispam_request(
meowlnir.base_url.as_str(),
&meowlnir.secret,
meowlnir_antispam::accept_make_join::v1::Request::new(
meowlnir.management_room.clone(),
user_id,
room_id,
),
)
.await
.inspect(|_| debug!("meowlnir allowed the make_join"))
.inspect_err(|e| debug!("meowlnir denied the make_join: {e:?}"))
.map(|_| ())
} else {
Ok(())
}
}
/// Returns whether all joins should be checked with Meowlnir.
/// Is always false if Meowlnir is not configured.
pub fn check_all_joins(&self) -> bool {
if let Some(Antispam { meowlnir: Some(cfg), .. }) = &self.services.config.antispam {
cfg.check_all_joins
} else {
false
}
}
}
+9
View File
@@ -47,6 +47,7 @@ impl crate::Service for Service {
})?
.local_address(url_preview_bind_addr)
.dns_resolver(resolver.resolver.clone())
.timeout(Duration::from_secs(config.url_preview_timeout))
.redirect(redirect::Policy::limited(3))
.build()?,
@@ -68,6 +69,11 @@ impl crate::Service for Service {
.dns_resolver(resolver.resolver.hooked.clone())
.connect_timeout(Duration::from_secs(config.federation_conn_timeout))
.read_timeout(Duration::from_secs(config.federation_timeout))
.timeout(Duration::from_secs(
config
.federation_timeout
.saturating_add(config.federation_conn_timeout),
))
.pool_max_idle_per_host(config.federation_idle_per_host.into())
.pool_idle_timeout(Duration::from_secs(config.federation_idle_timeout))
.redirect(redirect::Policy::limited(3))
@@ -77,6 +83,7 @@ impl crate::Service for Service {
.dns_resolver(resolver.resolver.hooked.clone())
.connect_timeout(Duration::from_secs(config.federation_conn_timeout))
.read_timeout(Duration::from_secs(305))
.timeout(Duration::from_secs(120))
.pool_max_idle_per_host(0)
.redirect(redirect::Policy::limited(3))
.build()?,
@@ -103,6 +110,8 @@ impl crate::Service for Service {
pusher: base(config)?
.dns_resolver(resolver.resolver.clone())
.connect_timeout(Duration::from_secs(config.pusher_conn_timeout))
.timeout(Duration::from_secs(config.pusher_timeout))
.pool_max_idle_per_host(1)
.pool_idle_timeout(Duration::from_secs(config.pusher_idle_timeout))
.redirect(redirect::Policy::limited(2))
+18 -12
View File
@@ -3,7 +3,7 @@ use std::{fmt::Debug, mem};
use bytes::Bytes;
use conduwuit::{
Err, Error, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err,
error::inspect_debug_log, implement, trace, utils::string::EMPTY,
error::inspect_debug_log, implement, trace,
};
use http::{HeaderValue, header::AUTHORIZATION};
use ipaddress::IPAddress;
@@ -88,9 +88,11 @@ where
let url = request.url().clone();
let method = request.method().clone();
debug!(%method, %url, "Sending request");
debug!(?method, ?url, "Sending request");
match client.execute(request).await {
| Ok(response) => handle_response::<T>(dest, actual, &method, &url, response).await,
| Ok(response) =>
self.handle_response::<T>(dest, actual, &method, &url, response)
.await,
| Err(error) =>
Err(handle_error(actual, &method, &url, error).expect_err("always returns error")),
}
@@ -119,7 +121,9 @@ fn validate_url(&self, url: &Url) -> Result<()> {
Ok(())
}
#[implement(super::Service)]
async fn handle_response<T>(
&self,
dest: &ServerName,
actual: &ActualDest,
method: &Method,
@@ -144,9 +148,9 @@ async fn into_http_response(
) -> Result<http::Response<Bytes>> {
let status = response.status();
trace!(
%status, %method,
request_url = %url,
response_url = %response.url(),
?status, ?method,
request_url = ?url,
response_url = ?response.url(),
"Received response from {}",
actual.string(),
);
@@ -162,7 +166,6 @@ async fn into_http_response(
.expect("http::response::Builder is usable"),
);
// TODO: handle timeout
trace!("Waiting for response body...");
let body = response
.bytes()
@@ -196,9 +199,9 @@ fn handle_error(
debug_warn!("{e:?}");
} else if e.is_redirect() {
debug_error!(
%method,
%url,
final_url = e.url().map(tracing::field::display),
method = ?method,
url = ?url,
final_url = ?e.url(),
"Redirect loop {}: {}",
actual.host,
e,
@@ -286,10 +289,13 @@ where
T: OutgoingRequest + Send,
{
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_11];
const SATIR: SendAccessToken<'_> = SendAccessToken::IfRequired(EMPTY);
let http_request = request
.try_into_http_request::<Vec<u8>>(actual.string().as_str(), SATIR, &VERSIONS)
.try_into_http_request::<Vec<u8>>(
actual.string().as_str(),
SendAccessToken::None,
&VERSIONS,
)
.map_err(|e| err!(BadServerResponse("Invalid destination: {e:?}")))?;
Ok(http_request)
+15
View File
@@ -18,6 +18,7 @@ pub struct Service {
pub server_user: OwnedUserId,
pub admin_alias: OwnedRoomAliasId,
pub turn_secret: String,
pub registration_token: Option<String>,
}
type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries
@@ -40,6 +41,19 @@ impl crate::Service for Service {
},
);
let registration_token = config.registration_token_file.as_ref().map_or_else(
|| config.registration_token.clone(),
|path| {
let Ok(token) = std::fs::read_to_string(path).inspect_err(|e| {
error!("Failed to read the registration token file: {e}");
}) else {
return config.registration_token.clone();
};
Some(token.trim().to_owned())
},
);
Ok(Arc::new(Self {
db,
server: args.server.clone(),
@@ -52,6 +66,7 @@ impl crate::Service for Service {
)
.expect("@conduit:server_name is valid"),
turn_secret,
registration_token,
}))
}
+3 -3
View File
@@ -124,16 +124,16 @@ impl Data {
.next()
.map(string_from_bytes)
.transpose()
.map_err(|e| err!(Database(error!(%mxc, "Content-type is invalid: {e}"))))?;
.map_err(|e| err!(Database(error!(?mxc, "Content-type is invalid: {e}"))))?;
let content_disposition = parts
.next()
.map(Some)
.ok_or_else(|| err!(Database(error!(%mxc, "Media ID in db is invalid."))))?
.ok_or_else(|| err!(Database(error!(?mxc, "Media ID in db is invalid."))))?
.filter(|bytes| !bytes.is_empty())
.map(string_from_bytes)
.transpose()
.map_err(|e| err!(Database(error!(%mxc, "Content-type is invalid: {e}"))))?
.map_err(|e| err!(Database(error!(?mxc, "Content-type is invalid: {e}"))))?
.as_deref()
.map(str::parse)
.transpose()?;
+7 -7
View File
@@ -118,14 +118,14 @@ impl Service {
match self.db.search_mxc_metadata_prefix(mxc).await {
| Ok(keys) => {
for key in keys {
trace!(%mxc, "MXC Key: {key:?}");
debug_info!(%mxc, "Deleting from filesystem");
trace!(?mxc, "MXC Key: {key:?}");
debug_info!(?mxc, "Deleting from filesystem");
if let Err(e) = self.remove_media_file(&key).await {
debug_error!(%mxc, "Failed to remove media file: {e}");
debug_error!(?mxc, "Failed to remove media file: {e}");
}
debug_info!(%mxc, "Deleting from database");
debug_info!(?mxc, "Deleting from database");
self.db.delete_file_mxc(mxc).await;
}
@@ -148,7 +148,7 @@ impl Service {
for mxc in mxcs {
let Ok(mxc) = mxc.as_str().try_into().inspect_err(|e| {
debug_error!(%mxc, "Failed to parse MXC URI from database: {e}");
debug_error!(?mxc, "Failed to parse MXC URI from database: {e}");
}) else {
continue;
};
@@ -210,7 +210,7 @@ impl Service {
let Some(mxc_s) = mxc else {
debug_warn!(
mxc,
?mxc,
"Parsed MXC URL unicode bytes from database but is still invalid"
);
continue;
@@ -256,7 +256,7 @@ impl Service {
let Some(mxc_s) = mxc else {
debug_warn!(
mxc,
?mxc,
"Parsed MXC URL unicode bytes from database but is still invalid"
);
continue;
+2 -2
View File
@@ -71,10 +71,10 @@ async fn request_url_preview(&self, url: &Url) -> Result<UrlPreviewData> {
let client = &self.services.client.url_preview;
let response = client.head(url.as_str()).send().await?;
debug!(%url, "URL preview response headers: {:?}", response.headers());
debug!(?url, "URL preview response headers: {:?}", response.headers());
if let Some(remote_addr) = response.remote_addr() {
debug!(%url, "URL preview response remote address: {:?}", remote_addr);
debug!(?url, "URL preview response remote address: {:?}", remote_addr);
if let Ok(ip) = IPAddress::parse(remote_addr.ip().to_string()) {
if !self.services.client.valid_cidr_range(&ip) {
+2 -2
View File
@@ -247,7 +247,7 @@ async fn handle_location(
) -> Result<FileMeta> {
self.location_request(location).await.map_err(|error| {
err!(Request(NotFound(
debug_warn!(%mxc, user = user.map(tracing::field::display), ?location, ?error, "Fetching media from location failed")
debug_warn!(%mxc, ?user, ?location, ?error, "Fetching media from location failed")
)))
})
}
@@ -320,7 +320,7 @@ fn handle_federation_error(
) -> Error {
let fallback = || {
err!(Request(NotFound(
debug_error!(%mxc, user = user.map(tracing::field::display), server = server.map(tracing::field::display), ?error, "Remote media not found")
debug_error!(%mxc, ?user, ?server, ?error, "Remote media not found")
)))
};
+1 -1
View File
@@ -120,7 +120,7 @@ async fn get_thumbnail_generate(
let mut cursor = std::io::Cursor::new(&mut thumbnail_bytes);
thumbnail
.write_to(&mut cursor, image::ImageFormat::Png)
.map_err(|error| err!(error!(%error, "Error writing PNG thumbnail.")))?;
.map_err(|error| err!(error!(?error, "Error writing PNG thumbnail.")))?;
// Save thumbnail in database so we don't have to generate it again next time
let thumbnail_key = self.db.create_file_metadata(
+3 -3
View File
@@ -677,7 +677,7 @@ async fn populate_userroomid_leftstate_table(services: &Services) -> Result {
shortstatehash_cache.insert(room_id.to_owned(), shortstatehash);
shortstatehash
} else {
warn!(%room_id, %user_id, "room has no shortstatehash");
warn!(?room_id, ?user_id, "room has no shortstatehash");
return Ok((total, fixed, shortstatehash_cache));
};
@@ -698,8 +698,8 @@ async fn populate_userroomid_leftstate_table(services: &Services) -> Result {
},
| Err(_) => {
warn!(
%room_id,
%user_id,
?room_id,
?user_id,
"room cached as left has no leave event for user, removing \
cache entry"
);
+3 -4
View File
@@ -1,8 +1,6 @@
#![type_length_limit = "8192"]
#![allow(refining_impl_trait)]
extern crate conduwuit_core as conduwuit;
extern crate conduwuit_database as database;
mod manager;
mod migrations;
mod service;
@@ -12,7 +10,6 @@ pub mod state;
pub mod account_data;
pub mod admin;
pub mod announcements;
pub mod antispam;
pub mod appservice;
pub mod client;
pub mod config;
@@ -24,7 +21,6 @@ pub mod media;
pub mod moderation;
pub mod presence;
pub mod pusher;
pub mod registration_tokens;
pub mod resolver;
pub mod rooms;
pub mod sending;
@@ -34,6 +30,9 @@ pub mod transaction_ids;
pub mod uiaa;
pub mod users;
extern crate conduwuit_core as conduwuit;
extern crate conduwuit_database as database;
use ctor::{ctor, dtor};
pub(crate) use service::{Args, Dep, Service};
+2 -2
View File
@@ -198,11 +198,11 @@ impl Service {
presence.presence,
PresenceState::Unavailable | PresenceState::Online | PresenceState::Busy
) {
trace!(%user_id, ?presence, "Skipping user");
trace!(?user_id, ?presence, "Skipping user");
continue;
}
trace!(%user_id, ?presence, "Resetting presence to offline");
trace!(?user_id, ?presence, "Resetting presence to offline");
_ = self
.set_presence(
+3 -3
View File
@@ -198,7 +198,7 @@ impl Service {
trace!("Push gateway destination: {dest}");
let http_request = request
.try_into_http_request::<BytesMut>(&dest, SendAccessToken::IfRequired(""), &VERSIONS)
.try_into_http_request::<BytesMut>(&dest, SendAccessToken::None, &VERSIONS)
.map_err(|e| {
err!(BadServerResponse(warn!(
"Failed to find destination {dest} for push gateway: {e}"
@@ -245,7 +245,7 @@ impl Service {
.expect("http::response::Builder is usable"),
);
let body = response.bytes().await?; // TODO: handle timeout
let body = response.bytes().await?;
if !status.is_success() {
debug_warn!("Push gateway response body: {:?}", string_from_bytes(&body));
@@ -288,7 +288,7 @@ impl Service {
let mut notify = None;
let mut tweaks = Vec::new();
if event.room_id().is_none() {
// TODO(hydra): does this matter?
// This only affects v12+ create events
return Ok(());
}
-129
View File
@@ -1,129 +0,0 @@
use std::{sync::Arc, time::SystemTime};
use conduwuit::utils::{
self,
stream::{ReadyExt, TryIgnore},
};
use database::{Database, Deserialized, Json, Map};
use futures::Stream;
use ruma::OwnedUserId;
use serde::{Deserialize, Serialize};
pub(super) struct Data {
registrationtoken_info: Arc<Map>,
}
/// Metadata of a registration token.
#[derive(Debug, Serialize, Deserialize)]
pub struct DatabaseTokenInfo {
/// The admin user who created this token.
pub creator: OwnedUserId,
/// The number of times this token has been used to create an account.
pub uses: u64,
/// When this token will expire, if it expires.
pub expires: Option<TokenExpires>,
}
impl DatabaseTokenInfo {
pub(super) fn new(creator: OwnedUserId, expires: Option<TokenExpires>) -> Self {
Self { creator, uses: 0, expires }
}
/// Determine whether this token info represents a valid token, i.e. one
/// that has not expired according to its [`Self::expires`] property. If
/// [`Self::expires`] is [`None`], this function will always return `true`.
#[must_use]
pub fn is_valid(&self) -> bool {
match self.expires {
| Some(TokenExpires::AfterUses(max_uses)) => self.uses < max_uses,
| Some(TokenExpires::AfterTime(expiry_time)) => {
let now = SystemTime::now();
expiry_time >= now
},
| None => true,
}
}
}
impl std::fmt::Display for DatabaseTokenInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Token created by {} and used {} times. ", &self.creator, self.uses)?;
if let Some(expires) = &self.expires {
write!(f, "{expires}.")?;
} else {
write!(f, "Never expires.")?;
}
Ok(())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum TokenExpires {
AfterUses(u64),
AfterTime(SystemTime),
}
impl std::fmt::Display for TokenExpires {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
| Self::AfterUses(max_uses) => write!(f, "Expires after {max_uses} uses"),
| Self::AfterTime(max_age) => {
let now = SystemTime::now();
let formatted_expiry = utils::time::format(*max_age, "%+");
match max_age.duration_since(now) {
| Ok(duration) => write!(
f,
"Expires in {} ({formatted_expiry})",
utils::time::pretty(duration)
),
| Err(_) => write!(f, "Expired at {formatted_expiry}"),
}
},
}
}
}
impl Data {
pub(super) fn new(db: &Arc<Database>) -> Self {
Self {
registrationtoken_info: db["registrationtoken_info"].clone(),
}
}
/// Associate a registration token with its metadata in the database.
pub(super) fn save_token(&self, token: &str, info: &DatabaseTokenInfo) {
self.registrationtoken_info.raw_put(token, Json(info));
}
/// Delete a registration token.
pub(super) fn revoke_token(&self, token: &str) { self.registrationtoken_info.remove(token); }
/// Look up a registration token's metadata.
pub(super) async fn lookup_token_info(&self, token: &str) -> Option<DatabaseTokenInfo> {
self.registrationtoken_info
.get(token)
.await
.deserialized()
.ok()
}
/// Iterate over all valid tokens and delete expired ones.
pub(super) fn iterate_and_clean_tokens(
&self,
) -> impl Stream<Item = (&str, DatabaseTokenInfo)> + Send + '_ {
self.registrationtoken_info
.stream()
.ignore_err()
.ready_filter_map(|item: (&str, DatabaseTokenInfo)| {
if item.1.is_valid() {
Some(item)
} else {
self.registrationtoken_info.remove(item.0);
None
}
})
}
}
-172
View File
@@ -1,172 +0,0 @@
mod data;
use std::sync::Arc;
use conduwuit::{Err, Result, utils};
use data::Data;
pub use data::{DatabaseTokenInfo, TokenExpires};
use futures::{Stream, StreamExt, stream};
use ruma::OwnedUserId;
use crate::{Dep, config};
const RANDOM_TOKEN_LENGTH: usize = 16;
pub struct Service {
db: Data,
services: Services,
}
struct Services {
config: Dep<config::Service>,
}
/// A validated registration token which may be used to create an account.
#[derive(Debug)]
pub struct ValidToken {
pub token: String,
pub source: ValidTokenSource,
}
impl std::fmt::Display for ValidToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "`{}` --- {}", self.token, &self.source)
}
}
impl PartialEq<str> for ValidToken {
fn eq(&self, other: &str) -> bool { self.token == other }
}
/// The source of a valid database token.
#[derive(Debug)]
pub enum ValidTokenSource {
/// The static token set in the homeserver's config file, which is
/// always valid.
ConfigFile,
/// A database token which has been checked to be valid.
Database(DatabaseTokenInfo),
}
impl std::fmt::Display for ValidTokenSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
| Self::ConfigFile => write!(f, "Token defined in config."),
| Self::Database(info) => info.fmt(f),
}
}
}
impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
db: Data::new(args.db),
services: Services {
config: args.depend::<config::Service>("config"),
},
}))
}
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
}
impl Service {
/// Issue a new registration token and save it in the database.
pub fn issue_token(
&self,
creator: OwnedUserId,
expires: Option<TokenExpires>,
) -> (String, DatabaseTokenInfo) {
let token = utils::random_string(RANDOM_TOKEN_LENGTH);
let info = DatabaseTokenInfo::new(creator, expires);
self.db.save_token(&token, &info);
(token, info)
}
/// Get the registration token set in the config file, if it exists.
pub fn get_config_file_token(&self) -> Option<ValidToken> {
self.services
.config
.registration_token
.clone()
.map(|token| ValidToken {
token,
source: ValidTokenSource::ConfigFile,
})
}
/// Validate a registration token.
pub async fn validate_token(&self, token: String) -> Option<ValidToken> {
// Check the registration token in the config first
if self
.get_config_file_token()
.is_some_and(|valid_token| valid_token == *token)
{
return Some(ValidToken {
token,
source: ValidTokenSource::ConfigFile,
});
}
// Now check the database
if let Some(token_info) = self.db.lookup_token_info(&token).await
&& token_info.is_valid()
{
return Some(ValidToken {
token,
source: ValidTokenSource::Database(token_info),
});
}
// Otherwise it's not valid
None
}
/// Mark a valid token as having been used to create a new account.
pub fn mark_token_as_used(&self, ValidToken { token, source }: ValidToken) {
match source {
| ValidTokenSource::ConfigFile => {
// we don't track uses of the config file token, do nothing
},
| ValidTokenSource::Database(mut info) => {
info.uses = info.uses.saturating_add(1);
self.db.save_token(&token, &info);
},
}
}
/// Try to revoke a valid token.
///
/// Note that some tokens (like the one set in the config file) cannot be
/// revoked.
pub fn revoke_token(&self, ValidToken { token, source }: ValidToken) -> Result {
match source {
| ValidTokenSource::ConfigFile => {
// the config file token cannot be revoked
Err!(
"The token set in the config file cannot be revoked. Edit the config file \
to change it."
)
},
| ValidTokenSource::Database(_) => {
self.db.revoke_token(&token);
Ok(())
},
}
}
/// Iterate over all valid registration tokens.
pub fn iterate_tokens(&self) -> impl Stream<Item = ValidToken> + Send + '_ {
let db_tokens = self
.db
.iterate_and_clean_tokens()
.map(|(token, info)| ValidToken {
token: token.to_owned(),
source: ValidTokenSource::Database(info),
});
stream::iter(self.get_config_file_token()).chain(db_tokens)
}
}
+6 -6
View File
@@ -151,7 +151,7 @@ async fn get_auth_chain_outer(
let auth_chain = self.get_auth_chain_inner(room_id, event_id).await?;
self.cache_auth_chain_vec(vec![shortid], auth_chain.as_slice());
debug!(
%event_id,
?event_id,
elapsed = ?started.elapsed(),
"Cache missed event"
);
@@ -188,18 +188,18 @@ async fn get_auth_chain_inner(
let mut found = HashSet::new();
while let Some(event_id) = todo.pop_front() {
trace!(%event_id, "processing auth event");
trace!(?event_id, "processing auth event");
match self.services.timeline.get_pdu(&event_id).await {
| Err(e) => {
debug_error!(%event_id, ?e, "Could not find pdu mentioned in auth events");
debug_error!(?event_id, ?e, "Could not find pdu mentioned in auth events");
},
| Ok(pdu) => {
if let Some(claimed_room_id) = pdu.room_id.clone() {
if claimed_room_id != *room_id {
return Err!(Request(Forbidden(error!(
%event_id,
%room_id,
?event_id,
?room_id,
wrong_room_id = ?pdu.room_id.unwrap(),
"auth event for incorrect room"
))));
@@ -214,7 +214,7 @@ async fn get_auth_chain_inner(
.await;
if found.insert(sauthevent) {
trace!(%event_id, ?auth_event, "adding auth event to processing queue");
trace!(?event_id, ?auth_event, "adding auth event to processing queue");
todo.push_back(auth_event.clone());
}
+3 -3
View File
@@ -104,9 +104,9 @@ fn check_room_id<Pdu: Event>(room_id: &RoomId, pdu: &Pdu) -> Result {
.is_some_and(|claimed_room_id| claimed_room_id != room_id)
{
return Err!(Request(InvalidParam(error!(
pdu_event_id = %pdu.event_id(),
pdu_room_id = pdu.room_id().map(tracing::field::display),
%room_id,
pdu_event_id = ?pdu.event_id(),
pdu_room_id = ?pdu.room_id(),
?room_id,
"Found event from room in room",
))));
}

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