mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-04-01 16:55:53 +00:00
Compare commits
65 Commits
v0.5.1
...
ginger/upd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5b11af3e8 | ||
|
|
71a26e433f | ||
|
|
d353446488 | ||
|
|
77e8fd1744 | ||
|
|
7fa7b129c0 | ||
|
|
247bc15659 | ||
|
|
88a35e139d | ||
|
|
37574ef5cc | ||
|
|
1c816850ed | ||
|
|
3483059e1c | ||
|
|
d865dd4454 | ||
|
|
adc7c5ac49 | ||
|
|
112403e470 | ||
|
|
ea0a124981 | ||
|
|
bf205fb13c | ||
|
|
9a6408f98f | ||
|
|
ca77970ff3 | ||
|
|
42f4ec34cd | ||
|
|
ecf74bb31f | ||
|
|
8c716befdc | ||
|
|
a8209d1dd9 | ||
|
|
9552dd7485 | ||
|
|
88c84f221f | ||
|
|
a10bd71945 | ||
|
|
2f11bf4d74 | ||
|
|
1e8748d1a0 | ||
|
|
70ef6e4211 | ||
|
|
212c1bc14d | ||
|
|
ce46b6869f | ||
|
|
a18b8254d0 | ||
|
|
279f7cbfe4 | ||
|
|
006c57face | ||
|
|
d52e0dc014 | ||
|
|
4b873a1b95 | ||
|
|
76865e6f91 | ||
|
|
99f16c2dfc | ||
|
|
5ac82f36f3 | ||
|
|
c249dd992e | ||
|
|
0956779802 | ||
|
|
a83c1f1513 | ||
|
|
8b5e4d8fe1 | ||
|
|
7502a944d7 | ||
|
|
aed15f246a | ||
|
|
27d6604d14 | ||
|
|
1c7bd2f6fa | ||
|
|
56d7099011 | ||
|
|
bc426e1bfc | ||
|
|
6c61b3ec5b | ||
|
|
9d9d1170b6 | ||
|
|
7be20abcad | ||
|
|
078275964c | ||
|
|
bf200ad12d | ||
|
|
41e628892d | ||
|
|
44851ee6a2 | ||
|
|
a7e6e6e83f | ||
|
|
8a561fcd3a | ||
|
|
25c305f473 | ||
|
|
c900350164 | ||
|
|
c565e6ffbc | ||
|
|
442f887c98 | ||
|
|
03220845e5 | ||
|
|
f8c1e9bcde | ||
|
|
21324b748f | ||
|
|
b7bf36443b | ||
|
|
d72192aa32 |
@@ -59,10 +59,9 @@ jobs:
|
||||
# Aggressive GC since cache restores don't increment counter
|
||||
echo "CARGO_INCREMENTAL_GC_TRIGGER=5" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Rust nightly
|
||||
- name: Setup Rust
|
||||
uses: ./.forgejo/actions/setup-rust
|
||||
with:
|
||||
rust-version: nightly
|
||||
github-token: ${{ secrets.GH_PUBLIC_RO }}
|
||||
|
||||
- name: Get package version and component
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
name: Renovate
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/renovatebot/renovate:42.11.0@sha256:656c1e5b808279eac16c37b89562fb4c699e02fc7e219244f4a1fc2f0a7ce367
|
||||
image: ghcr.io/renovatebot/renovate:42.70.2@sha256:3c2ac1b94fa92ef2fa4d1a0493f2c3ba564454720a32fdbcac2db2846ff1ee47
|
||||
options: --tmpfs /tmp:exec
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
persist-credentials: true
|
||||
token: ${{ secrets.FORGEJO_TOKEN }}
|
||||
|
||||
- uses: https://github.com/cachix/install-nix-action@7ab6e7fd29da88e74b1e314a4ae9ac6b5cda3801 # v31.8.0
|
||||
- uses: https://github.com/cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ repos:
|
||||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.40.0
|
||||
rev: v1.41.0
|
||||
hooks:
|
||||
- id: typos
|
||||
- id: typos
|
||||
@@ -31,7 +31,7 @@ repos:
|
||||
stages: [commit-msg]
|
||||
|
||||
- repo: https://github.com/crate-ci/committed
|
||||
rev: v1.1.8
|
||||
rev: v1.1.9
|
||||
hooks:
|
||||
- id: committed
|
||||
|
||||
|
||||
@@ -24,3 +24,4 @@ extend-ignore-re = [
|
||||
"continuwuity" = "continuwuity"
|
||||
"continuwity" = "continuwuity"
|
||||
"execuse" = "execuse"
|
||||
"oltp" = "OTLP"
|
||||
|
||||
@@ -4,7 +4,7 @@ # Continuwuity 0.5.0 (2025-12-30)
|
||||
|
||||
## Features
|
||||
|
||||
- Enabled the OLTP exporter in default builds, and allow configuring the exporter protocol. (@Jade). (#1251)
|
||||
- Enabled the OTLP exporter in default builds, and allow configuring the exporter protocol. (@Jade). (#1251)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
|
||||
58
Cargo.lock
generated
58
Cargo.lock
generated
@@ -1632,6 +1632,16 @@ 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"
|
||||
@@ -1750,7 +1760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2405,7 +2415,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.1",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -2982,6 +2992,16 @@ 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"
|
||||
@@ -3121,7 +3141,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3749,7 +3769,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.1",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -3786,9 +3806,9 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.1",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4065,11 +4085,13 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
|
||||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.10.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"draupnir-antispam",
|
||||
"js_int",
|
||||
"js_option",
|
||||
"meowlnir-antispam",
|
||||
"ruma-appservice-api",
|
||||
"ruma-client-api",
|
||||
"ruma-common",
|
||||
@@ -4085,7 +4107,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-appservice-api"
|
||||
version = "0.10.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4097,7 +4119,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.18.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"assign",
|
||||
@@ -4120,7 +4142,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.13.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"base64 0.22.1",
|
||||
@@ -4152,7 +4174,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.28.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"indexmap",
|
||||
@@ -4177,7 +4199,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"headers",
|
||||
@@ -4199,7 +4221,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.9.5"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"thiserror 2.0.17",
|
||||
@@ -4208,7 +4230,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-identity-service-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4218,7 +4240,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-macros"
|
||||
version = "0.13.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro-crate",
|
||||
@@ -4233,7 +4255,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-push-gateway-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4245,7 +4267,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.15.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek",
|
||||
@@ -4325,7 +4347,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
139
Cargo.toml
139
Cargo.toml
@@ -1,26 +1,17 @@
|
||||
#cargo-features = ["profile-rustflags"]
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["src/*", "xtask/*"]
|
||||
default-members = ["src/*"]
|
||||
|
||||
[workspace.package]
|
||||
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"
|
||||
authors = ["Continuwuity Team and contributors <team@continuwuity.org>"]
|
||||
description = "A Matrix homeserver written in Rust, the official continuation of the conduwuit homeserver."
|
||||
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]
|
||||
@@ -33,11 +24,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]
|
||||
@@ -96,13 +87,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]
|
||||
@@ -149,10 +140,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]
|
||||
@@ -188,18 +179,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
|
||||
@@ -229,13 +220,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]
|
||||
@@ -247,15 +238,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]
|
||||
@@ -280,18 +271,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
|
||||
@@ -310,9 +301,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
|
||||
@@ -351,7 +342,7 @@ version = "0.1.2"
|
||||
# Used for matrix spec type definitions and helpers
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
||||
rev = "27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
rev = "79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
features = [
|
||||
"compat",
|
||||
"rand",
|
||||
@@ -381,13 +372,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]
|
||||
@@ -395,11 +386,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]
|
||||
@@ -458,16 +449,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"
|
||||
@@ -491,9 +482,9 @@ default-features = false
|
||||
version = "0.1.2"
|
||||
default-features = false
|
||||
features = [
|
||||
"static",
|
||||
"gcc",
|
||||
"light",
|
||||
"static",
|
||||
"gcc",
|
||||
"light",
|
||||
]
|
||||
|
||||
[workspace.dependencies.rustyline-async]
|
||||
@@ -848,6 +839,8 @@ 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 }
|
||||
|
||||
1
changelog.d/+6de5f7b2.misc.md
Normal file
1
changelog.d/+6de5f7b2.misc.md
Normal file
@@ -0,0 +1 @@
|
||||
The `console` feature is now enabled by default, allowing the server console to be used for running admin commands directly.
|
||||
1
changelog.d/+f4a756d9.feature.md
Normal file
1
changelog.d/+f4a756d9.feature.md
Normal file
@@ -0,0 +1 @@
|
||||
Certain potentially dangerous admin commands are now restricted to only be usable in the admin room and server console.
|
||||
1
changelog.d/1253.feature
Normal file
1
changelog.d/1253.feature
Normal file
@@ -0,0 +1 @@
|
||||
Implemented a configuration defined admin list independent of the admin room. (@Terryiscool160).
|
||||
1
changelog.d/1257.bugfix
Normal file
1
changelog.d/1257.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fixed unreliable room summary fetching and improved error messages. Contributed by @nex.
|
||||
2
changelog.d/1261.bugfix
Normal file
2
changelog.d/1261.bugfix
Normal file
@@ -0,0 +1,2 @@
|
||||
Client requested timeout parameter is now applied to e2ee key lookups and claims. Related federation requests are now
|
||||
also concurrent. Contributed by @nex.
|
||||
2
changelog.d/1263.feature
Normal file
2
changelog.d/1263.feature
Normal file
@@ -0,0 +1,2 @@
|
||||
Added support for invite and join anti-spam via Draupnir and Meowlnir, similar to that of synapse-http-antispam.
|
||||
Contributed by @nex.
|
||||
1
changelog.d/1266.feature
Normal file
1
changelog.d/1266.feature
Normal file
@@ -0,0 +1 @@
|
||||
Implemented account locking functionality, to complement user suspension. Contributed by @nex.
|
||||
1
changelog.d/1271.feature
Normal file
1
changelog.d/1271.feature
Normal file
@@ -0,0 +1 @@
|
||||
Added admin command to forcefully log out all of a user's existing sessions. Contributed by @nex.
|
||||
1
changelog.d/1272.feature
Normal file
1
changelog.d/1272.feature
Normal file
@@ -0,0 +1 @@
|
||||
Implemented toggling the ability for an account to log in without mutating any of its data. Contributed by @nex.
|
||||
1
changelog.d/783.feature.md
Normal file
1
changelog.d/783.feature.md
Normal file
@@ -0,0 +1 @@
|
||||
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**.
|
||||
@@ -421,7 +421,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` or `registration_token_file`.
|
||||
# `registration_token`.
|
||||
#
|
||||
#allow_registration = false
|
||||
|
||||
@@ -452,22 +452,13 @@
|
||||
# `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
|
||||
# to true to allow open registration without any conditions.
|
||||
#
|
||||
# YOU NEED TO EDIT THIS OR USE registration_token_file.
|
||||
# If you do not want to set a static token, the `!admin token` commands
|
||||
# may also be used to manage registration tokens.
|
||||
#
|
||||
# example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
|
||||
#
|
||||
#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
|
||||
@@ -1590,6 +1581,18 @@
|
||||
#
|
||||
#admin_room_tag = "m.server_notice"
|
||||
|
||||
# A list of Matrix IDs that are qualified as server admins.
|
||||
#
|
||||
# Any Matrix IDs within this list are regarded as an admin
|
||||
# regardless of whether they are in the admin room or not
|
||||
#
|
||||
#admins_list = []
|
||||
|
||||
# Defines whether those within the admin room are added to the
|
||||
# admins_list.
|
||||
#
|
||||
#admins_from_room = true
|
||||
|
||||
# Sentry.io crash/panic reporting, performance monitoring/metrics, etc.
|
||||
# This is NOT enabled by default.
|
||||
#
|
||||
@@ -1635,7 +1638,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
|
||||
@@ -1911,3 +1914,41 @@
|
||||
# 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 =
|
||||
|
||||
@@ -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.3.7
|
||||
ENV LDDTREE_VERSION=0.4.0
|
||||
# renovate: datasource=crate depName=timelord-cli
|
||||
ENV TIMELORD_VERSION=3.0.1
|
||||
|
||||
|
||||
@@ -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.3.7
|
||||
ENV LDDTREE_VERSION=0.4.0
|
||||
|
||||
# Install unpackaged tools
|
||||
RUN <<EOF
|
||||
|
||||
@@ -11,10 +11,10 @@ ### Use a registry
|
||||
|
||||
| Registry | Image | Notes |
|
||||
| --------------- | --------------------------------------------------------------- | -----------------------|
|
||||
| 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
|
||||
| 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. |
|
||||
|
||||
Use
|
||||
|
||||
@@ -24,6 +24,15 @@ ### Use a registry
|
||||
|
||||
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
|
||||
@@ -49,7 +58,7 @@ ### Run
|
||||
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.
|
||||
@@ -158,8 +167,19 @@ # Build for the current platform and load into the local Docker daemon
|
||||
# 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 optimized for the current CPU
|
||||
# docker buildx build --load --tag continuwuity:latest --build-arg TARGET_CPU=native -f docker/Dockerfile .
|
||||
# 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 .
|
||||
```
|
||||
|
||||
Refer to the Docker Buildx documentation for more advanced build options.
|
||||
@@ -198,5 +218,3 @@ ### Use Traefik as Proxy
|
||||
## Voice communication
|
||||
|
||||
See the [TURN](../turn.md) page.
|
||||
|
||||
[nix-buildlayeredimage]: https://ryantm.github.io/nixpkgs/builders/images/dockertools/#ssec-pkgs-dockerTools-buildLayeredImage
|
||||
|
||||
@@ -8,29 +8,39 @@ # Generic deployment documentation
|
||||
|
||||
## Installing Continuwuity
|
||||
|
||||
### Static prebuilt binary
|
||||
### Prebuilt binary
|
||||
|
||||
You may simply download the binary that fits your machine architecture (x86_64
|
||||
or aarch64). Run `uname -m` to see what you need.
|
||||
Download the binary for your architecture (x86_64 or aarch64) -
|
||||
run the `uname -m` to check which you need.
|
||||
|
||||
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.
|
||||
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 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.
|
||||
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.
|
||||
|
||||
These binaries have jemalloc and io_uring statically linked and included with
|
||||
them, so no additional dynamic dependencies need to be installed.
|
||||
The binaries require jemalloc and io_uring on the host system. Currently
|
||||
we can't cross-build static binaries - contributions are welcome here.
|
||||
|
||||
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.
|
||||
#### 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.
|
||||
|
||||
### Compiling
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ ### Log Levels
|
||||
```rs
|
||||
// Good
|
||||
error!(
|
||||
error = %err,
|
||||
error = ?err,
|
||||
room_id = %room_id,
|
||||
"Failed to send event to room"
|
||||
);
|
||||
@@ -264,7 +264,7 @@ ### Code Comments
|
||||
warn!(
|
||||
destination = %destination,
|
||||
attempt = attempt,
|
||||
error = %err,
|
||||
error = ?err,
|
||||
retry_delay_ms = retry_delay.as_millis(),
|
||||
"Federation request failed, retrying"
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ # Command-Line Help for `continuwuity`
|
||||
|
||||
## `continuwuity`
|
||||
|
||||
a very cool Matrix chat homeserver written in Rust
|
||||
A Matrix homeserver written in Rust, the official continuation of the conduwuit homeserver.
|
||||
|
||||
**Usage:** `continuwuity [OPTIONS]`
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
Name: continuwuity
|
||||
Version: {{{ git_repo_version }}}
|
||||
Release: 1%{?dist}
|
||||
Summary: Very cool Matrix chat homeserver written in Rust
|
||||
Summary: A Matrix homeserver written in Rust.
|
||||
|
||||
License: Apache-2.0 AND MIT
|
||||
|
||||
@@ -23,7 +23,7 @@ Requires: glibc
|
||||
Requires: libstdc++
|
||||
|
||||
%global _description %{expand:
|
||||
A cool hard fork of Conduit, a Matrix homeserver written in Rust}
|
||||
A Matrix homeserver written in Rust, the official continuation of the conduwuit homeserver.}
|
||||
|
||||
%description %{_description}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
[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
|
||||
|
||||
@@ -2,10 +2,17 @@
|
||||
use conduwuit::Result;
|
||||
|
||||
use crate::{
|
||||
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,
|
||||
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},
|
||||
};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -19,6 +26,10 @@ 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),
|
||||
@@ -48,19 +59,36 @@ pub enum AdminCommand {
|
||||
Query(QueryCommand),
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, name = "command")]
|
||||
#[tracing::instrument(skip_all, name = "command", level = "info")]
|
||||
pub(super) async fn process(command: AdminCommand, context: &Context<'_>) -> Result {
|
||||
use AdminCommand::*;
|
||||
|
||||
match command {
|
||||
| Appservices(command) => appservice::process(command, context).await,
|
||||
| Appservices(command) => {
|
||||
// appservice commands are all restricted
|
||||
context.bail_restricted()?;
|
||||
appservice::process(command, context).await
|
||||
},
|
||||
| Media(command) => media::process(command, context).await,
|
||||
| Users(command) => user::process(command, context).await,
|
||||
| Users(command) => {
|
||||
// user commands are all restricted
|
||||
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,
|
||||
| Debug(command) => debug::process(command, context).await,
|
||||
| Query(command) => query::process(command, context).await,
|
||||
| Query(command) => {
|
||||
// query commands are all restricted
|
||||
context.bail_restricted()?;
|
||||
query::process(command, context).await
|
||||
},
|
||||
| Check(command) => check::process(command, context).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{fmt, time::SystemTime};
|
||||
|
||||
use conduwuit::Result;
|
||||
use conduwuit::{Err, Result};
|
||||
use conduwuit_service::Services;
|
||||
use futures::{
|
||||
Future, FutureExt, TryFutureExt,
|
||||
@@ -8,6 +8,7 @@
|
||||
lock::Mutex,
|
||||
};
|
||||
use ruma::{EventId, UserId};
|
||||
use service::admin::InvocationSource;
|
||||
|
||||
pub(crate) struct Context<'a> {
|
||||
pub(crate) services: &'a Services,
|
||||
@@ -16,6 +17,7 @@ pub(crate) struct Context<'a> {
|
||||
pub(crate) reply_id: Option<&'a EventId>,
|
||||
pub(crate) sender: Option<&'a UserId>,
|
||||
pub(crate) output: Mutex<BufWriter<Vec<u8>>>,
|
||||
pub(crate) source: InvocationSource,
|
||||
}
|
||||
|
||||
impl Context<'_> {
|
||||
@@ -43,4 +45,22 @@ pub(crate) fn sender_or_service_user(&self) -> &UserId {
|
||||
self.sender
|
||||
.unwrap_or_else(|| self.services.globals.server_user.as_ref())
|
||||
}
|
||||
|
||||
/// Returns an Err if the [`Self::source`] of this context does not allow
|
||||
/// restricted commands to be executed.
|
||||
///
|
||||
/// This is intended to be placed at the start of restricted commands'
|
||||
/// implementations, like so:
|
||||
///
|
||||
/// ```ignore
|
||||
/// self.bail_restricted()?;
|
||||
/// // actual command impl
|
||||
/// ```
|
||||
pub(crate) fn bail_restricted(&self) -> Result {
|
||||
if self.source.allows_restricted() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err!("This command can only be used in the admin room.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +291,8 @@ pub(super) async fn get_remote_pdu(
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
let room_id = self.services.rooms.alias.resolve(&room).await?;
|
||||
let room_state: Vec<Raw<AnyStateEvent>> = self
|
||||
.services
|
||||
@@ -417,27 +419,6 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
|
||||
Err!("No log level was specified.")
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn sign_json(&self) -> Result {
|
||||
if self.body.len() < 2
|
||||
|| !self.body[0].trim().starts_with("```")
|
||||
|| self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Err!("Expected code block in command body. Add --help for details.");
|
||||
}
|
||||
|
||||
let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
| Err(e) => return Err!("Invalid json: {e}"),
|
||||
| Ok(mut value) => {
|
||||
self.services.server_keys.sign_json(&mut value)?;
|
||||
let json_text = serde_json::to_string_pretty(&value)?;
|
||||
write!(self, "{json_text}")
|
||||
},
|
||||
}
|
||||
.await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn verify_json(&self) -> Result {
|
||||
if self.body.len() < 2
|
||||
@@ -475,8 +456,10 @@ pub(super) async fn verify_pdu(&self, event_id: OwnedEventId) -> Result {
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
#[tracing::instrument(skip(self))]
|
||||
#[tracing::instrument(skip(self), level = "info")]
|
||||
pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
@@ -500,8 +483,10 @@ pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
#[tracing::instrument(skip(self))]
|
||||
#[tracing::instrument(skip(self), level = "info")]
|
||||
pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
@@ -525,13 +510,15 @@ pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
#[tracing::instrument(skip(self))]
|
||||
#[tracing::instrument(skip(self), level = "info")]
|
||||
pub(super) async fn force_set_room_state_from_server(
|
||||
&self,
|
||||
room_id: OwnedRoomId,
|
||||
server_name: OwnedServerName,
|
||||
at_event: Option<OwnedEventId>,
|
||||
) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
|
||||
@@ -47,9 +47,9 @@ pub enum DebugCommand {
|
||||
shorteventid: ShortEventId,
|
||||
},
|
||||
|
||||
/// - Attempts to retrieve a PDU from a remote server. Inserts it into our
|
||||
/// database/timeline if found and we do not have this PDU already
|
||||
/// (following normal event auth rules, handles it as an incoming PDU).
|
||||
/// - Attempts to retrieve a PDU from a remote server. **Does not** insert
|
||||
/// it into the database
|
||||
/// or persist it anywhere.
|
||||
GetRemotePdu {
|
||||
/// An event ID (a $ followed by the base64 reference hash)
|
||||
event_id: OwnedEventId,
|
||||
@@ -125,12 +125,6 @@ pub enum DebugCommand {
|
||||
reset: bool,
|
||||
},
|
||||
|
||||
/// - Sign JSON blob
|
||||
///
|
||||
/// This command needs a JSON blob provided in a Markdown code block below
|
||||
/// the command.
|
||||
SignJson,
|
||||
|
||||
/// - Verify JSON signatures
|
||||
///
|
||||
/// This command needs a JSON blob provided in a Markdown code block below
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn disable_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
self.bail_restricted()?;
|
||||
self.services.rooms.metadata.disable_room(&room_id, true);
|
||||
self.write_str("Room disabled.").await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn enable_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
self.bail_restricted()?;
|
||||
self.services.rooms.metadata.disable_room(&room_id, false);
|
||||
self.write_str("Room enabled.").await
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ pub(super) async fn delete(
|
||||
mxc: Option<OwnedMxcUri>,
|
||||
event_id: Option<OwnedEventId>,
|
||||
) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if event_id.is_some() && mxc.is_some() {
|
||||
return Err!("Please specify either an MXC or an event ID, not both.",);
|
||||
}
|
||||
@@ -176,6 +178,8 @@ pub(super) async fn delete(
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_list(&self) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if self.body.len() < 2
|
||||
|| !self.body[0].trim().starts_with("```")
|
||||
|| self.body.last().unwrap_or(&"").trim() != "```"
|
||||
@@ -231,6 +235,8 @@ pub(super) async fn delete_past_remote_media(
|
||||
after: bool,
|
||||
yes_i_want_to_delete_local_media: bool,
|
||||
) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if before && after {
|
||||
return Err!("Please only pick one argument, --before or --after.",);
|
||||
}
|
||||
@@ -273,6 +279,8 @@ pub(super) async fn delete_all_from_server(
|
||||
server_name: OwnedServerName,
|
||||
yes_i_want_to_delete_local_media: bool,
|
||||
) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if server_name == self.services.globals.server_name() && !yes_i_want_to_delete_local_media {
|
||||
return Err!("This command only works for remote media by default.",);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
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;
|
||||
|
||||
@@ -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")]
|
||||
#[tracing::instrument(skip_all, name = "admin", level = "info")]
|
||||
async fn handle_command(services: Arc<Services>, command: CommandInput) -> ProcessorResult {
|
||||
AssertUnwindSafe(Box::pin(process_command(services, &command)))
|
||||
.catch_unwind()
|
||||
@@ -59,6 +59,7 @@ async fn process_command(services: Arc<Services>, input: &CommandInput) -> Proce
|
||||
reply_id: input.reply_id.as_deref(),
|
||||
sender: input.sender.as_deref(),
|
||||
output: BufWriter::new(Vec::new()).into(),
|
||||
source: input.source,
|
||||
};
|
||||
|
||||
let (result, mut logs) = process(&context, command, &args).await;
|
||||
|
||||
@@ -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}"
|
||||
);
|
||||
|
||||
@@ -24,6 +24,8 @@ pub(super) async fn uptime(&self) -> Result {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn show_config(&self) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
self.write_str(&format!("{}", *self.services.server.config))
|
||||
.await
|
||||
}
|
||||
@@ -118,6 +120,8 @@ pub(super) async fn list_backups(&self) -> Result {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn backup_database(&self) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
let db = Arc::clone(&self.services.db);
|
||||
let result = self
|
||||
.services
|
||||
@@ -144,6 +148,8 @@ pub(super) async fn admin_notice(&self, message: Vec<String>) -> Result {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn reload_mods(&self) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
self.services.server.reload()?;
|
||||
|
||||
self.write_str("Reloading server...").await
|
||||
@@ -168,6 +174,8 @@ pub(super) async fn restart(&self, force: bool) -> Result {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn shutdown(&self) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
warn!("shutdown command");
|
||||
self.services.server.shutdown()?;
|
||||
|
||||
|
||||
76
src/admin/token/commands.rs
Normal file
76
src/admin/token/commands.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
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
src/admin/token/mod.rs
Normal file
51
src/admin/token/mod.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
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,
|
||||
}
|
||||
@@ -238,6 +238,7 @@ 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 {
|
||||
@@ -262,6 +263,7 @@ 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 {
|
||||
@@ -278,7 +280,12 @@ pub(super) async fn unsuspend(&self, user_id: String) -> Result {
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn reset_password(&self, username: String, password: Option<String>) -> Result {
|
||||
pub(super) async fn reset_password(
|
||||
&self,
|
||||
logout: bool,
|
||||
username: String,
|
||||
password: Option<String>,
|
||||
) -> Result {
|
||||
let user_id = parse_local_user_id(self.services, &username)?;
|
||||
|
||||
if user_id == self.services.globals.server_user {
|
||||
@@ -301,7 +308,18 @@ pub(super) async fn reset_password(&self, username: String, password: Option<Str
|
||||
write!(self, "Successfully reset the password for user {user_id}: `{new_password}`")
|
||||
},
|
||||
}
|
||||
.await
|
||||
.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(())
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
@@ -461,9 +479,11 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
);
|
||||
}
|
||||
|
||||
let Ok(admin_room) = self.services.admin.get_admin_room().await else {
|
||||
return Err!("There is not an admin room to check for server admins.",);
|
||||
};
|
||||
let server_admins = self.services.admin.get_admins().await;
|
||||
|
||||
if server_admins.is_empty() {
|
||||
return Err!("There are no admins set for this server.");
|
||||
}
|
||||
|
||||
let (room_id, servers) = self
|
||||
.services
|
||||
@@ -482,15 +502,6 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
return Err!("We are not joined in this room.");
|
||||
}
|
||||
|
||||
let server_admins: Vec<_> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.active_local_users_in_room(&admin_room)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
@@ -583,9 +594,11 @@ pub(super) async fn force_join_all_local_users(
|
||||
);
|
||||
}
|
||||
|
||||
let Ok(admin_room) = self.services.admin.get_admin_room().await else {
|
||||
return Err!("There is not an admin room to check for server admins.",);
|
||||
};
|
||||
let server_admins = self.services.admin.get_admins().await;
|
||||
|
||||
if server_admins.is_empty() {
|
||||
return Err!("There are no admins set for this server.");
|
||||
}
|
||||
|
||||
let (room_id, servers) = self
|
||||
.services
|
||||
@@ -604,15 +617,6 @@ pub(super) async fn force_join_all_local_users(
|
||||
return Err!("We are not joined in this room.");
|
||||
}
|
||||
|
||||
let server_admins: Vec<_> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.active_local_users_in_room(&admin_room)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
@@ -988,3 +992,113 @@ 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
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ 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
|
||||
@@ -59,6 +62,18 @@ 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
|
||||
@@ -81,6 +96,42 @@ 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,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
[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
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
///
|
||||
/// 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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register_available", level = "info")]
|
||||
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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register", level = "info")]
|
||||
pub(crate) async fn register_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -179,13 +179,18 @@ pub(crate) async fn register_route(
|
||||
},
|
||||
}
|
||||
|
||||
return Err!(Request(Forbidden("Registration has been disabled.")));
|
||||
return Err!(Request(Forbidden(
|
||||
"This server is not accepting registrations at this time."
|
||||
)));
|
||||
}
|
||||
|
||||
if is_guest
|
||||
&& (!services.config.allow_guest_registration
|
||||
|| (services.config.allow_registration
|
||||
&& services.globals.registration_token.is_some()))
|
||||
&& services
|
||||
.registration_tokens
|
||||
.get_config_file_token()
|
||||
.is_some()))
|
||||
{
|
||||
info!(
|
||||
"Guest registration disabled / registration enabled with token configured, \
|
||||
@@ -203,7 +208,9 @@ 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("Registration is temporarily disabled.")));
|
||||
return Err!(Request(Forbidden(
|
||||
"This server is not accepting registrations at this time."
|
||||
)));
|
||||
}
|
||||
|
||||
let user_id = match (body.username.as_ref(), is_guest) {
|
||||
@@ -301,7 +308,13 @@ pub(crate) async fn register_route(
|
||||
let skip_auth = body.appservice_info.is_some() || is_guest;
|
||||
|
||||
// Populate required UIAA flows
|
||||
if services.globals.registration_token.is_some() {
|
||||
if services
|
||||
.registration_tokens
|
||||
.iterate_tokens()
|
||||
.next()
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
// Registration token required
|
||||
uiaainfo.flows.push(AuthFlow {
|
||||
stages: vec![AuthType::RegistrationToken],
|
||||
@@ -323,7 +336,19 @@ pub(crate) async fn register_route(
|
||||
}
|
||||
|
||||
if uiaainfo.flows.is_empty() && !skip_auth {
|
||||
// No registration token necessary, but clients must still go through the flow
|
||||
// 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
|
||||
uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
|
||||
completed: Vec::new(),
|
||||
@@ -603,7 +628,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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "change_password", level = "info")]
|
||||
pub(crate) async fn change_password_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -727,7 +752,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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "deactivate", level = "info")]
|
||||
pub(crate) async fn deactivate_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -846,19 +871,20 @@ 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
|
||||
///
|
||||
/// Currently does not have any ratelimiting, and this isn't very practical as
|
||||
/// there is only one registration token allowed.
|
||||
/// Checks if the provided registration token is valid at the time of checking.
|
||||
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> {
|
||||
let Some(reg_token) = services.globals.registration_token.clone() else {
|
||||
return Err!(Request(Forbidden("Server does not allow token registration")));
|
||||
};
|
||||
// TODO: ratelimit this pretty heavily
|
||||
|
||||
Ok(check_registration_token_validity::v1::Response { valid: reg_token == body.token })
|
||||
let valid = services
|
||||
.registration_tokens
|
||||
.validate_token(body.token.clone())
|
||||
.await
|
||||
.is_some();
|
||||
|
||||
Ok(check_registration_token_validity::v1::Response { valid })
|
||||
}
|
||||
|
||||
/// Runs through all the deactivation steps:
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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.")));
|
||||
}
|
||||
|
||||
|
||||
@@ -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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "update_device", level = "debug")]
|
||||
pub(crate) async fn update_device_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
/// Lists the public rooms on this server.
|
||||
///
|
||||
/// - Rooms are ordered by the number of joined members
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms", level = "info")]
|
||||
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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms", level = "info")]
|
||||
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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "room_directory", level = "info")]
|
||||
pub(crate) async fn set_room_visibility_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Error, Result, debug, debug_warn, err, result::NotFound, utils};
|
||||
use conduwuit::{
|
||||
Err, Error, Result, debug, debug_warn, err,
|
||||
result::NotFound,
|
||||
utils,
|
||||
utils::{IterStream, stream::WidebandExt},
|
||||
};
|
||||
use conduwuit_service::{Services, users::parse_master_key};
|
||||
use futures::{StreamExt, stream::FuturesUnordered};
|
||||
use ruma::{
|
||||
@@ -44,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}"
|
||||
);
|
||||
@@ -86,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"
|
||||
@@ -134,6 +142,7 @@ pub(crate) async fn get_keys_route(
|
||||
&body.device_keys,
|
||||
|u| u == sender_user,
|
||||
true, // Always allow local users to see device names of other local users
|
||||
body.timeout.unwrap_or(Duration::from_secs(10)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -145,7 +154,12 @@ pub(crate) async fn claim_keys_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<claim_keys::v3::Request>,
|
||||
) -> Result<claim_keys::v3::Response> {
|
||||
claim_keys_helper(&services, &body.one_time_keys).await
|
||||
claim_keys_helper(
|
||||
&services,
|
||||
&body.one_time_keys,
|
||||
body.timeout.unwrap_or(Duration::from_secs(10)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/keys/device_signing/upload`
|
||||
@@ -324,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;
|
||||
};
|
||||
@@ -421,6 +435,7 @@ pub(crate) async fn get_keys_helper<F>(
|
||||
device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
|
||||
allowed_signatures: F,
|
||||
include_display_names: bool,
|
||||
timeout: Duration,
|
||||
) -> Result<get_keys::v3::Response>
|
||||
where
|
||||
F: Fn(&UserId) -> bool + Send + Sync,
|
||||
@@ -512,9 +527,10 @@ pub(crate) async fn get_keys_helper<F>(
|
||||
|
||||
let mut failures = BTreeMap::new();
|
||||
|
||||
let mut futures: FuturesUnordered<_> = get_over_federation
|
||||
let futures = get_over_federation
|
||||
.into_iter()
|
||||
.map(|(server, vec)| async move {
|
||||
.stream()
|
||||
.wide_filter_map(|(server, vec)| async move {
|
||||
let mut device_keys_input_fed = BTreeMap::new();
|
||||
for (user_id, keys) in vec {
|
||||
device_keys_input_fed.insert(user_id.to_owned(), keys.clone());
|
||||
@@ -522,17 +538,22 @@ pub(crate) async fn get_keys_helper<F>(
|
||||
|
||||
let request =
|
||||
federation::keys::get_keys::v1::Request { device_keys: device_keys_input_fed };
|
||||
let response = tokio::time::timeout(
|
||||
timeout,
|
||||
services.sending.send_federation_request(server, request),
|
||||
)
|
||||
.await
|
||||
// Need to flatten the Result<Result<V, E>, E> into Result<V, E>
|
||||
.map_err(|_| err!(Request(Unknown("Timeout when getting keys over federation."))))
|
||||
.and_then(|res| res);
|
||||
|
||||
let response = services
|
||||
.sending
|
||||
.send_federation_request(server, request)
|
||||
.await;
|
||||
|
||||
(server, response)
|
||||
Some((server, response))
|
||||
})
|
||||
.collect();
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.await
|
||||
.into_iter();
|
||||
|
||||
while let Some((server, response)) = futures.next().await {
|
||||
for (server, response) in futures {
|
||||
match response {
|
||||
| Ok(response) => {
|
||||
for (user, master_key) in response.master_keys {
|
||||
@@ -564,8 +585,8 @@ pub(crate) async fn get_keys_helper<F>(
|
||||
self_signing_keys.extend(response.self_signing_keys);
|
||||
device_keys.extend(response.device_keys);
|
||||
},
|
||||
| _ => {
|
||||
failures.insert(server.to_string(), json!({}));
|
||||
| Err(e) => {
|
||||
failures.insert(server.to_string(), json!({ "error": e.to_string() }));
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -608,6 +629,7 @@ fn add_unsigned_device_display_name(
|
||||
pub(crate) async fn claim_keys_helper(
|
||||
services: &Services,
|
||||
one_time_keys_input: &BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, OneTimeKeyAlgorithm>>,
|
||||
timeout: Duration,
|
||||
) -> Result<claim_keys::v3::Response> {
|
||||
let mut one_time_keys = BTreeMap::new();
|
||||
|
||||
@@ -638,32 +660,39 @@ pub(crate) async fn claim_keys_helper(
|
||||
|
||||
let mut failures = BTreeMap::new();
|
||||
|
||||
let mut futures: FuturesUnordered<_> = get_over_federation
|
||||
let futures = get_over_federation
|
||||
.into_iter()
|
||||
.map(|(server, vec)| async move {
|
||||
.stream()
|
||||
.wide_filter_map(|(server, vec)| async move {
|
||||
let mut one_time_keys_input_fed = BTreeMap::new();
|
||||
for (user_id, keys) in vec {
|
||||
one_time_keys_input_fed.insert(user_id.clone(), keys.clone());
|
||||
}
|
||||
(
|
||||
server,
|
||||
services
|
||||
.sending
|
||||
.send_federation_request(server, federation::keys::claim_keys::v1::Request {
|
||||
let response = tokio::time::timeout(
|
||||
timeout,
|
||||
services.sending.send_federation_request(
|
||||
server,
|
||||
federation::keys::claim_keys::v1::Request {
|
||||
one_time_keys: one_time_keys_input_fed,
|
||||
})
|
||||
.await,
|
||||
},
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| err!(Request(Unknown("Timeout when claiming keys over federation."))))
|
||||
.and_then(|res| res);
|
||||
Some((server, response))
|
||||
})
|
||||
.collect();
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.await
|
||||
.into_iter();
|
||||
|
||||
while let Some((server, response)) = futures.next().await {
|
||||
for (server, response) in futures {
|
||||
match response {
|
||||
| Ok(keys) => {
|
||||
one_time_keys.extend(keys.one_time_keys);
|
||||
},
|
||||
| Err(_e) => {
|
||||
failures.insert(server.to_string(), json!({}));
|
||||
| Err(e) => {
|
||||
failures.insert(server.to_string(), json!({"error": e.to_string()}));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use conduwuit::{
|
||||
Err, Result, debug_error, err, info,
|
||||
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
|
||||
warn,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use ruma::{
|
||||
@@ -21,7 +22,7 @@
|
||||
/// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
|
||||
///
|
||||
/// Tries to send an invite event into the room.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "invite")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "invite", level = "info")]
|
||||
pub(crate) async fn invite_user_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -124,6 +125,18 @@ 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;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
events::{
|
||||
StateEventType,
|
||||
room::{
|
||||
join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent},
|
||||
join_rules::{AllowRule, JoinRule},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
},
|
||||
@@ -59,7 +59,7 @@
|
||||
/// rules locally
|
||||
/// - If the server does not know about the room: asks other servers over
|
||||
/// federation
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "join")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "join", level = "info")]
|
||||
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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "join", level = "info")]
|
||||
pub(crate) async fn join_room_by_id_or_alias_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -288,6 +288,23 @@ 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
|
||||
@@ -321,6 +338,17 @@ 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,
|
||||
@@ -347,11 +375,10 @@ 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")]
|
||||
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote", level = "info")]
|
||||
async fn join_room_by_id_helper_remote(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
@@ -709,7 +736,7 @@ async fn join_room_by_id_helper_remote(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_local")]
|
||||
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_local", level = "info")]
|
||||
async fn join_room_by_id_helper_local(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
@@ -720,45 +747,51 @@ 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 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
|
||||
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
|
||||
.rooms
|
||||
.state_cache
|
||||
.local_users_in_room(room_id)
|
||||
@@ -774,10 +807,14 @@ async fn join_room_by_id_helper_local(
|
||||
.boxed()
|
||||
.next()
|
||||
.await
|
||||
.map(ToOwned::to_owned)
|
||||
} else {
|
||||
trace!("No restriction rooms are joined by {sender_user}");
|
||||
None
|
||||
.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.")));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -805,16 +842,14 @@ async fn join_room_by_id_helper_local(
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if restriction_rooms.is_empty()
|
||||
&& (servers.is_empty()
|
||||
|| servers.len() == 1 && services.globals.server_is_ours(&servers[0]))
|
||||
{
|
||||
if servers.is_empty() || servers.len() == 1 && services.globals.server_is_ours(&servers[0]) {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
warn!(
|
||||
"We couldn't do the join locally, maybe federation can help to satisfy the restricted \
|
||||
join requirements"
|
||||
?error,
|
||||
servers = %servers.len(),
|
||||
"Could not join restricted room locally, attempting remote join",
|
||||
);
|
||||
let Ok((make_join_response, remote_server)) =
|
||||
make_join_request(services, sender_user, room_id, servers).await
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
/// # `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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "knock", level = "info")]
|
||||
pub(crate) async fn knock_room_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
|
||||
@@ -178,7 +178,20 @@ pub async fn leave_room(
|
||||
.rooms
|
||||
.state_cache
|
||||
.left_state(user_id, room_id)
|
||||
.await?
|
||||
.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()
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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))]
|
||||
#[tracing::instrument(skip(services), level = "info")]
|
||||
pub(crate) async fn banned_room_check(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
|
||||
@@ -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.")));
|
||||
}
|
||||
|
||||
|
||||
@@ -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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "report_room", level = "info")]
|
||||
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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "report_event", level = "info")]
|
||||
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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "report_user", level = "info")]
|
||||
pub(crate) async fn report_user_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
|
||||
@@ -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",))
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Result, debug_warn, trace,
|
||||
Err, Result, debug, debug_warn, info, trace,
|
||||
utils::{IterStream, future::TryExtExt},
|
||||
};
|
||||
use futures::{
|
||||
FutureExt, StreamExt,
|
||||
FutureExt, StreamExt, TryFutureExt,
|
||||
future::{OptionFuture, join3},
|
||||
stream::FuturesUnordered,
|
||||
};
|
||||
@@ -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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "room_summary", level = "info")]
|
||||
pub(crate) async fn get_room_summary(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -79,9 +79,15 @@ async fn room_summary_response(
|
||||
.server_in_room(services.globals.server_name(), room_id)
|
||||
.await
|
||||
{
|
||||
return local_room_summary_response(services, room_id, sender_user)
|
||||
match local_room_summary_response(services, room_id, sender_user)
|
||||
.boxed()
|
||||
.await;
|
||||
.await
|
||||
{
|
||||
| Ok(response) => return Ok(response),
|
||||
| Err(e) => {
|
||||
debug_warn!("Failed to get local room summary: {e:?}, falling back to remote");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let room =
|
||||
@@ -110,27 +116,31 @@ async fn local_room_summary_response(
|
||||
room_id: &RoomId,
|
||||
sender_user: Option<&UserId>,
|
||||
) -> Result<get_summary::msc3266::Response> {
|
||||
trace!(?sender_user, "Sending local room summary response for {room_id:?}");
|
||||
let join_rule = services.rooms.state_accessor.get_join_rules(room_id);
|
||||
|
||||
let world_readable = services.rooms.state_accessor.is_world_readable(room_id);
|
||||
|
||||
let guest_can_join = services.rooms.state_accessor.guest_can_join(room_id);
|
||||
|
||||
let (join_rule, world_readable, guest_can_join) =
|
||||
join3(join_rule, world_readable, guest_can_join).await;
|
||||
|
||||
trace!("{join_rule:?}, {world_readable:?}, {guest_can_join:?}");
|
||||
user_can_see_summary(
|
||||
services,
|
||||
room_id,
|
||||
&join_rule.clone().into(),
|
||||
guest_can_join,
|
||||
world_readable,
|
||||
join_rule.allowed_rooms(),
|
||||
sender_user,
|
||||
trace!(
|
||||
sender_user = sender_user.map(tracing::field::display),
|
||||
"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),
|
||||
services.rooms.state_accessor.guest_can_join(room_id),
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
|
||||
// Synapse allows server admins to bypass visibility checks.
|
||||
// That seems neat so we'll copy that behaviour.
|
||||
if sender_user.is_none() || !services.users.is_admin(sender_user.unwrap()).await {
|
||||
user_can_see_summary(
|
||||
services,
|
||||
room_id,
|
||||
&join_rule.clone().into(),
|
||||
guest_can_join,
|
||||
world_readable,
|
||||
join_rule.allowed_rooms(),
|
||||
sender_user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let canonical_alias = services
|
||||
.rooms
|
||||
@@ -221,7 +231,7 @@ async fn remote_room_summary_hierarchy_response(
|
||||
servers: &[OwnedServerName],
|
||||
sender_user: Option<&UserId>,
|
||||
) -> Result<SpaceHierarchyParentSummary> {
|
||||
trace!(?sender_user, ?servers, "Sending remote room summary response for {room_id:?}");
|
||||
trace!(sender_user = ?sender_user.map(tracing::field::display), ?servers, "Sending remote room summary response for {room_id:?}");
|
||||
if !services.config.allow_federation {
|
||||
return Err!(Request(Forbidden("Federation is disabled.")));
|
||||
}
|
||||
@@ -231,15 +241,27 @@ async fn remote_room_summary_hierarchy_response(
|
||||
"Federaton of room {room_id} is currently disabled on this server."
|
||||
)));
|
||||
}
|
||||
if servers.is_empty() {
|
||||
return Err!(Request(MissingParam(
|
||||
"No servers were provided to fetch the room over federation"
|
||||
)));
|
||||
}
|
||||
|
||||
let request = get_hierarchy::v1::Request::new(room_id.to_owned());
|
||||
|
||||
let mut requests: FuturesUnordered<_> = servers
|
||||
.iter()
|
||||
.map(|server| {
|
||||
info!("Fetching room summary for {room_id} from server {server}");
|
||||
services
|
||||
.sending
|
||||
.send_federation_request(server, request.clone())
|
||||
.inspect_ok(move |v| {
|
||||
debug!("Fetched room summary for {room_id} from server {server}: {v:?}");
|
||||
})
|
||||
.inspect_err(move |e| {
|
||||
info!("Failed to fetch room summary for {room_id} from server {server}: {e}");
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -255,23 +277,23 @@ async fn remote_room_summary_hierarchy_response(
|
||||
continue;
|
||||
}
|
||||
|
||||
return user_can_see_summary(
|
||||
services,
|
||||
room_id,
|
||||
&room.join_rule,
|
||||
room.guest_can_join,
|
||||
room.world_readable,
|
||||
room.allowed_room_ids.iter().map(AsRef::as_ref),
|
||||
sender_user,
|
||||
)
|
||||
.await
|
||||
.map(|()| room);
|
||||
if sender_user.is_none() || !services.users.is_admin(sender_user.unwrap()).await {
|
||||
return user_can_see_summary(
|
||||
services,
|
||||
room_id,
|
||||
&room.join_rule,
|
||||
room.guest_can_join,
|
||||
room.world_readable,
|
||||
room.allowed_room_ids.iter().map(AsRef::as_ref),
|
||||
sender_user,
|
||||
)
|
||||
.await
|
||||
.map(|()| room);
|
||||
}
|
||||
return Ok(room);
|
||||
}
|
||||
|
||||
Err!(Request(NotFound(
|
||||
"Room is unknown to this server and was unable to fetch over federation with the \
|
||||
provided servers available"
|
||||
)))
|
||||
Err!(Request(NotFound("Room not found or is not accessible")))
|
||||
}
|
||||
|
||||
async fn user_can_see_summary<'a, I>(
|
||||
@@ -311,21 +333,14 @@ async fn user_can_see_summary<'a, I>(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err!(Request(Forbidden(
|
||||
"Room is not world readable, not publicly accessible/joinable, restricted room \
|
||||
conditions not met, and guest access is forbidden. Not allowed to see details \
|
||||
of this room."
|
||||
)))
|
||||
Err!(Request(Forbidden("Room is not accessible")))
|
||||
},
|
||||
| None => {
|
||||
if is_public_room || world_readable {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err!(Request(Forbidden(
|
||||
"Room is not world readable or publicly accessible/joinable, authentication is \
|
||||
required"
|
||||
)))
|
||||
Err!(Request(Forbidden("Room is not accessible")))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
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};
|
||||
@@ -12,6 +13,7 @@
|
||||
use ruma::{
|
||||
OwnedUserId, UserId,
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
session::{
|
||||
get_login_token,
|
||||
get_login_types::{
|
||||
@@ -35,7 +37,7 @@
|
||||
///
|
||||
/// 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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "login", level = "info")]
|
||||
pub(crate) async fn get_login_types_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -53,7 +55,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")]
|
||||
#[tracing::instrument(skip_all, fields(%user_id), name = "password", level = "debug")]
|
||||
pub(crate) async fn password_login(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
@@ -96,7 +98,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")]
|
||||
#[tracing::instrument(skip_all, fields(%user_id), name = "ldap", level = "debug")]
|
||||
pub(super) async fn ldap_login(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
@@ -184,6 +186,15 @@ 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),
|
||||
@@ -212,7 +223,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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "login", level = "info")]
|
||||
pub(crate) async fn login_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -345,7 +356,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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "login_token", level = "info")]
|
||||
pub(crate) async fn login_token_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -413,7 +424,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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "logout", level = "info")]
|
||||
pub(crate) async fn logout_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -440,7 +451,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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "logout", level = "info")]
|
||||
pub(crate) async fn logout_all_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
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"
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {}", room_id);
|
||||
warn!(?err, %room_id, "error loading joined room");
|
||||
None
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
/// 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")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "mutual_rooms", level = "info")]
|
||||
pub(crate) async fn get_mutual_rooms_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use axum::{Json, extract::State, response::IntoResponse};
|
||||
use conduwuit::{Error, Result};
|
||||
use futures::StreamExt;
|
||||
use ruma::api::client::{
|
||||
discovery::{
|
||||
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
|
||||
@@ -71,21 +70,18 @@ pub(crate) async fn well_known_support(
|
||||
|
||||
// Try to add admin users as contacts if no contacts are configured
|
||||
if contacts.is_empty() {
|
||||
if let Ok(admin_room) = services.admin.get_admin_room().await {
|
||||
let admin_users = services.rooms.state_cache.room_members(&admin_room);
|
||||
let mut stream = admin_users;
|
||||
let admin_users = services.admin.get_admins().await;
|
||||
|
||||
while let Some(user_id) = stream.next().await {
|
||||
// Skip server user
|
||||
if *user_id == services.globals.server_user {
|
||||
continue;
|
||||
}
|
||||
contacts.push(Contact {
|
||||
role: role_value.clone(),
|
||||
email_address: None,
|
||||
matrix_id: Some(user_id.to_owned()),
|
||||
});
|
||||
for user_id in &admin_users {
|
||||
if *user_id == services.globals.server_user {
|
||||
continue;
|
||||
}
|
||||
|
||||
contacts.push(Contact {
|
||||
role: role_value.clone(),
|
||||
email_address: None,
|
||||
matrix_id: Some(user_id.to_owned()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,12 +137,30 @@ pub(super) async fn auth(
|
||||
| (
|
||||
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None,
|
||||
Token::User((user_id, device_id)),
|
||||
) => Ok(Auth {
|
||||
origin: None,
|
||||
sender_user: Some(user_id),
|
||||
sender_device: Some(device_id),
|
||||
appservice_info: None,
|
||||
}),
|
||||
) => {
|
||||
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,
|
||||
})
|
||||
},
|
||||
| (AuthScheme::ServerSignatures, Token::None) =>
|
||||
Ok(auth_server(services, request, json_body).await?),
|
||||
| (
|
||||
|
||||
@@ -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 = body.origin.as_ref().map(tracing::field::display),
|
||||
"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 = body.origin.as_ref().map(tracing::field::display),
|
||||
"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 = body.origin.as_ref().map(tracing::field::display),
|
||||
"Failed to convert PDU in database to canonical JSON: {pdu:?}"
|
||||
);
|
||||
i = i.saturating_add(1);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
/// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}`
|
||||
///
|
||||
/// Invites a remote user to a room.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "invite")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "invite", level = "info")]
|
||||
pub(crate) async fn create_invite_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -148,6 +148,15 @@ 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())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::borrow::ToOwned;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Error, Result, debug_info, info, matrix::pdu::PduBuilder, utils::IterStream, warn,
|
||||
};
|
||||
use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn};
|
||||
use conduwuit_service::Services;
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
@@ -22,7 +22,7 @@
|
||||
/// # `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()))]
|
||||
#[tracing::instrument(skip_all, fields(room_id = %body.room_id, user_id = %body.user_id, origin = %body.origin()), level = "info")]
|
||||
pub(crate) async fn create_join_event_template_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<prepare_join_event::v1::Request>,
|
||||
@@ -122,6 +122,16 @@ 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
|
||||
@@ -136,7 +146,6 @@ 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
|
||||
@@ -192,25 +201,44 @@ pub(crate) async fn user_can_perform_restricted_join(
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
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."
|
||||
)))
|
||||
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
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Err!(Request(UnableToAuthorizeJoin(
|
||||
"Joining user is not known to be in any required room."
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_strip_event_id(
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Error, Result, debug,
|
||||
debug::INFO_SPAN_LEVEL,
|
||||
debug_warn, err, error,
|
||||
Err, Error, Result, debug, debug_warn, err, error,
|
||||
result::LogErr,
|
||||
trace,
|
||||
utils::{
|
||||
@@ -48,7 +46,7 @@
|
||||
/// Push EDUs and PDUs to this server.
|
||||
#[tracing::instrument(
|
||||
name = "txn",
|
||||
level = INFO_SPAN_LEVEL,
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(
|
||||
%client,
|
||||
@@ -83,8 +81,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",
|
||||
);
|
||||
|
||||
@@ -110,8 +108,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 {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
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()))]
|
||||
#[tracing::instrument(skip(services, pdu, omit_members), fields(room_id = room_id.as_str(), origin = origin.as_str()), level = "info")]
|
||||
async fn create_join_event(
|
||||
services: &Services,
|
||||
origin: &ServerName,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Error, Result};
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt};
|
||||
@@ -96,6 +98,7 @@ pub(crate) async fn get_keys_route(
|
||||
&body.device_keys,
|
||||
|u| Some(u.server_name()) == body.origin.as_deref(),
|
||||
services.globals.allow_device_name_federation(),
|
||||
Duration::from_secs(0),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -124,7 +127,8 @@ pub(crate) async fn claim_keys_route(
|
||||
));
|
||||
}
|
||||
|
||||
let result = claim_keys_helper(&services, &body.one_time_keys).await?;
|
||||
let result =
|
||||
claim_keys_helper(&services, &body.one_time_keys, Duration::from_secs(0)).await?;
|
||||
|
||||
Ok(claim_keys::v1::Response { one_time_keys: result.one_time_keys })
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
[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
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
[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
|
||||
|
||||
@@ -340,7 +340,7 @@ fn set<T>(key: &Key, val: T) -> Result<T>
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "get",
|
||||
level = "trace"
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(?key)
|
||||
)]
|
||||
@@ -357,7 +357,7 @@ fn get<T>(key: &Key) -> Result<T>
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "xchg",
|
||||
level = "trace"
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(?key, ?val)
|
||||
)]
|
||||
|
||||
@@ -146,22 +146,6 @@ 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",
|
||||
@@ -187,29 +171,9 @@ 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 \
|
||||
|
||||
@@ -59,7 +59,7 @@ fn deref(&self) -> &Self::Target { HANDLE.with_borrow_mut(|handle| self.load(han
|
||||
|
||||
/// Update the active configuration, returning prior configuration.
|
||||
#[implement(Manager)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
pub fn update(&self, config: Config) -> Result<Arc<Config>> {
|
||||
let config = Arc::new(config);
|
||||
let new = Arc::into_raw(config);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
pub use figment::{Figment, value::Value as FigmentValue};
|
||||
use regex::RegexSet;
|
||||
use ruma::{
|
||||
OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
|
||||
OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
|
||||
api::client::discovery::discover_support::ContactRole,
|
||||
};
|
||||
use serde::{Deserialize, de::IgnoredAny};
|
||||
@@ -53,7 +53,8 @@
|
||||
### 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"
|
||||
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"
|
||||
)]
|
||||
pub struct Config {
|
||||
// Paths to config file(s). Not supposed to be set manually in the config file,
|
||||
@@ -544,7 +545,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` or `registration_token_file`.
|
||||
/// `registration_token`.
|
||||
#[serde(default)]
|
||||
pub allow_registration: bool,
|
||||
|
||||
@@ -575,22 +576,14 @@ 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.
|
||||
///
|
||||
/// YOU NEED TO EDIT THIS OR USE registration_token_file.
|
||||
/// If you do not want to set a static token, the `!admin token` commands
|
||||
/// may also be used to manage registration tokens.
|
||||
///
|
||||
/// example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
|
||||
///
|
||||
/// 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
|
||||
@@ -1819,6 +1812,22 @@ pub struct Config {
|
||||
#[serde(default = "default_admin_room_tag")]
|
||||
pub admin_room_tag: String,
|
||||
|
||||
/// A list of Matrix IDs that are qualified as server admins.
|
||||
///
|
||||
/// Any Matrix IDs within this list are regarded as an admin
|
||||
/// regardless of whether they are in the admin room or not
|
||||
///
|
||||
/// default: []
|
||||
#[serde(default)]
|
||||
pub admins_list: Vec<OwnedUserId>,
|
||||
|
||||
/// Defines whether those within the admin room are added to the
|
||||
/// admins_list.
|
||||
///
|
||||
/// default: true
|
||||
#[serde(default = "true_fn")]
|
||||
pub admins_from_room: bool,
|
||||
|
||||
/// Sentry.io crash/panic reporting, performance monitoring/metrics, etc.
|
||||
/// This is NOT enabled by default.
|
||||
#[serde(default)]
|
||||
@@ -1871,7 +1880,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,
|
||||
@@ -2008,6 +2017,10 @@ 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,
|
||||
@@ -2224,7 +2237,58 @@ struct ListeningAddr {
|
||||
addrs: Either<IpAddr, Vec<IpAddr>>,
|
||||
}
|
||||
|
||||
const DEPRECATED_KEYS: &[&str; 9] = &[
|
||||
#[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] = &[
|
||||
"cache_capacity",
|
||||
"conduit_cache_capacity_modifier",
|
||||
"max_concurrent_requests",
|
||||
@@ -2234,6 +2298,7 @@ struct ListeningAddr {
|
||||
"well_known_support_role",
|
||||
"well_known_support_email",
|
||||
"well_known_support_mxid",
|
||||
"registration_token_file",
|
||||
];
|
||||
|
||||
impl Config {
|
||||
|
||||
@@ -75,10 +75,12 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
|
||||
| ThreepidDenied
|
||||
| InviteBlocked
|
||||
| WrongRoomKeysVersion { .. }
|
||||
| UserSuspended
|
||||
| Forbidden { .. } => StatusCode::FORBIDDEN,
|
||||
|
||||
// 401
|
||||
| UnknownToken { .. } | MissingToken | Unauthorized => StatusCode::UNAUTHORIZED,
|
||||
| UnknownToken { .. } | MissingToken | Unauthorized | UserLocked =>
|
||||
StatusCode::UNAUTHORIZED,
|
||||
|
||||
// 400
|
||||
| _ => StatusCode::BAD_REQUEST,
|
||||
|
||||
@@ -532,8 +532,8 @@ pub async fn auth_check<E, F, Fut>(
|
||||
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 @@ pub async fn auth_check<E, F, Fut>(
|
||||
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,11 +772,12 @@ struct GetThirdPartyInvite {
|
||||
power_levels_event.as_ref().is_some(),
|
||||
) || auth_user_pl >= invite_level;
|
||||
trace!(
|
||||
auth_user_pl=?auth_user_pl,
|
||||
invite_level=?invite_level,
|
||||
user_joined=?user_joined,
|
||||
okay_power=?okay_power,
|
||||
passing=?(user_joined && okay_power),
|
||||
%auth_user_pl,
|
||||
%auth_user_pl,
|
||||
%invite_level,
|
||||
%user_joined,
|
||||
%okay_power,
|
||||
passing=%(user_joined && okay_power),
|
||||
"user for join auth is valid check details"
|
||||
);
|
||||
user_joined && okay_power
|
||||
@@ -1211,7 +1212,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",
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
[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
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use crate::util::map_err;
|
||||
|
||||
#[implement(Engine)]
|
||||
#[tracing::instrument(skip(self))]
|
||||
#[tracing::instrument(skip(self), level = "info")]
|
||||
pub fn backup(&self) -> Result {
|
||||
let mut engine = self.backup_engine()?;
|
||||
let config = &self.ctx.server.config;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#[tracing::instrument(
|
||||
parent = None,
|
||||
name = "rocksdb",
|
||||
level = "trace"
|
||||
level = "trace",
|
||||
skip(msg),
|
||||
)]
|
||||
pub(crate) fn handle(level: LogLevel, msg: &str) {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
use crate::{Context, or_else};
|
||||
|
||||
#[implement(Engine)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
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)]
|
||||
#[tracing::instrument(name = "configure", skip_all, level = "debug")]
|
||||
fn configure_cfds(
|
||||
ctx: &Arc<Context>,
|
||||
db_opts: &Options,
|
||||
@@ -119,7 +119,7 @@ fn configure_cfds(
|
||||
}
|
||||
|
||||
#[implement(Engine)]
|
||||
#[tracing::instrument(name = "discover", skip_all)]
|
||||
#[tracing::instrument(name = "discover", skip_all, level = "debug")]
|
||||
fn discover_cfs(path: &Path, opts: &Options) -> BTreeSet<String> {
|
||||
Db::list_cf(opts, path)
|
||||
.unwrap_or_default()
|
||||
|
||||
@@ -26,7 +26,7 @@ pub struct Options {
|
||||
#[implement(super::Map)]
|
||||
#[tracing::instrument(
|
||||
name = "compact",
|
||||
level = "info"
|
||||
level = "info",
|
||||
skip(self),
|
||||
fields(%self),
|
||||
)]
|
||||
|
||||
@@ -141,6 +141,10 @@ pub(super) fn open_list(db: &Arc<Engine>, maps: &[Descriptor]) -> Result<Maps> {
|
||||
name: "referencedevents",
|
||||
..descriptor::RANDOM
|
||||
},
|
||||
Descriptor {
|
||||
name: "registrationtoken_info",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "roomid_invitedcount",
|
||||
..descriptor::RANDOM_SMALL
|
||||
@@ -386,6 +390,14 @@ pub(super) fn open_list(db: &Arc<Engine>, maps: &[Descriptor]) -> Result<Maps> {
|
||||
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
|
||||
|
||||
@@ -113,7 +113,7 @@ fn drop(&mut self) {
|
||||
}
|
||||
|
||||
#[implement(Pool)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tracing::instrument(skip_all, level = "debug")]
|
||||
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"
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
[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
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
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
|
||||
|
||||
@@ -23,14 +20,13 @@ crate-type = [
|
||||
|
||||
[package.metadata.deb]
|
||||
name = "continuwuity"
|
||||
maintainer = "continuwuity developers <contact@continuwuity.org>"
|
||||
copyright = "2024, continuwuity developers"
|
||||
maintainer = "Continuwuity Team and contributors <team@continuwuity.org>"
|
||||
license-file = ["../../LICENSE", "3"]
|
||||
depends = "$auto, ca-certificates"
|
||||
breaks = ["conduwuit (<<0.5.0)"]
|
||||
replaces = ["conduwuit (<<0.5.0)"]
|
||||
extended-description = """\
|
||||
a cool hard fork of Conduit, a Matrix homeserver written in Rust"""
|
||||
A Matrix homeserver written in Rust, the official continuation of the conduwuit homeserver."""
|
||||
section = "net"
|
||||
priority = "optional"
|
||||
conf-files = ["/etc/conduwuit/conduwuit.toml"]
|
||||
@@ -64,15 +60,15 @@ standard = [
|
||||
"url_preview",
|
||||
"zstd_compression",
|
||||
"sentry_telemetry",
|
||||
"otlp_telemetry"
|
||||
"otlp_telemetry",
|
||||
"console",
|
||||
]
|
||||
full = [
|
||||
"standard",
|
||||
# "hardened_malloc", # Conflicts with jemalloc
|
||||
"jemalloc_prof",
|
||||
"perf_measurements",
|
||||
"tokio_console"
|
||||
# sentry_telemetry
|
||||
"tokio_console",
|
||||
]
|
||||
|
||||
blurhashing = [
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
debug_warn, err,
|
||||
log::{ConsoleFormat, ConsoleWriter, LogLevelReloadHandles, capture, fmt_span},
|
||||
result::UnwrapOrErr,
|
||||
warn,
|
||||
};
|
||||
#[cfg(feature = "otlp_telemetry")]
|
||||
use opentelemetry::trace::TracerProvider;
|
||||
@@ -85,7 +86,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)
|
||||
.with_protocol(opentelemetry_otlp::Protocol::Grpc) // TODO: build from env when 0.32 is released
|
||||
.build()
|
||||
.expect("Failed to create OTLP gRPC exporter"),
|
||||
| "http" => opentelemetry_otlp::SpanExporter::builder()
|
||||
@@ -93,7 +94,7 @@ pub(crate) fn init(
|
||||
.build()
|
||||
.expect("Failed to create OTLP HTTP exporter"),
|
||||
| protocol => {
|
||||
debug_warn!(
|
||||
warn!(
|
||||
"Invalid OTLP protocol '{}', falling back to HTTP. Valid options are \
|
||||
'http' or 'grpc'.",
|
||||
protocol
|
||||
|
||||
@@ -50,7 +50,8 @@ pub fn run_with_args(args: &Args) -> Result<()> {
|
||||
#[tracing::instrument(
|
||||
name = "main",
|
||||
parent = None,
|
||||
skip_all
|
||||
skip_all,
|
||||
level = "info"
|
||||
)]
|
||||
async fn async_main(server: &Arc<Server>) -> Result<(), Error> {
|
||||
extern crate conduwuit_router as router;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
use super::server::Server;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
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)]
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
pub(super) async fn signal(server: Arc<Server>) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
[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
|
||||
|
||||
@@ -66,7 +66,10 @@ pub(crate) fn build(services: &Arc<Services>) -> Result<(Router, Guard)> {
|
||||
.layer(RequestBodyTimeoutLayer::new(Duration::from_secs(
|
||||
server.config.client_receive_timeout,
|
||||
)))
|
||||
.layer(TimeoutLayer::with_status_code(StatusCode::REQUEST_TIMEOUT, Duration::from_secs(server.config.client_request_timeout)))
|
||||
.layer(TimeoutLayer::with_status_code(
|
||||
StatusCode::REQUEST_TIMEOUT,
|
||||
Duration::from_secs(server.config.client_request_timeout),
|
||||
))
|
||||
.layer(SetResponseHeaderLayer::if_not_present(
|
||||
HeaderName::from_static("origin-agent-cluster"), // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin-Agent-Cluster
|
||||
HeaderValue::from_static("?1"),
|
||||
|
||||
@@ -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 = ?method, uri = ?uri, "{code} {reason}");
|
||||
error!(%method, %uri, "{code} {reason}");
|
||||
} else if status.is_client_error() {
|
||||
debug_error!(method = ?method, uri = ?uri, "{code} {reason}");
|
||||
debug_error!(%method, %uri, "{code} {reason}");
|
||||
} else if status.is_redirection() {
|
||||
debug!(method = ?method, uri = ?uri, "{code} {reason}");
|
||||
debug!(%method, %uri, "{code} {reason}");
|
||||
} else {
|
||||
trace!(method = ?method, uri = ?uri, "{code} {reason}");
|
||||
trace!(%method, %uri, "{code} {reason}");
|
||||
}
|
||||
|
||||
if status == StatusCode::METHOD_NOT_ALLOWED {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
use crate::serve;
|
||||
|
||||
/// Main loop base
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
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)]
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
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)]
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
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)]
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
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"
|
||||
);
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
[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
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
use termimad::MadSkin;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::{Dep, admin};
|
||||
use crate::{
|
||||
Dep,
|
||||
admin::{self, InvocationSource},
|
||||
};
|
||||
|
||||
pub struct Console {
|
||||
server: Arc<Server>,
|
||||
@@ -160,7 +163,11 @@ async fn handle(self: Arc<Self>, line: String) {
|
||||
}
|
||||
|
||||
async fn process(self: Arc<Self>, line: String) {
|
||||
match self.admin.command_in_place(line, None).await {
|
||||
match self
|
||||
.admin
|
||||
.command_in_place(line, None, InvocationSource::Console)
|
||||
.await
|
||||
{
|
||||
| Ok(Some(ref content)) => self.output(content),
|
||||
| Err(ref content) => self.output_err(content),
|
||||
| _ => unreachable!(),
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
use tokio::time::{Duration, sleep};
|
||||
|
||||
use crate::admin::InvocationSource;
|
||||
|
||||
pub(super) const SIGNAL: &str = "SIGUSR2";
|
||||
|
||||
/// Possibly spawn the terminal console at startup if configured.
|
||||
@@ -88,7 +90,10 @@ pub(super) async fn signal_execute(&self) -> Result {
|
||||
async fn execute_command(&self, i: usize, command: String) -> Result {
|
||||
debug!("Execute command #{i}: executing {command:?}");
|
||||
|
||||
match self.command_in_place(command, None).await {
|
||||
match self
|
||||
.command_in_place(command, None, InvocationSource::Console)
|
||||
.await
|
||||
{
|
||||
| Ok(Some(output)) => Self::execute_command_output(i, &output),
|
||||
| Err(output) => Self::execute_command_error(i, &output),
|
||||
| Ok(None) => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user