mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Compare commits
42 Commits
| 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 |
@@ -43,7 +43,7 @@ jobs:
|
|||||||
name: Renovate
|
name: Renovate
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
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
|
options: --tmpfs /tmp:exec
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
token: ${{ secrets.FORGEJO_TOKEN }}
|
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:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
|
||||||
|
|||||||
Generated
+33
-11
@@ -1632,6 +1632,16 @@ dependencies = [
|
|||||||
"litrs",
|
"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]]
|
[[package]]
|
||||||
name = "dtor"
|
name = "dtor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2982,6 +2992,16 @@ version = "2.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
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]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
@@ -4065,11 +4085,13 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma"
|
name = "ruma"
|
||||||
version = "0.10.1"
|
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 = [
|
dependencies = [
|
||||||
"assign",
|
"assign",
|
||||||
|
"draupnir-antispam",
|
||||||
"js_int",
|
"js_int",
|
||||||
"js_option",
|
"js_option",
|
||||||
|
"meowlnir-antispam",
|
||||||
"ruma-appservice-api",
|
"ruma-appservice-api",
|
||||||
"ruma-client-api",
|
"ruma-client-api",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
@@ -4085,7 +4107,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-appservice-api"
|
name = "ruma-appservice-api"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
@@ -4097,7 +4119,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-client-api"
|
name = "ruma-client-api"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"assign",
|
"assign",
|
||||||
@@ -4120,7 +4142,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-common"
|
name = "ruma-common"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
@@ -4152,7 +4174,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-events"
|
name = "ruma-events"
|
||||||
version = "0.28.1"
|
version = "0.28.1"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
@@ -4177,7 +4199,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-federation-api"
|
name = "ruma-federation-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"headers",
|
"headers",
|
||||||
@@ -4199,7 +4221,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-identifiers-validation"
|
name = "ruma-identifiers-validation"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
@@ -4208,7 +4230,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-identity-service-api"
|
name = "ruma-identity-service-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
@@ -4218,7 +4240,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-macros"
|
name = "ruma-macros"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
@@ -4233,7 +4255,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-push-gateway-api"
|
name = "ruma-push-gateway-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
@@ -4245,7 +4267,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-signatures"
|
name = "ruma-signatures"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
|||||||
+66
-73
@@ -1,26 +1,17 @@
|
|||||||
#cargo-features = ["profile-rustflags"]
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["src/*", "xtask/*"]
|
members = ["src/*", "xtask/*"]
|
||||||
default-members = ["src/*"]
|
default-members = ["src/*"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
authors = [
|
authors = ["Continuwuity Team and contributors <team@continuwuity.org>"]
|
||||||
"June Clementine Strawberry <june@girlboss.ceo>",
|
description = "A Matrix homeserver written in Rust, the official continuation of the conduwuit homeserver."
|
||||||
"strawberry <strawberry@puppygock.gay>", # woof
|
|
||||||
"Jason Volk <jason@zemos.net>",
|
|
||||||
]
|
|
||||||
categories = ["network-programming"]
|
|
||||||
description = "a very cool Matrix chat homeserver written in Rust"
|
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
homepage = "https://continuwuity.org/"
|
homepage = "https://continuwuity.org/"
|
||||||
keywords = ["chat", "matrix", "networking", "server", "uwu"]
|
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
# See also `rust-toolchain.toml`
|
# See also `rust-toolchain.toml`
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
||||||
rust-version = "1.86.0"
|
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|
||||||
[workspace.metadata.crane]
|
[workspace.metadata.crane]
|
||||||
@@ -33,11 +24,11 @@ features = ["serde"]
|
|||||||
[workspace.dependencies.smallvec]
|
[workspace.dependencies.smallvec]
|
||||||
version = "1.14.0"
|
version = "1.14.0"
|
||||||
features = [
|
features = [
|
||||||
"const_generics",
|
"const_generics",
|
||||||
"const_new",
|
"const_new",
|
||||||
"serde",
|
"serde",
|
||||||
"union",
|
"union",
|
||||||
"write",
|
"write",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.smallstr]
|
[workspace.dependencies.smallstr]
|
||||||
@@ -96,13 +87,13 @@ version = "1.11.1"
|
|||||||
version = "0.7.9"
|
version = "0.7.9"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"form",
|
"form",
|
||||||
"http1",
|
"http1",
|
||||||
"http2",
|
"http2",
|
||||||
"json",
|
"json",
|
||||||
"matched-path",
|
"matched-path",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.axum-extra]
|
[workspace.dependencies.axum-extra]
|
||||||
@@ -149,10 +140,10 @@ features = ["aws_lc_rs"]
|
|||||||
version = "0.12.15"
|
version = "0.12.15"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"rustls-tls-native-roots",
|
"rustls-tls-native-roots",
|
||||||
"socks",
|
"socks",
|
||||||
"hickory-dns",
|
"hickory-dns",
|
||||||
"http2",
|
"http2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.serde]
|
[workspace.dependencies.serde]
|
||||||
@@ -188,18 +179,18 @@ default-features = false
|
|||||||
version = "0.25.5"
|
version = "0.25.5"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"png",
|
"png",
|
||||||
"gif",
|
"gif",
|
||||||
"webp",
|
"webp",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.blurhash]
|
[workspace.dependencies.blurhash]
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"fast-linear-to-srgb",
|
"fast-linear-to-srgb",
|
||||||
"image",
|
"image",
|
||||||
]
|
]
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
@@ -229,13 +220,13 @@ default-features = false
|
|||||||
version = "4.5.35"
|
version = "4.5.35"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"derive",
|
"derive",
|
||||||
"env",
|
"env",
|
||||||
"error-context",
|
"error-context",
|
||||||
"help",
|
"help",
|
||||||
"std",
|
"std",
|
||||||
"string",
|
"string",
|
||||||
"usage",
|
"usage",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.futures]
|
[workspace.dependencies.futures]
|
||||||
@@ -247,15 +238,15 @@ features = ["std", "async-await"]
|
|||||||
version = "1.44.2"
|
version = "1.44.2"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"fs",
|
"fs",
|
||||||
"net",
|
"net",
|
||||||
"macros",
|
"macros",
|
||||||
"sync",
|
"sync",
|
||||||
"signal",
|
"signal",
|
||||||
"time",
|
"time",
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"io-util",
|
"io-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.tokio-metrics]
|
[workspace.dependencies.tokio-metrics]
|
||||||
@@ -280,18 +271,18 @@ default-features = false
|
|||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"server",
|
"server",
|
||||||
"http1",
|
"http1",
|
||||||
"http2",
|
"http2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.hyper-util]
|
[workspace.dependencies.hyper-util]
|
||||||
version = "=0.1.17"
|
version = "=0.1.17"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"server-auto",
|
"server-auto",
|
||||||
"server-graceful",
|
"server-graceful",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
# to support multiple variations of setting a config option
|
# to support multiple variations of setting a config option
|
||||||
@@ -310,9 +301,9 @@ features = ["env", "toml"]
|
|||||||
version = "0.25.1"
|
version = "0.25.1"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"serde",
|
"serde",
|
||||||
"system-config",
|
"system-config",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Used for conduwuit::Error type
|
# Used for conduwuit::Error type
|
||||||
@@ -351,7 +342,7 @@ version = "0.1.2"
|
|||||||
# Used for matrix spec type definitions and helpers
|
# Used for matrix spec type definitions and helpers
|
||||||
[workspace.dependencies.ruma]
|
[workspace.dependencies.ruma]
|
||||||
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
||||||
rev = "27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
rev = "79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||||
features = [
|
features = [
|
||||||
"compat",
|
"compat",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -381,13 +372,13 @@ features = [
|
|||||||
"unstable-msc4095",
|
"unstable-msc4095",
|
||||||
"unstable-msc4121",
|
"unstable-msc4121",
|
||||||
"unstable-msc4125",
|
"unstable-msc4125",
|
||||||
"unstable-msc4155",
|
"unstable-msc4155",
|
||||||
"unstable-msc4186",
|
"unstable-msc4186",
|
||||||
"unstable-msc4203", # sending to-device events to appservices
|
"unstable-msc4203", # sending to-device events to appservices
|
||||||
"unstable-msc4210", # remove legacy mentions
|
"unstable-msc4210", # remove legacy mentions
|
||||||
"unstable-extensible-events",
|
"unstable-extensible-events",
|
||||||
"unstable-pdu",
|
"unstable-pdu",
|
||||||
"unstable-msc4155"
|
"unstable-msc4155"
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.rust-rocksdb]
|
[workspace.dependencies.rust-rocksdb]
|
||||||
@@ -395,11 +386,11 @@ git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1"
|
|||||||
rev = "61d9d23872197e9ace4a477f2617d5c9f50ecb23"
|
rev = "61d9d23872197e9ace4a477f2617d5c9f50ecb23"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"multi-threaded-cf",
|
"multi-threaded-cf",
|
||||||
"mt_static",
|
"mt_static",
|
||||||
"lz4",
|
"lz4",
|
||||||
"zstd",
|
"zstd",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.sha2]
|
[workspace.dependencies.sha2]
|
||||||
@@ -458,16 +449,16 @@ git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
|||||||
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"background_threads_runtime_support",
|
"background_threads_runtime_support",
|
||||||
"unprefixed_malloc_on_supported_platforms",
|
"unprefixed_malloc_on_supported_platforms",
|
||||||
]
|
]
|
||||||
[workspace.dependencies.tikv-jemallocator]
|
[workspace.dependencies.tikv-jemallocator]
|
||||||
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
||||||
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"background_threads_runtime_support",
|
"background_threads_runtime_support",
|
||||||
"unprefixed_malloc_on_supported_platforms",
|
"unprefixed_malloc_on_supported_platforms",
|
||||||
]
|
]
|
||||||
[workspace.dependencies.tikv-jemalloc-ctl]
|
[workspace.dependencies.tikv-jemalloc-ctl]
|
||||||
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
||||||
@@ -491,9 +482,9 @@ default-features = false
|
|||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"static",
|
"static",
|
||||||
"gcc",
|
"gcc",
|
||||||
"light",
|
"light",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.rustyline-async]
|
[workspace.dependencies.rustyline-async]
|
||||||
@@ -848,6 +839,8 @@ unknown_lints = "allow"
|
|||||||
|
|
||||||
###################
|
###################
|
||||||
cargo = { level = "warn", priority = -1 }
|
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
|
## some sadness
|
||||||
multiple_crate_versions = { level = "allow", priority = 1 }
|
multiple_crate_versions = { level = "allow", priority = 1 }
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Implemented account locking functionality, to complement user suspension. Contributed by @nex.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Added admin command to forcefully log out all of a user's existing sessions. Contributed by @nex.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Implemented toggling the ability for an account to log in without mutating any of its data. Contributed by @nex.
|
||||||
@@ -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**.
|
||||||
+42
-13
@@ -421,7 +421,7 @@
|
|||||||
# `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
|
# `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
|
# If you would like registration only via token reg, please configure
|
||||||
# `registration_token` or `registration_token_file`.
|
# `registration_token`.
|
||||||
#
|
#
|
||||||
#allow_registration = false
|
#allow_registration = false
|
||||||
|
|
||||||
@@ -452,22 +452,13 @@
|
|||||||
# `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
|
# `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
|
||||||
# to true to allow open registration without any conditions.
|
# 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"
|
# example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
|
||||||
#
|
#
|
||||||
#registration_token =
|
#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
|
# The public site key for reCaptcha. If this is provided, reCaptcha
|
||||||
# becomes required during registration. If both captcha *and*
|
# becomes required during registration. If both captcha *and*
|
||||||
# registration token are enabled, both will be required during
|
# registration token are enabled, both will be required during
|
||||||
@@ -1647,7 +1638,7 @@
|
|||||||
|
|
||||||
# Enable the tokio-console. This option is only relevant to developers.
|
# 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
|
# https://continuwuity.org/development.html#debugging-with-tokio-console
|
||||||
#
|
#
|
||||||
#tokio_console = false
|
#tokio_console = false
|
||||||
@@ -1923,3 +1914,41 @@
|
|||||||
# example: "(objectClass=conduwuitAdmin)" or "(uid={username})"
|
# example: "(objectClass=conduwuitAdmin)" or "(uid={username})"
|
||||||
#
|
#
|
||||||
#admin_filter = ""
|
#admin_filter = ""
|
||||||
|
|
||||||
|
[global.antispam.meowlnir]
|
||||||
|
|
||||||
|
# The base URL on which to contact Meowlnir (before /_meowlnir/antispam).
|
||||||
|
#
|
||||||
|
# Example: "http://127.0.0.1:29339"
|
||||||
|
#
|
||||||
|
#base_url =
|
||||||
|
|
||||||
|
# The authentication secret defined in antispam->secret. Required for
|
||||||
|
# continuwuity to talk to Meowlnir.
|
||||||
|
#
|
||||||
|
#secret =
|
||||||
|
|
||||||
|
# The management room for which to send requests
|
||||||
|
#
|
||||||
|
#management_room =
|
||||||
|
|
||||||
|
# If enabled run all federated join attempts (both federated and local)
|
||||||
|
# through the Meowlnir anti-spam checks.
|
||||||
|
#
|
||||||
|
# By default, only join attempts for rooms with the `fi.mau.spam_checker`
|
||||||
|
# restricted join rule are checked.
|
||||||
|
#
|
||||||
|
#check_all_joins = false
|
||||||
|
|
||||||
|
[global.antispam.draupnir]
|
||||||
|
|
||||||
|
# The base URL on which to contact Draupnir (before /api/).
|
||||||
|
#
|
||||||
|
# Example: "http://127.0.0.1:29339"
|
||||||
|
#
|
||||||
|
#base_url =
|
||||||
|
|
||||||
|
# The authentication secret defined in
|
||||||
|
# web->synapseHTTPAntispam->authorization
|
||||||
|
#
|
||||||
|
#secret =
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@ ENV BINSTALL_VERSION=1.16.6
|
|||||||
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
||||||
ENV CARGO_SBOM_VERSION=0.9.1
|
ENV CARGO_SBOM_VERSION=0.9.1
|
||||||
# renovate: datasource=crate depName=lddtree
|
# renovate: datasource=crate depName=lddtree
|
||||||
ENV LDDTREE_VERSION=0.3.7
|
ENV LDDTREE_VERSION=0.4.0
|
||||||
# renovate: datasource=crate depName=timelord-cli
|
# renovate: datasource=crate depName=timelord-cli
|
||||||
ENV TIMELORD_VERSION=3.0.1
|
ENV TIMELORD_VERSION=3.0.1
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ ENV BINSTALL_VERSION=1.16.6
|
|||||||
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
||||||
ENV CARGO_SBOM_VERSION=0.9.1
|
ENV CARGO_SBOM_VERSION=0.9.1
|
||||||
# renovate: datasource=crate depName=lddtree
|
# renovate: datasource=crate depName=lddtree
|
||||||
ENV LDDTREE_VERSION=0.3.7
|
ENV LDDTREE_VERSION=0.4.0
|
||||||
|
|
||||||
# Install unpackaged tools
|
# Install unpackaged tools
|
||||||
RUN <<EOF
|
RUN <<EOF
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ OCI images for Continuwuity are available in the registries listed below.
|
|||||||
|
|
||||||
| Registry | Image | Notes |
|
| Registry | Image | Notes |
|
||||||
| --------------- | --------------------------------------------------------------- | -----------------------|
|
| --------------- | --------------------------------------------------------------- | -----------------------|
|
||||||
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:latest][fj] | Latest tagged image. |
|
| 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][fj] | Main branch 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. |
|
||||||
[fj]: https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity
|
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:main-maxperf](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/main-maxperf) | Performance optimised version. |
|
||||||
|
|
||||||
Use
|
Use
|
||||||
|
|
||||||
@@ -24,6 +24,15 @@ docker image pull $LINK
|
|||||||
|
|
||||||
to pull it to your machine.
|
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
|
### Run
|
||||||
|
|
||||||
When you have the image, you can simply run it with
|
When you have the image, you can simply run it with
|
||||||
@@ -49,7 +58,7 @@ If you just want to test Continuwuity for a short time, you can use the `--rm`
|
|||||||
flag, which cleans up everything related to your container after you stop
|
flag, which cleans up everything related to your container after you stop
|
||||||
it.
|
it.
|
||||||
|
|
||||||
### Docker-compose
|
### Docker Compose
|
||||||
|
|
||||||
If the `docker run` command is not suitable for you or your setup, you can also use one
|
If the `docker run` command is not suitable for you or your setup, you can also use one
|
||||||
of the provided `docker-compose` files.
|
of the provided `docker-compose` files.
|
||||||
@@ -158,8 +167,19 @@ docker buildx build --load --tag continuwuity:latest -f docker/Dockerfile .
|
|||||||
# Example: Build for specific platforms and push to a registry.
|
# 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
|
# 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
|
# 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 .
|
# 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.
|
Refer to the Docker Buildx documentation for more advanced build options.
|
||||||
@@ -198,5 +218,3 @@ Alternatively, you can use Continuwuity's built-in delegation file capability. S
|
|||||||
## Voice communication
|
## Voice communication
|
||||||
|
|
||||||
See the [TURN](../turn.md) page.
|
See the [TURN](../turn.md) page.
|
||||||
|
|
||||||
[nix-buildlayeredimage]: https://ryantm.github.io/nixpkgs/builders/images/dockertools/#ssec-pkgs-dockerTools-buildLayeredImage
|
|
||||||
|
|||||||
+28
-18
@@ -8,29 +8,39 @@
|
|||||||
|
|
||||||
## Installing Continuwuity
|
## Installing Continuwuity
|
||||||
|
|
||||||
### Static prebuilt binary
|
### Prebuilt binary
|
||||||
|
|
||||||
You may simply download the binary that fits your machine architecture (x86_64
|
Download the binary for your architecture (x86_64 or aarch64) -
|
||||||
or aarch64). Run `uname -m` to see what you need.
|
run the `uname -m` to check which you need.
|
||||||
|
|
||||||
You can download prebuilt fully static musl binaries from the latest tagged
|
Prebuilt binaries are available from:
|
||||||
release [here](https://forgejo.ellis.link/continuwuation/continuwuity/releases/latest) or
|
- **Tagged releases**: [Latest release page](https://forgejo.ellis.link/continuwuation/continuwuity/releases/latest)
|
||||||
from the `main` CI branch workflow artifact output. These also include Debian/Ubuntu
|
- **Development builds**: CI artifacts from the `main` branch
|
||||||
packages.
|
(includes Debian/Ubuntu packages)
|
||||||
|
|
||||||
You can download these directly using curl. The `ci-bins` are CI workflow binaries organized by commit
|
When browsing CI artifacts, `ci-bins` contains binaries organised
|
||||||
hash/revision, and `releases` are tagged releases. Sort by descending last
|
by commit hash, while `releases` contains tagged versions. Sort
|
||||||
modified date to find the latest.
|
by last modified date to find the most recent builds.
|
||||||
|
|
||||||
These binaries have jemalloc and io_uring statically linked and included with
|
The binaries require jemalloc and io_uring on the host system. Currently
|
||||||
them, so no additional dynamic dependencies need to be installed.
|
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,
|
#### Performance-optimised builds
|
||||||
we recommend using the `-haswell-` optimized binaries. These set
|
|
||||||
`-march=haswell`, which provides the most compatible and highest performance with
|
For x86_64 systems with CPUs from the last ~15 years, use the
|
||||||
optimized binaries. The database backend, RocksDB, benefits most from this as it
|
`-haswell-` optimised binaries for best performance. These
|
||||||
uses hardware-accelerated CRC32 hashing/checksumming, which is critical
|
binaries enable hardware-accelerated CRC32 checksumming in
|
||||||
for performance.
|
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
|
### Compiling
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ This document contains the help content for the `continuwuity` command-line prog
|
|||||||
|
|
||||||
## `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]`
|
**Usage:** `continuwuity [OPTIONS]`
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
Name: continuwuity
|
Name: continuwuity
|
||||||
Version: {{{ git_repo_version }}}
|
Version: {{{ git_repo_version }}}
|
||||||
Release: 1%{?dist}
|
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
|
License: Apache-2.0 AND MIT
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Requires: glibc
|
|||||||
Requires: libstdc++
|
Requires: libstdc++
|
||||||
|
|
||||||
%global _description %{expand:
|
%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}
|
%description %{_description}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "conduwuit_admin"
|
name = "conduwuit_admin"
|
||||||
categories.workspace = true
|
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
keywords.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|||||||
+20
-4
@@ -2,10 +2,17 @@ use clap::Parser;
|
|||||||
use conduwuit::Result;
|
use conduwuit::Result;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
appservice, appservice::AppserviceCommand, check, check::CheckCommand, context::Context,
|
appservice::{self, AppserviceCommand},
|
||||||
debug, debug::DebugCommand, federation, federation::FederationCommand, media,
|
check::{self, CheckCommand},
|
||||||
media::MediaCommand, query, query::QueryCommand, room, room::RoomCommand, server,
|
context::Context,
|
||||||
server::ServerCommand, user, user::UserCommand,
|
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)]
|
#[derive(Debug, Parser)]
|
||||||
@@ -19,6 +26,10 @@ pub enum AdminCommand {
|
|||||||
/// - Commands for managing local users
|
/// - Commands for managing local users
|
||||||
Users(UserCommand),
|
Users(UserCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// - Commands for managing registration tokens
|
||||||
|
Token(TokenCommand),
|
||||||
|
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
/// - Commands for managing rooms
|
/// - Commands for managing rooms
|
||||||
Rooms(RoomCommand),
|
Rooms(RoomCommand),
|
||||||
@@ -64,6 +75,11 @@ pub(super) async fn process(command: AdminCommand, context: &Context<'_>) -> Res
|
|||||||
context.bail_restricted()?;
|
context.bail_restricted()?;
|
||||||
user::process(command, context).await
|
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,
|
| Rooms(command) => room::process(command, context).await,
|
||||||
| Federation(command) => federation::process(command, context).await,
|
| Federation(command) => federation::process(command, context).await,
|
||||||
| Server(command) => server::process(command, context).await,
|
| Server(command) => server::process(command, context).await,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub(crate) mod media;
|
|||||||
pub(crate) mod query;
|
pub(crate) mod query;
|
||||||
pub(crate) mod room;
|
pub(crate) mod room;
|
||||||
pub(crate) mod server;
|
pub(crate) mod server;
|
||||||
|
pub(crate) mod token;
|
||||||
pub(crate) mod user;
|
pub(crate) mod user;
|
||||||
|
|
||||||
extern crate conduwuit_api as api;
|
extern crate conduwuit_api as api;
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
+130
-2
@@ -238,6 +238,7 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
|
|||||||
|
|
||||||
#[admin_command]
|
#[admin_command]
|
||||||
pub(super) async fn suspend(&self, user_id: String) -> Result {
|
pub(super) async fn suspend(&self, user_id: String) -> Result {
|
||||||
|
self.bail_restricted()?;
|
||||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||||
|
|
||||||
if user_id == self.services.globals.server_user {
|
if user_id == self.services.globals.server_user {
|
||||||
@@ -262,6 +263,7 @@ pub(super) async fn suspend(&self, user_id: String) -> Result {
|
|||||||
|
|
||||||
#[admin_command]
|
#[admin_command]
|
||||||
pub(super) async fn unsuspend(&self, user_id: String) -> Result {
|
pub(super) async fn unsuspend(&self, user_id: String) -> Result {
|
||||||
|
self.bail_restricted()?;
|
||||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||||
|
|
||||||
if user_id == self.services.globals.server_user {
|
if user_id == self.services.globals.server_user {
|
||||||
@@ -278,7 +280,12 @@ pub(super) async fn unsuspend(&self, user_id: String) -> Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[admin_command]
|
#[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)?;
|
let user_id = parse_local_user_id(self.services, &username)?;
|
||||||
|
|
||||||
if user_id == self.services.globals.server_user {
|
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}`")
|
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]
|
#[admin_command]
|
||||||
@@ -974,3 +992,113 @@ pub(super) async fn force_leave_remote_room(
|
|||||||
self.write_str(&format!("{user_id} successfully left {room_id} via remote server."))
|
self.write_str(&format!("{user_id} successfully left {room_id} via remote server."))
|
||||||
.await
|
.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
|
/// - Reset user password
|
||||||
ResetPassword {
|
ResetPassword {
|
||||||
|
/// Log out existing sessions
|
||||||
|
#[arg(short, long)]
|
||||||
|
logout: bool,
|
||||||
/// Username of the user for whom the password should be reset
|
/// Username of the user for whom the password should be reset
|
||||||
username: String,
|
username: String,
|
||||||
/// New password for the user, if unspecified one is generated
|
/// New password for the user, if unspecified one is generated
|
||||||
@@ -59,6 +62,18 @@ pub enum UserCommand {
|
|||||||
force: bool,
|
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
|
/// - Suspend a user
|
||||||
///
|
///
|
||||||
/// Suspended users are able to log in, sync, and read messages, but are not
|
/// Suspended users are able to log in, sync, and read messages, but are not
|
||||||
@@ -81,6 +96,42 @@ pub enum UserCommand {
|
|||||||
user_id: String,
|
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
|
/// - List local users in the database
|
||||||
#[clap(alias = "list")]
|
#[clap(alias = "list")]
|
||||||
ListUsers,
|
ListUsers,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "conduwuit_api"
|
name = "conduwuit_api"
|
||||||
categories.workspace = true
|
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
keywords.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|||||||
+39
-13
@@ -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
|
if is_guest
|
||||||
&& (!services.config.allow_guest_registration
|
&& (!services.config.allow_guest_registration
|
||||||
|| (services.config.allow_registration
|
|| (services.config.allow_registration
|
||||||
&& services.globals.registration_token.is_some()))
|
&& services
|
||||||
|
.registration_tokens
|
||||||
|
.get_config_file_token()
|
||||||
|
.is_some()))
|
||||||
{
|
{
|
||||||
info!(
|
info!(
|
||||||
"Guest registration disabled / registration enabled with token configured, \
|
"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: \"{}\"",
|
rejecting registration. Guest's initial device name: \"{}\"",
|
||||||
body.initial_device_display_name.as_deref().unwrap_or("")
|
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) {
|
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;
|
let skip_auth = body.appservice_info.is_some() || is_guest;
|
||||||
|
|
||||||
// Populate required UIAA flows
|
// Populate required UIAA flows
|
||||||
if services.globals.registration_token.is_some() {
|
if services
|
||||||
|
.registration_tokens
|
||||||
|
.iterate_tokens()
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
// Registration token required
|
// Registration token required
|
||||||
uiaainfo.flows.push(AuthFlow {
|
uiaainfo.flows.push(AuthFlow {
|
||||||
stages: vec![AuthType::RegistrationToken],
|
stages: vec![AuthType::RegistrationToken],
|
||||||
@@ -323,7 +336,19 @@ pub(crate) async fn register_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if uiaainfo.flows.is_empty() && !skip_auth {
|
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 {
|
uiaainfo = UiaaInfo {
|
||||||
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
|
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
|
||||||
completed: Vec::new(),
|
completed: Vec::new(),
|
||||||
@@ -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`
|
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
|
||||||
///
|
///
|
||||||
/// Checks if the provided registration token is valid at the time of checking
|
/// Checks if the provided registration token is valid at the time of checking.
|
||||||
///
|
|
||||||
/// Currently does not have any ratelimiting, and this isn't very practical as
|
|
||||||
/// there is only one registration token allowed.
|
|
||||||
pub(crate) async fn check_registration_token_validity(
|
pub(crate) async fn check_registration_token_validity(
|
||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
body: Ruma<check_registration_token_validity::v1::Request>,
|
body: Ruma<check_registration_token_validity::v1::Request>,
|
||||||
) -> Result<check_registration_token_validity::v1::Response> {
|
) -> Result<check_registration_token_validity::v1::Response> {
|
||||||
let Some(reg_token) = services.globals.registration_token.clone() else {
|
// TODO: ratelimit this pretty heavily
|
||||||
return Err!(Request(Forbidden("Server does not allow token registration")));
|
|
||||||
};
|
|
||||||
|
|
||||||
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:
|
/// Runs through all the deactivation steps:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use axum_client_ip::InsecureClientIp;
|
|||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Result, debug_error, err, info,
|
Err, Result, debug_error, err, info,
|
||||||
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
|
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
|
||||||
|
warn,
|
||||||
};
|
};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
@@ -124,6 +125,18 @@ pub(crate) async fn invite_helper(
|
|||||||
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
|
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) {
|
if !services.globals.user_is_local(recipient_user) {
|
||||||
let (pdu, pdu_json, invite_room_state) = {
|
let (pdu, pdu_json, invite_room_state) = {
|
||||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ use ruma::{
|
|||||||
events::{
|
events::{
|
||||||
StateEventType,
|
StateEventType,
|
||||||
room::{
|
room::{
|
||||||
join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent},
|
join_rules::{AllowRule, JoinRule},
|
||||||
member::{MembershipState, RoomMemberEventContent},
|
member::{MembershipState, RoomMemberEventContent},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -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() });
|
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
|
let server_in_room = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.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 {
|
if server_in_room {
|
||||||
join_room_by_id_helper_local(
|
join_room_by_id_helper_local(
|
||||||
services,
|
services,
|
||||||
@@ -347,7 +375,6 @@ pub async fn join_room_by_id_helper(
|
|||||||
.boxed()
|
.boxed()
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
|
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -720,45 +747,51 @@ async fn join_room_by_id_helper_local(
|
|||||||
state_lock: RoomMutexGuard,
|
state_lock: RoomMutexGuard,
|
||||||
) -> Result {
|
) -> Result {
|
||||||
debug_info!("We can join locally");
|
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
|
let mut restricted_join_authorized = None;
|
||||||
.rooms
|
match join_rules {
|
||||||
.state_accessor
|
| JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted) => {
|
||||||
.room_state_get_content::<RoomJoinRulesEventContent>(
|
for restriction in restricted.allow {
|
||||||
room_id,
|
match restriction {
|
||||||
&StateEventType::RoomJoinRules,
|
| AllowRule::RoomMembership(membership) => {
|
||||||
"",
|
if services
|
||||||
)
|
.rooms
|
||||||
.await;
|
.state_cache
|
||||||
|
.is_joined(sender_user, &membership.room_id)
|
||||||
let restriction_rooms = match join_rules_event_content {
|
.await
|
||||||
| Ok(RoomJoinRulesEventContent {
|
{
|
||||||
join_rule: JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted),
|
restricted_join_authorized = Some(true);
|
||||||
}) => restricted
|
break;
|
||||||
.allow
|
}
|
||||||
.into_iter()
|
},
|
||||||
.filter_map(|a| match a {
|
| AllowRule::UnstableSpamChecker => {
|
||||||
| AllowRule::RoomMembership(r) => Some(r.room_id),
|
match services
|
||||||
| _ => None,
|
.antispam
|
||||||
})
|
.meowlnir_accept_make_join(room_id.to_owned(), sender_user.to_owned())
|
||||||
.collect(),
|
.await
|
||||||
| _ => Vec::new(),
|
{
|
||||||
};
|
| Ok(()) => {
|
||||||
|
restricted_join_authorized = Some(true);
|
||||||
let join_authorized_via_users_server: Option<OwnedUserId> = {
|
break;
|
||||||
if restriction_rooms
|
},
|
||||||
.iter()
|
| Err(_) =>
|
||||||
.stream()
|
return Err!(Request(Forbidden(
|
||||||
.any(|restriction_room_id| {
|
"Antispam rejected join request."
|
||||||
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 join_authorized_via_users_server = if restricted_join_authorized.is_none() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
match restricted_join_authorized.unwrap() {
|
||||||
|
| true => services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.local_users_in_room(room_id)
|
.local_users_in_room(room_id)
|
||||||
@@ -774,10 +807,14 @@ async fn join_room_by_id_helper_local(
|
|||||||
.boxed()
|
.boxed()
|
||||||
.next()
|
.next()
|
||||||
.await
|
.await
|
||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned),
|
||||||
} else {
|
| false => {
|
||||||
trace!("No restriction rooms are joined by {sender_user}");
|
warn!(
|
||||||
None
|
"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(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
if restriction_rooms.is_empty()
|
if servers.is_empty() || servers.len() == 1 && services.globals.server_is_ours(&servers[0]) {
|
||||||
&& (servers.is_empty()
|
|
||||||
|| servers.len() == 1 && services.globals.server_is_ours(&servers[0]))
|
|
||||||
{
|
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
warn!(
|
warn!(
|
||||||
"We couldn't do the join locally, maybe federation can help to satisfy the restricted \
|
?error,
|
||||||
join requirements"
|
servers = %servers.len(),
|
||||||
|
"Could not join restricted room locally, attempting remote join",
|
||||||
);
|
);
|
||||||
let Ok((make_join_response, remote_server)) =
|
let Ok((make_join_response, remote_server)) =
|
||||||
make_join_request(services, sender_user, room_id, servers).await
|
make_join_request(services, sender_user, room_id, servers).await
|
||||||
|
|||||||
@@ -178,7 +178,20 @@ pub async fn leave_room(
|
|||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.left_state(user_id, room_id)
|
.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()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use axum_client_ip::InsecureClientIp;
|
|||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Error, Result, debug, err, info,
|
Err, Error, Result, debug, err, info,
|
||||||
utils::{self, ReadyExt, hash},
|
utils::{self, ReadyExt, hash},
|
||||||
|
warn,
|
||||||
};
|
};
|
||||||
use conduwuit_core::{debug_error, debug_warn};
|
use conduwuit_core::{debug_error, debug_warn};
|
||||||
use conduwuit_service::{Services, uiaa::SESSION_ID_LENGTH};
|
use conduwuit_service::{Services, uiaa::SESSION_ID_LENGTH};
|
||||||
@@ -12,6 +13,7 @@ use futures::StreamExt;
|
|||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedUserId, UserId,
|
OwnedUserId, UserId,
|
||||||
api::client::{
|
api::client::{
|
||||||
|
error::ErrorKind,
|
||||||
session::{
|
session::{
|
||||||
get_login_token,
|
get_login_token,
|
||||||
get_login_types::{
|
get_login_types::{
|
||||||
@@ -184,6 +186,15 @@ pub(crate) async fn handle_login(
|
|||||||
return Err!(Request(Unknown("User ID does not belong to this homeserver")));
|
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 {
|
if cfg!(feature = "ldap") && services.config.ldap.enable {
|
||||||
match Box::pin(ldap_login(services, &user_id, &lowercased_user_id, password)).await {
|
match Box::pin(ldap_login(services, &user_id, &lowercased_user_id, password)).await {
|
||||||
| Ok(user_id) => Ok(user_id),
|
| Ok(user_id) => Ok(user_id),
|
||||||
|
|||||||
+24
-6
@@ -137,12 +137,30 @@ pub(super) async fn auth(
|
|||||||
| (
|
| (
|
||||||
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None,
|
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None,
|
||||||
Token::User((user_id, device_id)),
|
Token::User((user_id, device_id)),
|
||||||
) => Ok(Auth {
|
) => {
|
||||||
origin: None,
|
let is_locked = services.users.is_locked(&user_id).await.map_err(|e| {
|
||||||
sender_user: Some(user_id),
|
err!(Request(Forbidden(warn!("Failed to check user lock status: {e}"))))
|
||||||
sender_device: Some(device_id),
|
})?;
|
||||||
appservice_info: None,
|
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) =>
|
| (AuthScheme::ServerSignatures, Token::None) =>
|
||||||
Ok(auth_server(services, request, json_body).await?),
|
Ok(auth_server(services, request, json_body).await?),
|
||||||
| (
|
| (
|
||||||
|
|||||||
@@ -148,6 +148,15 @@ pub(crate) async fn create_invite_route(
|
|||||||
return Err!(Request(Forbidden("This server does not allow room invites.")));
|
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 invite_state = body.invite_room_state.clone();
|
||||||
|
|
||||||
let mut event: JsonObject = serde_json::from_str(body.event.get())
|
let mut event: JsonObject = serde_json::from_str(body.event.get())
|
||||||
|
|||||||
+50
-22
@@ -1,7 +1,7 @@
|
|||||||
|
use std::borrow::ToOwned;
|
||||||
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{
|
use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn};
|
||||||
Err, Error, Result, debug_info, info, matrix::pdu::PduBuilder, utils::IterStream, warn,
|
|
||||||
};
|
|
||||||
use conduwuit_service::Services;
|
use conduwuit_service::Services;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
@@ -122,6 +122,16 @@ pub(crate) async fn create_join_event_template_route(
|
|||||||
None
|
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
|
let (_pdu, mut pdu_json) = services
|
||||||
.rooms
|
.rooms
|
||||||
@@ -136,7 +146,6 @@ pub(crate) async fn create_join_event_template_route(
|
|||||||
&state_lock,
|
&state_lock,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
|
|
||||||
// room v3 and above removed the "event_id" field from remote PDU format
|
// 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);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.allow
|
for allow_rule in &r.allow {
|
||||||
.iter()
|
match allow_rule {
|
||||||
.filter_map(|rule| {
|
| AllowRule::RoomMembership(membership) => {
|
||||||
if let AllowRule::RoomMembership(membership) = rule {
|
if services
|
||||||
Some(membership)
|
.rooms
|
||||||
} else {
|
.state_cache
|
||||||
None
|
.is_joined(user_id, &membership.room_id)
|
||||||
}
|
.await
|
||||||
})
|
{
|
||||||
.stream()
|
debug!(
|
||||||
.any(|m| services.rooms.state_cache.is_joined(user_id, &m.room_id))
|
"User {} is allowed to join room {} via membership in room {}",
|
||||||
.await
|
user_id, room_id, membership.room_id
|
||||||
{
|
);
|
||||||
Ok(true)
|
return Ok(true);
|
||||||
} else {
|
}
|
||||||
Err!(Request(UnableToAuthorizeJoin(
|
},
|
||||||
"Joining user is not known to be in any required room."
|
| 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(
|
pub(crate) fn maybe_strip_event_id(
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "conduwuit_build_metadata"
|
name = "conduwuit_build_metadata"
|
||||||
categories.workspace = true
|
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
keywords.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "conduwuit_core"
|
name = "conduwuit_core"
|
||||||
categories.workspace = true
|
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
keywords.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|||||||
@@ -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 {
|
if config.max_request_size < 10_000_000 {
|
||||||
return Err!(Config(
|
return Err!(Config(
|
||||||
"max_request_size",
|
"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
|
if config.allow_registration
|
||||||
&& config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
|
&& config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
|
||||||
&& config.registration_token.is_none()
|
&& config.registration_token.is_none()
|
||||||
&& config.registration_token_file.is_none()
|
|
||||||
{
|
{
|
||||||
warn!(
|
warn!(
|
||||||
"Open registration is enabled via setting \
|
"Open registration is enabled via setting \
|
||||||
|
|||||||
+64
-15
@@ -18,7 +18,7 @@ use figment::providers::{Env, Format, Toml};
|
|||||||
pub use figment::{Figment, value::Value as FigmentValue};
|
pub use figment::{Figment, value::Value as FigmentValue};
|
||||||
use regex::RegexSet;
|
use regex::RegexSet;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
|
OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
|
||||||
api::client::discovery::discover_support::ContactRole,
|
api::client::discovery::discover_support::ContactRole,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, de::IgnoredAny};
|
use serde::{Deserialize, de::IgnoredAny};
|
||||||
@@ -53,7 +53,8 @@ use crate::{Result, err, error::Error, utils::sys};
|
|||||||
### For more information, see:
|
### For more information, see:
|
||||||
### https://continuwuity.org/configuration.html
|
### 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 {
|
pub struct Config {
|
||||||
// Paths to config file(s). Not supposed to be set manually in the config file,
|
// 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`
|
/// `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
|
/// If you would like registration only via token reg, please configure
|
||||||
/// `registration_token` or `registration_token_file`.
|
/// `registration_token`.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub allow_registration: bool,
|
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`
|
/// `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
|
||||||
/// to true to allow open registration without any conditions.
|
/// 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"
|
/// example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
|
||||||
///
|
///
|
||||||
/// display: sensitive
|
/// display: sensitive
|
||||||
pub registration_token: Option<String>,
|
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
|
/// The public site key for reCaptcha. If this is provided, reCaptcha
|
||||||
/// becomes required during registration. If both captcha *and*
|
/// becomes required during registration. If both captcha *and*
|
||||||
/// registration token are enabled, both will be required during
|
/// registration token are enabled, both will be required during
|
||||||
@@ -1887,7 +1880,7 @@ pub struct Config {
|
|||||||
|
|
||||||
/// Enable the tokio-console. This option is only relevant to developers.
|
/// 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
|
/// https://continuwuity.org/development.html#debugging-with-tokio-console
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub tokio_console: bool,
|
pub tokio_console: bool,
|
||||||
@@ -2024,6 +2017,10 @@ pub struct Config {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub ldap: LdapConfig,
|
pub ldap: LdapConfig,
|
||||||
|
|
||||||
|
/// Configuration for antispam support
|
||||||
|
#[serde(default)]
|
||||||
|
pub antispam: Option<Antispam>,
|
||||||
|
|
||||||
// external structure; separate section
|
// external structure; separate section
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub blurhashing: BlurhashConfig,
|
pub blurhashing: BlurhashConfig,
|
||||||
@@ -2240,7 +2237,58 @@ struct ListeningAddr {
|
|||||||
addrs: Either<IpAddr, Vec<IpAddr>>,
|
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",
|
"cache_capacity",
|
||||||
"conduit_cache_capacity_modifier",
|
"conduit_cache_capacity_modifier",
|
||||||
"max_concurrent_requests",
|
"max_concurrent_requests",
|
||||||
@@ -2250,6 +2298,7 @@ const DEPRECATED_KEYS: &[&str; 9] = &[
|
|||||||
"well_known_support_role",
|
"well_known_support_role",
|
||||||
"well_known_support_email",
|
"well_known_support_email",
|
||||||
"well_known_support_mxid",
|
"well_known_support_mxid",
|
||||||
|
"registration_token_file",
|
||||||
];
|
];
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
|||||||
@@ -75,10 +75,12 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
|
|||||||
| ThreepidDenied
|
| ThreepidDenied
|
||||||
| InviteBlocked
|
| InviteBlocked
|
||||||
| WrongRoomKeysVersion { .. }
|
| WrongRoomKeysVersion { .. }
|
||||||
|
| UserSuspended
|
||||||
| Forbidden { .. } => StatusCode::FORBIDDEN,
|
| Forbidden { .. } => StatusCode::FORBIDDEN,
|
||||||
|
|
||||||
// 401
|
// 401
|
||||||
| UnknownToken { .. } | MissingToken | Unauthorized => StatusCode::UNAUTHORIZED,
|
| UnknownToken { .. } | MissingToken | Unauthorized | UserLocked =>
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
|
||||||
// 400
|
// 400
|
||||||
| _ => StatusCode::BAD_REQUEST,
|
| _ => StatusCode::BAD_REQUEST,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "conduwuit_database"
|
name = "conduwuit_database"
|
||||||
categories.workspace = true
|
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
keywords.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|||||||
@@ -141,6 +141,10 @@ pub(super) static MAPS: &[Descriptor] = &[
|
|||||||
name: "referencedevents",
|
name: "referencedevents",
|
||||||
..descriptor::RANDOM
|
..descriptor::RANDOM
|
||||||
},
|
},
|
||||||
|
Descriptor {
|
||||||
|
name: "registrationtoken_info",
|
||||||
|
..descriptor::RANDOM_SMALL
|
||||||
|
},
|
||||||
Descriptor {
|
Descriptor {
|
||||||
name: "roomid_invitedcount",
|
name: "roomid_invitedcount",
|
||||||
..descriptor::RANDOM_SMALL
|
..descriptor::RANDOM_SMALL
|
||||||
@@ -386,6 +390,14 @@ pub(super) static MAPS: &[Descriptor] = &[
|
|||||||
name: "userid_suspension",
|
name: "userid_suspension",
|
||||||
..descriptor::RANDOM_SMALL
|
..descriptor::RANDOM_SMALL
|
||||||
},
|
},
|
||||||
|
Descriptor {
|
||||||
|
name: "userid_lock",
|
||||||
|
..descriptor::RANDOM_SMALL
|
||||||
|
},
|
||||||
|
Descriptor {
|
||||||
|
name: "userid_logindisabled",
|
||||||
|
..descriptor::RANDOM_SMALL
|
||||||
|
},
|
||||||
Descriptor {
|
Descriptor {
|
||||||
name: "userid_presenceid",
|
name: "userid_presenceid",
|
||||||
..descriptor::RANDOM_SMALL
|
..descriptor::RANDOM_SMALL
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "conduwuit_macros"
|
name = "conduwuit_macros"
|
||||||
categories.workspace = true
|
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
keywords.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|||||||
+2
-6
@@ -2,15 +2,12 @@
|
|||||||
name = "conduwuit"
|
name = "conduwuit"
|
||||||
default-run = "conduwuit"
|
default-run = "conduwuit"
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
categories.workspace = true
|
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
homepage.workspace = true
|
homepage.workspace = true
|
||||||
keywords.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
rust-version.workspace = true
|
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
metadata.crane.workspace = true
|
metadata.crane.workspace = true
|
||||||
|
|
||||||
@@ -23,14 +20,13 @@ crate-type = [
|
|||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
name = "continuwuity"
|
name = "continuwuity"
|
||||||
maintainer = "continuwuity developers <contact@continuwuity.org>"
|
maintainer = "Continuwuity Team and contributors <team@continuwuity.org>"
|
||||||
copyright = "2024, continuwuity developers"
|
|
||||||
license-file = ["../../LICENSE", "3"]
|
license-file = ["../../LICENSE", "3"]
|
||||||
depends = "$auto, ca-certificates"
|
depends = "$auto, ca-certificates"
|
||||||
breaks = ["conduwuit (<<0.5.0)"]
|
breaks = ["conduwuit (<<0.5.0)"]
|
||||||
replaces = ["conduwuit (<<0.5.0)"]
|
replaces = ["conduwuit (<<0.5.0)"]
|
||||||
extended-description = """\
|
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"
|
section = "net"
|
||||||
priority = "optional"
|
priority = "optional"
|
||||||
conf-files = ["/etc/conduwuit/conduwuit.toml"]
|
conf-files = ["/etc/conduwuit/conduwuit.toml"]
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "conduwuit_router"
|
name = "conduwuit_router"
|
||||||
categories.workspace = true
|
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
keywords.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "conduwuit_service"
|
name = "conduwuit_service"
|
||||||
categories.workspace = true
|
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
keywords.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
use std::{fmt::Debug, sync::Arc};
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use conduwuit::{Result, config::Antispam, debug};
|
||||||
|
use ruma::{OwnedRoomId, OwnedUserId, draupnir_antispam, meowlnir_antispam};
|
||||||
|
|
||||||
|
use crate::{client, config, sending, service::Dep};
|
||||||
|
|
||||||
|
struct Services {
|
||||||
|
config: Dep<config::Service>,
|
||||||
|
client: Dep<client::Service>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Service {
|
||||||
|
services: Services,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl crate::Service for Service {
|
||||||
|
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||||
|
Ok(Arc::new(Self {
|
||||||
|
services: Services {
|
||||||
|
client: args.depend::<client::Service>("client"),
|
||||||
|
config: args.depend::<config::Service>("config"),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service {
|
||||||
|
async fn send_antispam_request<T>(
|
||||||
|
&self,
|
||||||
|
base_url: &str,
|
||||||
|
secret: &str,
|
||||||
|
request: T,
|
||||||
|
) -> Result<T::IncomingResponse>
|
||||||
|
where
|
||||||
|
T: ruma::api::OutgoingRequest + Debug + Send,
|
||||||
|
{
|
||||||
|
sending::antispam::send_antispam_request(
|
||||||
|
&self.services.client.appservice,
|
||||||
|
base_url,
|
||||||
|
secret,
|
||||||
|
request,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks with the antispam service whether `inviter` may invite `invitee`
|
||||||
|
/// to `room_id`.
|
||||||
|
///
|
||||||
|
/// If no antispam service is configured, this always returns `Ok(())`.
|
||||||
|
/// If an error is returned, the invite should be blocked - the antispam
|
||||||
|
/// service was unreachable, or refused the invite.
|
||||||
|
pub async fn user_may_invite(
|
||||||
|
&self,
|
||||||
|
inviter: OwnedUserId,
|
||||||
|
invitee: OwnedUserId,
|
||||||
|
room_id: OwnedRoomId,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some(config) = &self.services.config.antispam {
|
||||||
|
let result = if let Some(meowlnir) = &config.meowlnir {
|
||||||
|
debug!(?room_id, ?inviter, ?invitee, "Asking meowlnir for user_may_invite");
|
||||||
|
self.send_antispam_request(
|
||||||
|
meowlnir.base_url.as_str(),
|
||||||
|
&meowlnir.secret,
|
||||||
|
meowlnir_antispam::user_may_invite::v1::Request::new(
|
||||||
|
meowlnir.management_room.clone(),
|
||||||
|
inviter,
|
||||||
|
invitee,
|
||||||
|
room_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.inspect(|_| debug!("meowlnir allowed the invite"))
|
||||||
|
.inspect_err(|e| debug!("meowlnir denied the invite: {e:?}"))
|
||||||
|
.map(|_| ())
|
||||||
|
} else if let Some(draupnir) = &config.draupnir {
|
||||||
|
debug!(?room_id, ?inviter, ?invitee, "Asking draupnir for user_may_invite");
|
||||||
|
self.send_antispam_request(
|
||||||
|
draupnir.base_url.as_str(),
|
||||||
|
&draupnir.secret,
|
||||||
|
draupnir_antispam::user_may_invite::v1::Request::new(
|
||||||
|
room_id, inviter, invitee,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.inspect(|_| debug!("draupnir allowed the invite"))
|
||||||
|
.inspect_err(|e| debug!("draupnir denied the invite: {e:?}"))
|
||||||
|
.map(|_| ())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks with the antispam service whether `user_id` may join `room_id`.
|
||||||
|
pub async fn user_may_join_room(
|
||||||
|
&self,
|
||||||
|
user_id: OwnedUserId,
|
||||||
|
room_id: OwnedRoomId,
|
||||||
|
is_invited: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some(config) = &self.services.config.antispam {
|
||||||
|
let result = if let Some(meowlnir) = &config.meowlnir {
|
||||||
|
debug!(?room_id, ?user_id, ?is_invited, "Asking meowlnir for user_may_join_room");
|
||||||
|
self.send_antispam_request(
|
||||||
|
meowlnir.base_url.as_str(),
|
||||||
|
&meowlnir.secret,
|
||||||
|
meowlnir_antispam::user_may_join_room::v1::Request::new(
|
||||||
|
meowlnir.management_room.clone(),
|
||||||
|
user_id,
|
||||||
|
room_id,
|
||||||
|
is_invited,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.inspect(|_| debug!("meowlnir allowed the join"))
|
||||||
|
.inspect_err(|e| debug!("meowlnir denied the join: {e:?}"))
|
||||||
|
.map(|_| ())
|
||||||
|
} else if let Some(draupnir) = &config.draupnir {
|
||||||
|
debug!(?room_id, ?user_id, ?is_invited, "Asking draupnir for user_may_join_room");
|
||||||
|
self.send_antispam_request(
|
||||||
|
draupnir.base_url.as_str(),
|
||||||
|
&draupnir.secret,
|
||||||
|
draupnir_antispam::user_may_join_room::v1::Request::new(
|
||||||
|
user_id, room_id, is_invited,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.inspect(|_| debug!("draupnir allowed the join"))
|
||||||
|
.inspect_err(|e| debug!("draupnir denied the join: {e:?}"))
|
||||||
|
.map(|_| ())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks with Meowlnir whether the incoming federated `make_join` request
|
||||||
|
/// should be allowed. Applies the `fi.mau.spam_checker` join rule.
|
||||||
|
pub async fn meowlnir_accept_make_join(
|
||||||
|
&self,
|
||||||
|
room_id: OwnedRoomId,
|
||||||
|
user_id: OwnedUserId,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some(Antispam { meowlnir: Some(meowlnir), .. }) = &self.services.config.antispam {
|
||||||
|
debug!(?room_id, ?user_id, "Asking meowlnir for accept_make_join");
|
||||||
|
self.send_antispam_request(
|
||||||
|
meowlnir.base_url.as_str(),
|
||||||
|
&meowlnir.secret,
|
||||||
|
meowlnir_antispam::accept_make_join::v1::Request::new(
|
||||||
|
meowlnir.management_room.clone(),
|
||||||
|
user_id,
|
||||||
|
room_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.inspect(|_| debug!("meowlnir allowed the make_join"))
|
||||||
|
.inspect_err(|e| debug!("meowlnir denied the make_join: {e:?}"))
|
||||||
|
.map(|_| ())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether all joins should be checked with Meowlnir.
|
||||||
|
/// Is always false if Meowlnir is not configured.
|
||||||
|
pub fn check_all_joins(&self) -> bool {
|
||||||
|
if let Some(Antispam { meowlnir: Some(cfg), .. }) = &self.services.config.antispam {
|
||||||
|
cfg.check_all_joins
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,6 @@ pub struct Service {
|
|||||||
pub server_user: OwnedUserId,
|
pub server_user: OwnedUserId,
|
||||||
pub admin_alias: OwnedRoomAliasId,
|
pub admin_alias: OwnedRoomAliasId,
|
||||||
pub turn_secret: String,
|
pub turn_secret: String,
|
||||||
pub registration_token: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries
|
type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries
|
||||||
@@ -41,19 +40,6 @@ impl crate::Service for Service {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let registration_token = config.registration_token_file.as_ref().map_or_else(
|
|
||||||
|| config.registration_token.clone(),
|
|
||||||
|path| {
|
|
||||||
let Ok(token) = std::fs::read_to_string(path).inspect_err(|e| {
|
|
||||||
error!("Failed to read the registration token file: {e}");
|
|
||||||
}) else {
|
|
||||||
return config.registration_token.clone();
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(token.trim().to_owned())
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Arc::new(Self {
|
Ok(Arc::new(Self {
|
||||||
db,
|
db,
|
||||||
server: args.server.clone(),
|
server: args.server.clone(),
|
||||||
@@ -66,7 +52,6 @@ impl crate::Service for Service {
|
|||||||
)
|
)
|
||||||
.expect("@conduit:server_name is valid"),
|
.expect("@conduit:server_name is valid"),
|
||||||
turn_secret,
|
turn_secret,
|
||||||
registration_token,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -1,6 +1,8 @@
|
|||||||
#![type_length_limit = "8192"]
|
#![type_length_limit = "8192"]
|
||||||
#![allow(refining_impl_trait)]
|
#![allow(refining_impl_trait)]
|
||||||
|
|
||||||
|
extern crate conduwuit_core as conduwuit;
|
||||||
|
extern crate conduwuit_database as database;
|
||||||
mod manager;
|
mod manager;
|
||||||
mod migrations;
|
mod migrations;
|
||||||
mod service;
|
mod service;
|
||||||
@@ -10,6 +12,7 @@ pub mod state;
|
|||||||
pub mod account_data;
|
pub mod account_data;
|
||||||
pub mod admin;
|
pub mod admin;
|
||||||
pub mod announcements;
|
pub mod announcements;
|
||||||
|
pub mod antispam;
|
||||||
pub mod appservice;
|
pub mod appservice;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
@@ -21,6 +24,7 @@ pub mod media;
|
|||||||
pub mod moderation;
|
pub mod moderation;
|
||||||
pub mod presence;
|
pub mod presence;
|
||||||
pub mod pusher;
|
pub mod pusher;
|
||||||
|
pub mod registration_tokens;
|
||||||
pub mod resolver;
|
pub mod resolver;
|
||||||
pub mod rooms;
|
pub mod rooms;
|
||||||
pub mod sending;
|
pub mod sending;
|
||||||
@@ -30,9 +34,6 @@ pub mod transaction_ids;
|
|||||||
pub mod uiaa;
|
pub mod uiaa;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
||||||
extern crate conduwuit_core as conduwuit;
|
|
||||||
extern crate conduwuit_database as database;
|
|
||||||
|
|
||||||
use ctor::{ctor, dtor};
|
use ctor::{ctor, dtor};
|
||||||
pub(crate) use service::{Args, Dep, Service};
|
pub(crate) use service::{Args, Dep, Service};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
use std::{sync::Arc, time::SystemTime};
|
||||||
|
|
||||||
|
use conduwuit::utils::{
|
||||||
|
self,
|
||||||
|
stream::{ReadyExt, TryIgnore},
|
||||||
|
};
|
||||||
|
use database::{Database, Deserialized, Json, Map};
|
||||||
|
use futures::Stream;
|
||||||
|
use ruma::OwnedUserId;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub(super) struct Data {
|
||||||
|
registrationtoken_info: Arc<Map>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata of a registration token.
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct DatabaseTokenInfo {
|
||||||
|
/// The admin user who created this token.
|
||||||
|
pub creator: OwnedUserId,
|
||||||
|
/// The number of times this token has been used to create an account.
|
||||||
|
pub uses: u64,
|
||||||
|
/// When this token will expire, if it expires.
|
||||||
|
pub expires: Option<TokenExpires>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DatabaseTokenInfo {
|
||||||
|
pub(super) fn new(creator: OwnedUserId, expires: Option<TokenExpires>) -> Self {
|
||||||
|
Self { creator, uses: 0, expires }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine whether this token info represents a valid token, i.e. one
|
||||||
|
/// that has not expired according to its [`Self::expires`] property. If
|
||||||
|
/// [`Self::expires`] is [`None`], this function will always return `true`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_valid(&self) -> bool {
|
||||||
|
match self.expires {
|
||||||
|
| Some(TokenExpires::AfterUses(max_uses)) => self.uses < max_uses,
|
||||||
|
| Some(TokenExpires::AfterTime(expiry_time)) => {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
|
||||||
|
expiry_time >= now
|
||||||
|
},
|
||||||
|
| None => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for DatabaseTokenInfo {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Token created by {} and used {} times. ", &self.creator, self.uses)?;
|
||||||
|
if let Some(expires) = &self.expires {
|
||||||
|
write!(f, "{expires}.")?;
|
||||||
|
} else {
|
||||||
|
write!(f, "Never expires.")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum TokenExpires {
|
||||||
|
AfterUses(u64),
|
||||||
|
AfterTime(SystemTime),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for TokenExpires {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
| Self::AfterUses(max_uses) => write!(f, "Expires after {max_uses} uses"),
|
||||||
|
| Self::AfterTime(max_age) => {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let formatted_expiry = utils::time::format(*max_age, "%+");
|
||||||
|
|
||||||
|
match max_age.duration_since(now) {
|
||||||
|
| Ok(duration) => write!(
|
||||||
|
f,
|
||||||
|
"Expires in {} ({formatted_expiry})",
|
||||||
|
utils::time::pretty(duration)
|
||||||
|
),
|
||||||
|
| Err(_) => write!(f, "Expired at {formatted_expiry}"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Data {
|
||||||
|
pub(super) fn new(db: &Arc<Database>) -> Self {
|
||||||
|
Self {
|
||||||
|
registrationtoken_info: db["registrationtoken_info"].clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Associate a registration token with its metadata in the database.
|
||||||
|
pub(super) fn save_token(&self, token: &str, info: &DatabaseTokenInfo) {
|
||||||
|
self.registrationtoken_info.raw_put(token, Json(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a registration token.
|
||||||
|
pub(super) fn revoke_token(&self, token: &str) { self.registrationtoken_info.remove(token); }
|
||||||
|
|
||||||
|
/// Look up a registration token's metadata.
|
||||||
|
pub(super) async fn lookup_token_info(&self, token: &str) -> Option<DatabaseTokenInfo> {
|
||||||
|
self.registrationtoken_info
|
||||||
|
.get(token)
|
||||||
|
.await
|
||||||
|
.deserialized()
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all valid tokens and delete expired ones.
|
||||||
|
pub(super) fn iterate_and_clean_tokens(
|
||||||
|
&self,
|
||||||
|
) -> impl Stream<Item = (&str, DatabaseTokenInfo)> + Send + '_ {
|
||||||
|
self.registrationtoken_info
|
||||||
|
.stream()
|
||||||
|
.ignore_err()
|
||||||
|
.ready_filter_map(|item: (&str, DatabaseTokenInfo)| {
|
||||||
|
if item.1.is_valid() {
|
||||||
|
Some(item)
|
||||||
|
} else {
|
||||||
|
self.registrationtoken_info.remove(item.0);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
mod data;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use conduwuit::{Err, Result, utils};
|
||||||
|
use data::Data;
|
||||||
|
pub use data::{DatabaseTokenInfo, TokenExpires};
|
||||||
|
use futures::{Stream, StreamExt, stream};
|
||||||
|
use ruma::OwnedUserId;
|
||||||
|
|
||||||
|
use crate::{Dep, config};
|
||||||
|
|
||||||
|
const RANDOM_TOKEN_LENGTH: usize = 16;
|
||||||
|
|
||||||
|
pub struct Service {
|
||||||
|
db: Data,
|
||||||
|
services: Services,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Services {
|
||||||
|
config: Dep<config::Service>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A validated registration token which may be used to create an account.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ValidToken {
|
||||||
|
pub token: String,
|
||||||
|
pub source: ValidTokenSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ValidToken {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "`{}` --- {}", self.token, &self.source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<str> for ValidToken {
|
||||||
|
fn eq(&self, other: &str) -> bool { self.token == other }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The source of a valid database token.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ValidTokenSource {
|
||||||
|
/// The static token set in the homeserver's config file, which is
|
||||||
|
/// always valid.
|
||||||
|
ConfigFile,
|
||||||
|
/// A database token which has been checked to be valid.
|
||||||
|
Database(DatabaseTokenInfo),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ValidTokenSource {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
| Self::ConfigFile => write!(f, "Token defined in config."),
|
||||||
|
| Self::Database(info) => info.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::Service for Service {
|
||||||
|
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||||
|
Ok(Arc::new(Self {
|
||||||
|
db: Data::new(args.db),
|
||||||
|
services: Services {
|
||||||
|
config: args.depend::<config::Service>("config"),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service {
|
||||||
|
/// Issue a new registration token and save it in the database.
|
||||||
|
pub fn issue_token(
|
||||||
|
&self,
|
||||||
|
creator: OwnedUserId,
|
||||||
|
expires: Option<TokenExpires>,
|
||||||
|
) -> (String, DatabaseTokenInfo) {
|
||||||
|
let token = utils::random_string(RANDOM_TOKEN_LENGTH);
|
||||||
|
let info = DatabaseTokenInfo::new(creator, expires);
|
||||||
|
|
||||||
|
self.db.save_token(&token, &info);
|
||||||
|
(token, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the registration token set in the config file, if it exists.
|
||||||
|
pub fn get_config_file_token(&self) -> Option<ValidToken> {
|
||||||
|
self.services
|
||||||
|
.config
|
||||||
|
.registration_token
|
||||||
|
.clone()
|
||||||
|
.map(|token| ValidToken {
|
||||||
|
token,
|
||||||
|
source: ValidTokenSource::ConfigFile,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate a registration token.
|
||||||
|
pub async fn validate_token(&self, token: String) -> Option<ValidToken> {
|
||||||
|
// Check the registration token in the config first
|
||||||
|
if self
|
||||||
|
.get_config_file_token()
|
||||||
|
.is_some_and(|valid_token| valid_token == *token)
|
||||||
|
{
|
||||||
|
return Some(ValidToken {
|
||||||
|
token,
|
||||||
|
source: ValidTokenSource::ConfigFile,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check the database
|
||||||
|
if let Some(token_info) = self.db.lookup_token_info(&token).await
|
||||||
|
&& token_info.is_valid()
|
||||||
|
{
|
||||||
|
return Some(ValidToken {
|
||||||
|
token,
|
||||||
|
source: ValidTokenSource::Database(token_info),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise it's not valid
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark a valid token as having been used to create a new account.
|
||||||
|
pub fn mark_token_as_used(&self, ValidToken { token, source }: ValidToken) {
|
||||||
|
match source {
|
||||||
|
| ValidTokenSource::ConfigFile => {
|
||||||
|
// we don't track uses of the config file token, do nothing
|
||||||
|
},
|
||||||
|
| ValidTokenSource::Database(mut info) => {
|
||||||
|
info.uses = info.uses.saturating_add(1);
|
||||||
|
|
||||||
|
self.db.save_token(&token, &info);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to revoke a valid token.
|
||||||
|
///
|
||||||
|
/// Note that some tokens (like the one set in the config file) cannot be
|
||||||
|
/// revoked.
|
||||||
|
pub fn revoke_token(&self, ValidToken { token, source }: ValidToken) -> Result {
|
||||||
|
match source {
|
||||||
|
| ValidTokenSource::ConfigFile => {
|
||||||
|
// the config file token cannot be revoked
|
||||||
|
Err!(
|
||||||
|
"The token set in the config file cannot be revoked. Edit the config file \
|
||||||
|
to change it."
|
||||||
|
)
|
||||||
|
},
|
||||||
|
| ValidTokenSource::Database(_) => {
|
||||||
|
self.db.revoke_token(&token);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all valid registration tokens.
|
||||||
|
pub fn iterate_tokens(&self) -> impl Stream<Item = ValidToken> + Send + '_ {
|
||||||
|
let db_tokens = self
|
||||||
|
.db
|
||||||
|
.iterate_and_clean_tokens()
|
||||||
|
.map(|(token, info)| ValidToken {
|
||||||
|
token: token.to_owned(),
|
||||||
|
source: ValidTokenSource::Database(info),
|
||||||
|
});
|
||||||
|
|
||||||
|
stream::iter(self.get_config_file_token()).chain(db_tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
use std::{fmt::Debug, mem};
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use conduwuit::{Err, Result, debug_error, err, utils, warn};
|
||||||
|
use reqwest::Client;
|
||||||
|
use ruma::api::{IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken};
|
||||||
|
|
||||||
|
/// Sends a request to an antispam service
|
||||||
|
pub(crate) async fn send_antispam_request<T>(
|
||||||
|
client: &Client,
|
||||||
|
base_url: &str,
|
||||||
|
secret: &str,
|
||||||
|
request: T,
|
||||||
|
) -> Result<T::IncomingResponse>
|
||||||
|
where
|
||||||
|
T: OutgoingRequest + Debug + Send,
|
||||||
|
{
|
||||||
|
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_15];
|
||||||
|
let http_request = request
|
||||||
|
.try_into_http_request::<BytesMut>(base_url, SendAccessToken::Always(secret), &VERSIONS)?
|
||||||
|
.map(BytesMut::freeze);
|
||||||
|
let reqwest_request = reqwest::Request::try_from(http_request)?;
|
||||||
|
|
||||||
|
let mut response = client.execute(reqwest_request).await.map_err(|e| {
|
||||||
|
warn!("Could not send request to antispam: {e:?}");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// reqwest::Response -> http::Response conversion
|
||||||
|
let status = response.status();
|
||||||
|
let mut http_response_builder = http::Response::builder()
|
||||||
|
.status(status)
|
||||||
|
.version(response.version());
|
||||||
|
mem::swap(
|
||||||
|
response.headers_mut(),
|
||||||
|
http_response_builder
|
||||||
|
.headers_mut()
|
||||||
|
.expect("http::response::Builder is usable"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let body = response.bytes().await?; // TODO: handle timeout
|
||||||
|
|
||||||
|
if !status.is_success() {
|
||||||
|
debug_error!("Antispam response bytes: {:?}", utils::string_from_bytes(&body));
|
||||||
|
return match status {
|
||||||
|
| http::StatusCode::FORBIDDEN =>
|
||||||
|
Err!(Request(Forbidden("Request was rejected by antispam service.",))),
|
||||||
|
| _ => Err!(BadServerResponse(warn!(
|
||||||
|
"Antispam returned unsuccessful HTTP response {status}",
|
||||||
|
))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = T::IncomingResponse::try_from_http_response(
|
||||||
|
http_response_builder
|
||||||
|
.body(body)
|
||||||
|
.expect("reqwest body is valid http body"),
|
||||||
|
);
|
||||||
|
|
||||||
|
response.map_err(|e| {
|
||||||
|
err!(BadServerResponse(warn!(
|
||||||
|
"Antispam returned invalid/malformed response bytes: {e}",
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod antispam;
|
||||||
mod appservice;
|
mod appservice;
|
||||||
mod data;
|
mod data;
|
||||||
mod dest;
|
mod dest;
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ use futures::{Stream, StreamExt, TryStreamExt};
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account_data, admin, announcements, appservice, client, config, emergency, federation,
|
account_data, admin, announcements, antispam, appservice, client, config, emergency,
|
||||||
globals, key_backups,
|
federation, globals, key_backups,
|
||||||
manager::Manager,
|
manager::Manager,
|
||||||
media, moderation, presence, pusher, resolver, rooms, sending, server_keys, service,
|
media, moderation, presence, pusher, registration_tokens, resolver, rooms, sending,
|
||||||
service::{Args, Map, Service},
|
server_keys,
|
||||||
|
service::{self, Args, Map, Service},
|
||||||
sync, transaction_ids, uiaa, users,
|
sync, transaction_ids, uiaa, users,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ pub struct Services {
|
|||||||
pub media: Arc<media::Service>,
|
pub media: Arc<media::Service>,
|
||||||
pub presence: Arc<presence::Service>,
|
pub presence: Arc<presence::Service>,
|
||||||
pub pusher: Arc<pusher::Service>,
|
pub pusher: Arc<pusher::Service>,
|
||||||
|
pub registration_tokens: Arc<registration_tokens::Service>,
|
||||||
pub resolver: Arc<resolver::Service>,
|
pub resolver: Arc<resolver::Service>,
|
||||||
pub rooms: rooms::Service,
|
pub rooms: rooms::Service,
|
||||||
pub federation: Arc<federation::Service>,
|
pub federation: Arc<federation::Service>,
|
||||||
@@ -39,6 +41,7 @@ pub struct Services {
|
|||||||
pub users: Arc<users::Service>,
|
pub users: Arc<users::Service>,
|
||||||
pub moderation: Arc<moderation::Service>,
|
pub moderation: Arc<moderation::Service>,
|
||||||
pub announcements: Arc<announcements::Service>,
|
pub announcements: Arc<announcements::Service>,
|
||||||
|
pub antispam: Arc<antispam::Service>,
|
||||||
|
|
||||||
manager: Mutex<Option<Arc<Manager>>>,
|
manager: Mutex<Option<Arc<Manager>>>,
|
||||||
pub(crate) service: Arc<Map>,
|
pub(crate) service: Arc<Map>,
|
||||||
@@ -76,6 +79,7 @@ impl Services {
|
|||||||
media: build!(media::Service),
|
media: build!(media::Service),
|
||||||
presence: build!(presence::Service),
|
presence: build!(presence::Service),
|
||||||
pusher: build!(pusher::Service),
|
pusher: build!(pusher::Service),
|
||||||
|
registration_tokens: build!(registration_tokens::Service),
|
||||||
rooms: rooms::Service {
|
rooms: rooms::Service {
|
||||||
alias: build!(rooms::alias::Service),
|
alias: build!(rooms::alias::Service),
|
||||||
auth_chain: build!(rooms::auth_chain::Service),
|
auth_chain: build!(rooms::auth_chain::Service),
|
||||||
@@ -107,6 +111,7 @@ impl Services {
|
|||||||
users: build!(users::Service),
|
users: build!(users::Service),
|
||||||
moderation: build!(moderation::Service),
|
moderation: build!(moderation::Service),
|
||||||
announcements: build!(announcements::Service),
|
announcements: build!(announcements::Service),
|
||||||
|
antispam: build!(antispam::Service),
|
||||||
|
|
||||||
manager: Mutex::new(None),
|
manager: Mutex::new(None),
|
||||||
service,
|
service,
|
||||||
|
|||||||
+17
-27
@@ -1,7 +1,4 @@
|
|||||||
use std::{
|
use std::{collections::BTreeMap, sync::Arc};
|
||||||
collections::{BTreeMap, HashSet},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Error, Result, SyncRwLock, err, error, implement, utils,
|
Err, Error, Result, SyncRwLock, err, error, implement, utils,
|
||||||
@@ -16,7 +13,7 @@ use ruma::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Dep, config, globals, users};
|
use crate::{Dep, config, globals, registration_tokens, users};
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
userdevicesessionid_uiaarequest: SyncRwLock<RequestMap>,
|
userdevicesessionid_uiaarequest: SyncRwLock<RequestMap>,
|
||||||
@@ -28,6 +25,7 @@ struct Services {
|
|||||||
globals: Dep<globals::Service>,
|
globals: Dep<globals::Service>,
|
||||||
users: Dep<users::Service>,
|
users: Dep<users::Service>,
|
||||||
config: Dep<config::Service>,
|
config: Dep<config::Service>,
|
||||||
|
registration_tokens: Dep<registration_tokens::Service>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Data {
|
struct Data {
|
||||||
@@ -50,6 +48,8 @@ impl crate::Service for Service {
|
|||||||
globals: args.depend::<globals::Service>("globals"),
|
globals: args.depend::<globals::Service>("globals"),
|
||||||
users: args.depend::<users::Service>("users"),
|
users: args.depend::<users::Service>("users"),
|
||||||
config: args.depend::<config::Service>("config"),
|
config: args.depend::<config::Service>("config"),
|
||||||
|
registration_tokens: args
|
||||||
|
.depend::<registration_tokens::Service>("registration_tokens"),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -57,26 +57,6 @@ impl crate::Service for Service {
|
|||||||
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(Service)]
|
|
||||||
pub async fn read_tokens(&self) -> Result<HashSet<String>> {
|
|
||||||
let mut tokens = HashSet::new();
|
|
||||||
if let Some(file) = &self.services.config.registration_token_file.as_ref() {
|
|
||||||
match std::fs::read_to_string(file) {
|
|
||||||
| Ok(text) => {
|
|
||||||
text.split_ascii_whitespace().for_each(|token| {
|
|
||||||
tokens.insert(token.to_owned());
|
|
||||||
});
|
|
||||||
},
|
|
||||||
| Err(e) => error!("Failed to read the registration token file: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(token) = &self.services.config.registration_token {
|
|
||||||
tokens.insert(token.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(tokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new Uiaa session. Make sure the session token is unique.
|
/// Creates a new Uiaa session. Make sure the session token is unique.
|
||||||
#[implement(Service)]
|
#[implement(Service)]
|
||||||
pub fn create(
|
pub fn create(
|
||||||
@@ -229,8 +209,18 @@ pub async fn try_auth(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
| AuthData::RegistrationToken(t) => {
|
| AuthData::RegistrationToken(t) => {
|
||||||
let tokens = self.read_tokens().await?;
|
let token = t.token.trim().to_owned();
|
||||||
if tokens.contains(t.token.trim()) {
|
|
||||||
|
if let Some(valid_token) = self
|
||||||
|
.services
|
||||||
|
.registration_tokens
|
||||||
|
.validate_token(token)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
self.services
|
||||||
|
.registration_tokens
|
||||||
|
.mark_token_as_used(valid_token);
|
||||||
|
|
||||||
uiaainfo.completed.push(AuthType::RegistrationToken);
|
uiaainfo.completed.push(AuthType::RegistrationToken);
|
||||||
} else {
|
} else {
|
||||||
uiaainfo.auth_error = Some(StandardErrorBody {
|
uiaainfo.auth_error = Some(StandardErrorBody {
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ struct Data {
|
|||||||
userid_origin: Arc<Map>,
|
userid_origin: Arc<Map>,
|
||||||
userid_password: Arc<Map>,
|
userid_password: Arc<Map>,
|
||||||
userid_suspension: Arc<Map>,
|
userid_suspension: Arc<Map>,
|
||||||
|
userid_lock: Arc<Map>,
|
||||||
|
userid_logindisabled: Arc<Map>,
|
||||||
userid_selfsigningkeyid: Arc<Map>,
|
userid_selfsigningkeyid: Arc<Map>,
|
||||||
userid_usersigningkeyid: Arc<Map>,
|
userid_usersigningkeyid: Arc<Map>,
|
||||||
useridprofilekey_value: Arc<Map>,
|
useridprofilekey_value: Arc<Map>,
|
||||||
@@ -115,6 +117,8 @@ impl crate::Service for Service {
|
|||||||
userid_origin: args.db["userid_origin"].clone(),
|
userid_origin: args.db["userid_origin"].clone(),
|
||||||
userid_password: args.db["userid_password"].clone(),
|
userid_password: args.db["userid_password"].clone(),
|
||||||
userid_suspension: args.db["userid_suspension"].clone(),
|
userid_suspension: args.db["userid_suspension"].clone(),
|
||||||
|
userid_lock: args.db["userid_lock"].clone(),
|
||||||
|
userid_logindisabled: args.db["userid_logindisabled"].clone(),
|
||||||
userid_selfsigningkeyid: args.db["userid_selfsigningkeyid"].clone(),
|
userid_selfsigningkeyid: args.db["userid_selfsigningkeyid"].clone(),
|
||||||
userid_usersigningkeyid: args.db["userid_usersigningkeyid"].clone(),
|
userid_usersigningkeyid: args.db["userid_usersigningkeyid"].clone(),
|
||||||
useridprofilekey_value: args.db["useridprofilekey_value"].clone(),
|
useridprofilekey_value: args.db["useridprofilekey_value"].clone(),
|
||||||
@@ -220,6 +224,26 @@ impl Service {
|
|||||||
self.db.userid_suspension.remove(user_id);
|
self.db.userid_suspension.remove(user_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn lock_account(&self, user_id: &UserId, locking_user: &UserId) {
|
||||||
|
// NOTE: Locking is basically just suspension with a more severe effect,
|
||||||
|
// so we'll just re-use the suspension data structure to store the lock state.
|
||||||
|
let suspension = self
|
||||||
|
.db
|
||||||
|
.userid_lock
|
||||||
|
.get(user_id)
|
||||||
|
.await
|
||||||
|
.deserialized::<UserSuspension>()
|
||||||
|
.unwrap_or_else(|_| UserSuspension {
|
||||||
|
suspended: true,
|
||||||
|
suspended_at: MilliSecondsSinceUnixEpoch::now().get().into(),
|
||||||
|
suspended_by: locking_user.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self.db.userid_lock.raw_put(user_id, Json(suspension));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn unlock_account(&self, user_id: &UserId) { self.db.userid_lock.remove(user_id); }
|
||||||
|
|
||||||
/// Check if a user has an account on this homeserver.
|
/// Check if a user has an account on this homeserver.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn exists(&self, user_id: &UserId) -> bool {
|
pub async fn exists(&self, user_id: &UserId) -> bool {
|
||||||
@@ -255,6 +279,34 @@ impl Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn is_locked(&self, user_id: &UserId) -> Result<bool> {
|
||||||
|
match self
|
||||||
|
.db
|
||||||
|
.userid_lock
|
||||||
|
.get(user_id)
|
||||||
|
.await
|
||||||
|
.deserialized::<UserSuspension>()
|
||||||
|
{
|
||||||
|
| Ok(s) => Ok(s.suspended),
|
||||||
|
| Err(e) =>
|
||||||
|
if e.is_not_found() {
|
||||||
|
Ok(false)
|
||||||
|
} else {
|
||||||
|
Err(e)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_login(&self, user_id: &UserId) {
|
||||||
|
self.db.userid_logindisabled.insert(user_id, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_login(&self, user_id: &UserId) { self.db.userid_logindisabled.remove(user_id); }
|
||||||
|
|
||||||
|
pub async fn is_login_disabled(&self, user_id: &UserId) -> bool {
|
||||||
|
self.db.userid_logindisabled.contains(user_id).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if account is active, infallible
|
/// Check if account is active, infallible
|
||||||
pub async fn is_active(&self, user_id: &UserId) -> bool {
|
pub async fn is_active(&self, user_id: &UserId) -> bool {
|
||||||
!self.is_deactivated(user_id).await.unwrap_or(true)
|
!self.is_deactivated(user_id).await.unwrap_or(true)
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "conduwuit_web"
|
name = "conduwuit_web"
|
||||||
categories.workspace = true
|
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
keywords.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "xtask-generate-commands"
|
name = "xtask-generate-commands"
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
categories.workspace = true
|
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
homepage.workspace = true
|
homepage.workspace = true
|
||||||
keywords.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
rust-version.workspace = true
|
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "xtask"
|
name = "xtask"
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
categories.workspace = true
|
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
homepage.workspace = true
|
homepage.workspace = true
|
||||||
keywords.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
rust-version.workspace = true
|
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
Reference in New Issue
Block a user