Compare commits

..

1 Commits

Author SHA1 Message Date
Ginger eba0130f8e fix(complement): Fix complement conflicting with first-run
- Disabled first-run mode when running Complement tests
- Updated logging config under complement to be a bit less verbose
- Changed test result and log output locations
2026-03-03 18:43:53 +00:00
69 changed files with 344 additions and 1175 deletions
+12 -12
View File
@@ -30,22 +30,22 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "distribution=$DISTRIBUTION" >> $GITHUB_OUTPUT echo "distribution=$DISTRIBUTION" >> $GITHUB_OUTPUT
echo "Debian distribution: $DISTRIBUTION ($VERSION)" echo "Debian distribution: $DISTRIBUTION ($VERSION)"
#- name: Work around llvm-project#153385 - name: Work around llvm-project#153385
# id: llvm-workaround id: llvm-workaround
# run: | run: |
# if [ -f /usr/share/apt/default-sequoia.config ]; then if [ -f /usr/share/apt/default-sequoia.config ]; then
# echo "Applying workaround for llvm-project#153385" echo "Applying workaround for llvm-project#153385"
# mkdir -p /etc/crypto-policies/back-ends/ mkdir -p /etc/crypto-policies/back-ends/
# cp /usr/share/apt/default-sequoia.config /etc/crypto-policies/back-ends/apt-sequoia.config cp /usr/share/apt/default-sequoia.config /etc/crypto-policies/back-ends/apt-sequoia.config
# sed -i 's/\(sha1\.second_preimage_resistance = \)2026-02-01/\12026-06-01/' /etc/crypto-policies/back-ends/apt-sequoia.config sed -i 's/\(sha1\.second_preimage_resistance = \)2026-02-01/\12026-06-01/' /etc/crypto-policies/back-ends/apt-sequoia.config
# else else
# echo "No workaround needed for llvm-project#153385" echo "No workaround needed for llvm-project#153385"
# fi fi
- name: Pick compatible clang version - name: Pick compatible clang version
id: clang-version id: clang-version
run: | run: |
# both latest need to use clang-23, but oldstable and previous can just use clang # both latest need to use clang-23, but oldstable and previous can just use clang
if [[ "${{ matrix.container }}" == "ubuntu-latest" ]]; then if [[ "${{ matrix.container }}" == "ubuntu-latest" || "${{ matrix.container }}" == "debian-latest" ]]; then
echo "Using clang-23 package for ${{ matrix.container }}" echo "Using clang-23 package for ${{ matrix.container }}"
echo "version=clang-23" >> $GITHUB_OUTPUT echo "version=clang-23" >> $GITHUB_OUTPUT
else else
+2 -14
View File
@@ -1,6 +1,5 @@
default_install_hook_types: default_install_hook_types:
- pre-commit - pre-commit
- pre-push
- commit-msg - commit-msg
default_stages: default_stages:
- pre-commit - pre-commit
@@ -24,7 +23,7 @@ repos:
- id: check-added-large-files - id: check-added-large-files
- repo: https://github.com/crate-ci/typos - repo: https://github.com/crate-ci/typos
rev: v1.44.0 rev: v1.43.5
hooks: hooks:
- id: typos - id: typos
- id: typos - id: typos
@@ -32,7 +31,7 @@ repos:
stages: [commit-msg] stages: [commit-msg]
- repo: https://github.com/crate-ci/committed - repo: https://github.com/crate-ci/committed
rev: v1.1.11 rev: v1.1.10
hooks: hooks:
- id: committed - id: committed
@@ -46,14 +45,3 @@ repos:
pass_filenames: false pass_filenames: false
stages: stages:
- pre-commit - pre-commit
- repo: local
hooks:
- id: cargo-clippy
name: cargo clippy
entry: cargo clippy -- -D warnings
language: system
pass_filenames: false
types: [rust]
stages:
- pre-push
-29
View File
@@ -1,32 +1,3 @@
# Continuwuity 0.5.6 (2026-03-03)
## Security
- Admin escape commands received over federation will never be executed, as this is never valid in a genuine situation. Contributed by @Jade.
- Fixed data amplification vulnerability (CWE-409) that affected configurations with server-side compression enabled (non-default). Contributed by @nex.
## Features
- Outgoing presence is now disabled by default, and the config option documentation has been adjusted to more accurately represent the weight of presence, typing indicators, and read receipts. Contributed by @nex. ([#1399](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1399))
- Improved the concurrency handling of federation transactions, vastly improving performance and reliability by more accurately handling inbound transactions and reducing the amount of repeated wasted work. Contributed by @nex and @Jade. ([#1428](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1428))
- Added [MSC3202](https://github.com/matrix-org/matrix-spec-proposals/pull/3202) Device masquerading (not all of MSC3202). This should fix issues with enabling [MSC4190](https://github.com/matrix-org/matrix-spec-proposals/pull/4190) for some Mautrix bridges. Contributed by @Jade ([#1435](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1435))
- Added [MSC3814](https://github.com/matrix-org/matrix-spec-proposals/pull/3814) Dehydrated Devices - you can now decrypt messages sent while all devices were logged out. ([#1436](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1436))
- Implement [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) MatrixRTC transport discovery endpoint. Move RTC foci configuration from `[global.well_known]` to a new `[global.matrix_rtc]` section with a `foci` field. Contributed by @0xnim ([#1442](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1442))
- Updated `list-backups` admin command to output one backup per line. ([#1394](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1394))
- Improved URL preview fetching with a more compatible user agent for sites like YouTube Music. Added `!admin media delete-url-preview <url>` command to clear cached URL previews that were stuck and broken. ([#1434](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1434))
## Bugfixes
- Removed non-compliant nor functional room alias lookups over federation. Contributed by @nex ([#1393](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1393))
- Removed ability to set rocksdb as read only. Doing so would cause unintentional and buggy behaviour. Contributed by @Terryiscool160. ([#1418](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1418))
- Fixed a startup crash in the sender service if we can't detect the number of CPU cores, even if the `sender_workers` config option is set correctly. Contributed by @katie. ([#1421](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1421))
- Removed the `allow_public_room_directory_without_auth` config option. Contributed by @0xnim. ([#1441](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1441))
- Fixed sliding sync v5 list ranges always starting from 0, causing extra rooms to be unnecessarily processed and returned. Contributed by @0xnim ([#1445](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1445))
- Fixed a bug that (repairably) caused a room split between continuwuity and non-continuwuity servers when the room had both `m.room.policy` and `org.matrix.msc4284.policy` in its room state. Contributed by @nex ([#1481](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1481))
- Fixed `!admin media delete --mxc <url>` responding with an error message when the media was deleted successfully. Contributed by @lynxize
- Fixed spurious 404 media errors in the logs. Contributed by @benbot.
- Fixed spurious warn about needed backfill via federation for non-federated rooms. Contributed by @kraem.
# Continuwuity v0.5.5 (2026-02-15) # Continuwuity v0.5.5 (2026-02-15)
## Features ## Features
Generated
+11 -26
View File
@@ -887,7 +887,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit" name = "conduwuit"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"clap", "clap",
"conduwuit_admin", "conduwuit_admin",
@@ -919,7 +919,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_admin" name = "conduwuit_admin"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"clap", "clap",
"conduwuit_api", "conduwuit_api",
@@ -940,7 +940,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_api" name = "conduwuit_api"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum", "axum",
@@ -972,14 +972,14 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_build_metadata" name = "conduwuit_build_metadata"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"built", "built",
] ]
[[package]] [[package]]
name = "conduwuit_core" name = "conduwuit_core"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"argon2", "argon2",
"arrayvec", "arrayvec",
@@ -1041,7 +1041,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_database" name = "conduwuit_database"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"conduwuit_core", "conduwuit_core",
@@ -1059,7 +1059,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_macros" name = "conduwuit_macros"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"itertools 0.14.0", "itertools 0.14.0",
"proc-macro2", "proc-macro2",
@@ -1069,7 +1069,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_router" name = "conduwuit_router"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"axum", "axum",
"axum-client-ip", "axum-client-ip",
@@ -1103,7 +1103,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_service" name = "conduwuit_service"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"askama", "askama",
"async-trait", "async-trait",
@@ -1145,7 +1145,7 @@ dependencies = [
[[package]] [[package]]
name = "conduwuit_web" name = "conduwuit_web"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"askama", "askama",
"axum", "axum",
@@ -4055,14 +4055,12 @@ dependencies = [
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"tokio-util",
"tower", "tower",
"tower-http", "tower-http",
"tower-service", "tower-service",
"url", "url",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-streams",
"web-sys", "web-sys",
"webpki-roots", "webpki-roots",
] ]
@@ -5870,19 +5868,6 @@ dependencies = [
"wasmparser", "wasmparser",
] ]
[[package]]
name = "wasm-streams"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]] [[package]]
name = "wasmparser" name = "wasmparser"
version = "0.244.0" version = "0.244.0"
@@ -6347,7 +6332,7 @@ dependencies = [
[[package]] [[package]]
name = "xtask" name = "xtask"
version = "0.5.6" version = "0.5.5"
dependencies = [ dependencies = [
"askama", "askama",
"cargo_metadata", "cargo_metadata",
+1 -3
View File
@@ -12,7 +12,7 @@ license = "Apache-2.0"
# See also `rust-toolchain.toml` # See also `rust-toolchain.toml`
readme = "README.md" readme = "README.md"
repository = "https://forgejo.ellis.link/continuwuation/continuwuity" repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
version = "0.5.6" version = "0.5.5"
[workspace.metadata.crane] [workspace.metadata.crane]
name = "conduwuit" name = "conduwuit"
@@ -144,7 +144,6 @@ features = [
"socks", "socks",
"hickory-dns", "hickory-dns",
"http2", "http2",
"stream",
] ]
[workspace.dependencies.serde] [workspace.dependencies.serde]
@@ -364,7 +363,6 @@ features = [
"unstable-msc2870", "unstable-msc2870",
"unstable-msc3026", "unstable-msc3026",
"unstable-msc3061", "unstable-msc3061",
"unstable-msc3814",
"unstable-msc3245", "unstable-msc3245",
"unstable-msc3266", "unstable-msc3266",
"unstable-msc3381", # polls "unstable-msc3381", # polls
-1
View File
@@ -1 +0,0 @@
Stopped left rooms from being unconditionally sent on initial sync, hopefully fixing spurious appearances of left rooms in some clients (and making sync faster as a bonus). Contributed by @ginger
-1
View File
@@ -1 +0,0 @@
Re-added support for reading registration tokens from a file. Contributed by @ginger and @benbot.
+1
View File
@@ -0,0 +1 @@
Removed non-compliant nor functional room alias lookups over federation. Contributed by @nex
+1
View File
@@ -0,0 +1 @@
Outgoing presence is now disabled by default, and the config option documentation has been adjusted to more accurately represent the weight of presence, typing indicators, and read receipts. Contributed by @nex.
+1
View File
@@ -0,0 +1 @@
Removed ability to set rocksdb as read only. Doing so would cause unintentional and buggy behaviour. Contributed by @Terryiscool160.
+1
View File
@@ -0,0 +1 @@
Fixed a startup crash in the sender service if we can't detect the number of CPU cores, even if the `sender_workers' config option is set correctly. Contributed by @katie.
+1
View File
@@ -0,0 +1 @@
Improved the concurrency handling of federation transactions, vastly improving performance and reliability by more accurately handling inbound transactions and reducing the amount of repeated wasted work. Contributed by @nex and @Jade.
+1
View File
@@ -0,0 +1 @@
Added MSC3202 Device masquerading (not all of MSC3202). This should fix issues with enabling MSC4190 for some Mautrix bridges. Contributed by @Jade
+1
View File
@@ -0,0 +1 @@
Removed the `allow_public_room_directory_without_auth` config option. Contributed by @0xnim.
+1
View File
@@ -0,0 +1 @@
Implement MSC4143 MatrixRTC transport discovery endpoint. Move RTC foci configuration from `[global.well_known]` to a new `[global.matrix_rtc]` section with a `foci` field. Contributed by @0xnim
+1
View File
@@ -0,0 +1 @@
Fixed sliding sync v5 list ranges always starting from 0, causing extra rooms to be unnecessarily processed and returned. Contributed by @0xnim
-1
View File
@@ -1 +0,0 @@
Prevent removing the admin room alias (`#admins`) to avoid accidentally breaking admin room functionality. Contributed by @0xnim
@@ -0,0 +1 @@
Updated `list-backups` admin command to output one backup per line.
+1
View File
@@ -0,0 +1 @@
Improved URL preview fetching with a more compatible user agent for sites like YouTube Music. Added `!admin media delete-url-preview <url>` command to clear cached URL previews that were stuck and broken.
+3 -15
View File
@@ -15,18 +15,6 @@ disallowed-macros = [
{ path = "log::trace", reason = "use conduwuit_core::trace" }, { path = "log::trace", reason = "use conduwuit_core::trace" },
] ]
[[disallowed-methods]] disallowed-methods = [
path = "tokio::spawn" { path = "tokio::spawn", reason = "use and pass conduuwit_core::server::Server::runtime() to spawn from" },
reason = "use and pass conduwuit_core::server::Server::runtime() to spawn from" ]
[[disallowed-methods]]
path = "reqwest::Response::bytes"
reason = "bytes is unsafe, use limit_read via the conduwuit_core::utils::LimitReadExt trait instead"
[[disallowed-methods]]
path = "reqwest::Response::text"
reason = "text is unsafe, use limit_read_text via the conduwuit_core::utils::LimitReadExt trait instead"
[[disallowed-methods]]
path = "reqwest::Response::json"
reason = "json is unsafe, use limit_read_text via the conduwuit_core::utils::LimitReadExt trait instead"
+7 -14
View File
@@ -476,25 +476,18 @@
#yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = false #yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = false
# A static registration token that new users will have to provide when # A static registration token that new users will have to provide when
# creating an account. This token does not supersede tokens from other # creating an account. If unset and `allow_registration` is true,
# sources, such as the `!admin token` command or the # you must set
# `registration_token_file` configuration option. # `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
# to true to allow open registration without any conditions.
#
# If you do not want to set a static token, the `!admin token` commands
# may also be used to manage registration tokens.
# #
# example: "o&^uCtes4HPf0Vu@F20jQeeWE7" # example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
# #
#registration_token = #registration_token =
# A path to a file containing static registration tokens, one per line.
# Tokens in this file do not supersede tokens from other sources, such as
# the `!admin token` command or the `registration_token` configuration
# option.
#
# The file will be read once, when Continuwuity starts. It is not
# currently reread when the server configuration is reloaded. If the file
# cannot be read, Continuwuity will fail to start.
#
#registration_token_file =
# The public site key for reCaptcha. If this is provided, reCaptcha # The public site key for reCaptcha. If this is provided, reCaptcha
# becomes required during registration. If both captcha *and* # becomes required during registration. If both captcha *and*
# registration token are enabled, both will be required during # registration token are enabled, both will be required during
+3 -8
View File
@@ -48,7 +48,7 @@ EOF
# Developer tool versions # Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall # renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.17.6 ENV BINSTALL_VERSION=1.17.5
# renovate: datasource=github-releases depName=psastras/sbom-rs # renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1 ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree # renovate: datasource=crate depName=lddtree
@@ -180,11 +180,6 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
export RUSTFLAGS="${RUSTFLAGS}" export RUSTFLAGS="${RUSTFLAGS}"
fi fi
RUST_PROFILE_DIR="${RUST_PROFILE}"
if [[ "${RUST_PROFILE}" == "dev" ]]; then
RUST_PROFILE_DIR="debug"
fi
TARGET_DIR=($(cargo metadata --no-deps --format-version 1 | \ TARGET_DIR=($(cargo metadata --no-deps --format-version 1 | \
jq -r ".target_directory")) jq -r ".target_directory"))
mkdir /out/sbin mkdir /out/sbin
@@ -196,8 +191,8 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name")) jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
for BINARY in "${BINARIES[@]}"; do for BINARY in "${BINARIES[@]}"; do
echo $BINARY echo $BINARY
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE_DIR}/$BINARY xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE_DIR}/$BINARY /out/sbin/$BINARY cp $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY /out/sbin/$BINARY
done done
EOF EOF
+1 -1
View File
@@ -18,7 +18,7 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
# Developer tool versions # Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall # renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.17.6 ENV BINSTALL_VERSION=1.17.5
# renovate: datasource=github-releases depName=psastras/sbom-rs # renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1 ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree # renovate: datasource=crate depName=lddtree
-5
View File
@@ -34,11 +34,6 @@
"name": "troubleshooting", "name": "troubleshooting",
"label": "Troubleshooting" "label": "Troubleshooting"
}, },
{
"type": "dir",
"name": "advanced",
"label": "Advanced"
},
"security", "security",
{ {
"type": "dir-section-header", "type": "dir-section-header",
+1 -1
View File
@@ -2,7 +2,7 @@
{ {
"text": "Guide", "text": "Guide",
"link": "/introduction", "link": "/introduction",
"activeMatch": "^/(introduction|configuration|deploying|calls|appservices|maintenance|troubleshooting|advanced)" "activeMatch": "^/(introduction|configuration|deploying|calls|appservices|maintenance|troubleshooting)"
}, },
{ {
"text": "Development", "text": "Development",
-7
View File
@@ -1,7 +0,0 @@
[
{
"type": "file",
"name": "delegation",
"label": "Delegation / split-domain"
}
]
-209
View File
@@ -1,209 +0,0 @@
# Delegation/split-domain deployment
Matrix allows clients and servers to discover a homeserver's "true" destination via **`.well-known` delegation**. This is especially useful if you would like to:
- Serve Continuwuity on a subdomain while having only the base domain for your usernames
- Use a port other than `:8448` for server-to-server connections
This guide will show you how to have `@user:example.com` usernames while serving Continuwuity on `https://matrix.example.com`. It assumes you are using port 443 for both client-to-server connections and server-to-server federation.
## Configuration
First, ensure you have set up A/AAAA records for `matrix.example.com` and `example.com` pointing to your IP.
Then, ensure that the `server_name` field matches your intended username suffix. If this is not the case, you **MUST** wipe the database directory and reinstall Continuwuity with your desired `server_name`.
Then, in the `[global.well_known]` section of your config file, add the following fields:
```toml
[global.well_known]
client = "https://matrix.example.com"
# port number MUST be specified
server = "matrix.example.com:443"
# (optional) customize your support contacts
#support_page =
#support_role = "m.role.admin"
#support_email =
#support_mxid = "@user:example.com"
```
Alternatively if you are using Docker, you can set the `CONTINUWUITY_WELL_KNOWN` environment variable as below:
```yaml
services:
continuwuity:
...
environment:
CONTINUWUITY_WELL_KNOWN: |
{
client=https://matrix.example.com,
server=matrix.example.com:443
}
```
## Serving with a reverse proxy
After doing the steps above, Continuwuity will serve these 3 JSON files:
- `/.well-known/matrix/client`: for Client-Server discovery
- `/.well-known/matrix/server`: for Server-Server (federation) discovery
- `/.well-known/matrix/support`: admin contact details (strongly recommended to have)
To enable full discovery, you will need to reverse proxy these paths from the base domain back to Continuwuity.
<details>
<summary>For Caddy</summary>
```
matrix.example.com:443 {
reverse_proxy 127.0.0.1:8008
}
example.com:443 {
reverse_proxy /.well-known/matrix* 127.0.0.1:8008
}
```
</details>
<details>
<summary>For Traefik (via Docker labels)</summary>
```
services:
continuwuity:
...
labels:
- "traefik.enable=true"
- "traefik.http.routers.continuwuity.rule=(Host(`matrix.example.com`) || (Host(`example.com`) && PathPrefix(`/.well-known/matrix`)))"
- "traefik.http.routers.continuwuity.service=continuwuity"
- "traefik.http.services.continuwuity.loadbalancer.server.port=8008"
```
</details>
Restart Continuwuity and your reverse proxy. Once that's done, visit these routes and check that the responses match the examples below:
<details open>
<summary>`https://example.com/.well-known/matrix/server`</summary>
```json
{
"m.server": "matrix.example.com:443"
}
```
</details>
<details open>
<summary>`https://example.com/.well-known/matrix/client`</summary>
```json
{
"m.homeserver": {
"base_url": "https://matrix.example.com/"
},
"org.matrix.msc3575.proxy": {
"url": "https://matrix.example.com/"
}
}
```
</details>
## Troubleshooting
### Cannot log in with web clients
Make sure there is an `Access-Control-Allow-Origin: *` header in your `/.well-known/matrix/client` path. While Continuwuity serves this header by default, it may be dropped by reverse proxies or other middlewares.
---
## Using SRV records (not recommended)
:::warning
The following methods are **not recommended** due to increased complexity with little benefits. If you have already set up `.well-known` delegation as above, you can safely skip this part.
:::
The following methods uses SRV DNS records and only work with federation traffic. They are only included for completeness.
<details>
<summary>Using only SRV records</summary>
If you can't set up `/.well-known/matrix/server` on :443 for some reason, you can set up a SRV record (via your DNS provider) as below:
- Service and name: `_matrix-fed._tcp.example.com.`
- Priority: `10` (can be any number)
- Weight: `10` (can be any number)
- Port: `443`
- Target: `matrix.example.com.`
On the target's IP at port 443, you must configure a valid route and cert for your server name, `example.com`. Therefore, this method only works to redirect traffic into the right IP/port combo, and can not delegate your federation to a different domain.
</details>
<details>
<summary>Using SRV records + .well-known</summary>
You can also set up `/.well-known/matrix/server` with a delegated domain but no ports:
```toml
[global.well_known]
server = "matrix.example.com"
```
Then, set up a SRV record (via your DNS provider) to announce the port number as below:
- Service and name: `_matrix-fed._tcp.matrix.example.com.`
- Priority: `10` (can be any number)
- Weight: `10` (can be any number)
- Port: `443`
- Target: `matrix.example.com.`
On the target's IP at port 443, you'll need to provide a valid route and cert for `matrix.example.com`. It provides the same feature as pure `.well-known` delegation, albeit with more parts to handle.
</details>
<details>
<summary>Using SRV records as a fallback for .well-known delegation</summary>
Assume your delegation is as below:
```toml
[global.well_known]
server = "example.com:443"
```
If your Continuwuity instance becomes temporarily unreachable, other servers will not be able to find your `/.well-known/matrix/server` file, and defaults to using `server_name:8448`. This incorrect cache can persist for a long time, and would hinder re-federation when your server eventually comes back online.
If you want other servers to default to using port :443 even when it is offline, you could set up a SRV record (via your DNS provider) as follows:
- Service and name: `_matrix-fed._tcp.example.com.`
- Priority: `10` (can be any number)
- Weight: `10` (can be any number)
- Port: `443`
- Target: `example.com.`
On the target's IP at port 443, you'll need to provide a valid route and cert for `example.com`.
</details>
---
## Related Documentation
See the following Matrix Specs for full details on client/server resolution mechanisms:
- [Server-to-Server resolution](https://spec.matrix.org/v1.17/server-server-api/#resolving-server-names) (see this for more information on SRV records)
- [Client-to-Server resolution](https://spec.matrix.org/v1.17/client-server-api/#server-discovery)
- [MSC1929: Homeserver Admin Contact and Support page](https://github.com/matrix-org/matrix-spec-proposals/pull/1929)
+6 -9
View File
@@ -13,9 +13,8 @@ settings.
The config file to use can be specified on the commandline when running The config file to use can be specified on the commandline when running
Continuwuity by specifying the `-c`, `--config` flag. Alternatively, you can use Continuwuity by specifying the `-c`, `--config` flag. Alternatively, you can use
the environment variable `CONTINUWUITY_CONFIG` to specify the config file to be the environment variable `CONDUWUIT_CONFIG` to specify the config file to used.
used; see [the section on environment variables](#environment-variables) for Conduit's environment variables are supported for backwards compatibility.
more information.
## Option commandline flag ## Option commandline flag
@@ -53,15 +52,13 @@ This commandline argument can be paired with the `--option` flag.
All of the settings that are found in the config file can be specified by using All of the settings that are found in the config file can be specified by using
environment variables. The environment variable names should be all caps and environment variables. The environment variable names should be all caps and
prefixed with `CONTINUWUITY_`. prefixed with `CONDUWUIT_`.
For example, if the setting you are changing is `max_request_size`, then the For example, if the setting you are changing is `max_request_size`, then the
environment variable to set is `CONTINUWUITY_MAX_REQUEST_SIZE`. environment variable to set is `CONDUWUIT_MAX_REQUEST_SIZE`.
To modify config options not in the `[global]` context such as To modify config options not in the `[global]` context such as
`[global.well_known]`, use the `__` suffix split: `[global.well_known]`, use the `__` suffix split: `CONDUWUIT_WELL_KNOWN__SERVER`
`CONTINUWUITY_WELL_KNOWN__SERVER`
Conduit and conduwuit's environment variables are also supported for backwards Conduit's environment variables are supported for backwards compatibility (e.g.
compatibility, via the `CONDUIT_` and `CONDUWUIT_` prefixes respectively (e.g.
`CONDUIT_SERVER_NAME`). `CONDUIT_SERVER_NAME`).
+2 -5
View File
@@ -16,7 +16,8 @@ services:
restart: unless-stopped restart: unless-stopped
labels: labels:
caddy: example.com caddy: example.com
caddy.reverse_proxy: /.well-known/matrix/* homeserver:6167 caddy.0_respond: /.well-known/matrix/server {"m.server":"matrix.example.com:443"}
caddy.1_respond: /.well-known/matrix/client {"m.server":{"base_url":"https://matrix.example.com"},"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc3575.proxy":{"url":"https://matrix.example.com"}}
homeserver: homeserver:
### If you already built the Continuwuity image with 'docker build' or want to use a registry image, ### If you already built the Continuwuity image with 'docker build' or want to use a registry image,
@@ -41,10 +42,6 @@ services:
#CONTINUWUITY_LOG: warn,state_res=warn #CONTINUWUITY_LOG: warn,state_res=warn
CONTINUWUITY_ADDRESS: 0.0.0.0 CONTINUWUITY_ADDRESS: 0.0.0.0
#CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above #CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
# Required for .well-known delegation - edit these according to your chosen domain
CONTINUWUITY_WELL_KNOWN__CLIENT: https://matrix.example.com
CONTINUWUITY_WELL_KNOWN__SERVER: matrix.example.com:443
networks: networks:
- caddy - caddy
labels: labels:
@@ -6,10 +6,10 @@
"message": "Welcome to Continuwuity! Important announcements about the project will appear here." "message": "Welcome to Continuwuity! Important announcements about the project will appear here."
}, },
{ {
"id": 10, "id": 9,
"mention_room": false, "mention_room": false,
"date": "2026-03-03", "date": "2026-02-09",
"message": "We've just released [v0.5.6](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.6), which contains a few security improvements - plus significant reliability and performance improvements. Please update as soon as possible. \n\nWe released [v0.5.5](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.5) two weeks ago, but it skipped your admin room straight to [our announcements channel](https://matrix.to/#/!jIdNjSM5X-V5JVx2h2kAhUZIIQ08GyzPL55NFZAH1vM?via=ellis.link&via=gingershaped.computer&via=matrix.org). Make sure you're there to get important information as soon as we announce it! [Our space](https://matrix.to/#/!8cR4g-i9ucof69E4JHNg9LbPVkGprHb3SzcrGBDDJgk?via=continuwuity.org&via=ellis.link&via=matrix.org) has also gained a bunch of new and interesting rooms - be there or be square." "message": "Yesterday we released [v0.5.4](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.4). Bugfixes, performance improvements and more moderation features! There's also a security fix, so please update as soon as possible. Don't forget to join [our announcements channel](https://matrix.to/#/!jIdNjSM5X-V5JVx2h2kAhUZIIQ08GyzPL55NFZAH1vM/%2489TY9CqRg4-ff1MGo3Ulc5r5X4pakfdzT-99RD8Docc?via=ellis.link&via=explodie.org&via=matrix.org) to get important information sooner <3 "
} }
] ]
} }
+1 -1
View File
@@ -27,7 +27,7 @@ default.
* Delete all remote and local media from 3 days ago, up until now: * Delete all remote and local media from 3 days ago, up until now:
`!admin media delete-past-remote-media -a 3d `!admin media delete-past-remote-media -a 3d
--yes-i-want-to-delete-local-media` -yes-i-want-to-delete-local-media`
## `!admin media delete-all-from-user` ## `!admin media delete-all-from-user`
+1
View File
@@ -12,6 +12,7 @@
rocksdbAllFeatures = self'.packages.rocksdb.override { rocksdbAllFeatures = self'.packages.rocksdb.override {
enableJemalloc = true; enableJemalloc = true;
enableLiburing = true;
}; };
commonAttrs = (uwulib.build.commonAttrs { }) // { commonAttrs = (uwulib.build.commonAttrs { }) // {
+1
View File
@@ -27,6 +27,7 @@
commonAttrsArgs.profile = "release"; commonAttrsArgs.profile = "release";
rocksdb = self'.packages.rocksdb.override { rocksdb = self'.packages.rocksdb.override {
enableJemalloc = true; enableJemalloc = true;
enableLiburing = true;
}; };
features = { features = {
enabledFeatures = "all"; enabledFeatures = "all";
+3 -2
View File
@@ -7,6 +7,7 @@
rust-jemalloc-sys-unprefixed, rust-jemalloc-sys-unprefixed,
enableJemalloc ? false, enableJemalloc ? false,
enableLiburing ? false,
fetchFromGitea, fetchFromGitea,
@@ -31,7 +32,7 @@ in
# for some reason enableLiburing in nixpkgs rocksdb is default true # for some reason enableLiburing in nixpkgs rocksdb is default true
# which breaks Darwin entirely # which breaks Darwin entirely
enableLiburing = notDarwin; enableLiburing = enableLiburing && notDarwin;
}).overrideAttrs }).overrideAttrs
(old: { (old: {
src = fetchFromGitea { src = fetchFromGitea {
@@ -73,7 +74,7 @@ in
"USE_RTTI" "USE_RTTI"
]); ]);
enableLiburing = notDarwin; enableLiburing = enableLiburing && notDarwin;
# outputs has "tools" which we don't need or use # outputs has "tools" which we don't need or use
outputs = [ "out" ]; outputs = [ "out" ];
+1
View File
@@ -11,6 +11,7 @@
uwulib = inputs.self.uwulib.init pkgs; uwulib = inputs.self.uwulib.init pkgs;
rocksdbAllFeatures = self'.packages.rocksdb.override { rocksdbAllFeatures = self'.packages.rocksdb.override {
enableJemalloc = true; enableJemalloc = true;
enableLiburing = true;
}; };
in in
{ {
+153 -188
View File
@@ -119,14 +119,15 @@
} }
}, },
"node_modules/@rsbuild/core": { "node_modules/@rsbuild/core": {
"version": "2.0.0-beta.6", "version": "2.0.0-beta.3",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.6.tgz", "resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.3.tgz",
"integrity": "sha512-DUBhUzvzj6xlGUAHTTipFskSuZmVEuTX7lGU+ToPuo8n3bsQrWn/UBOEQAd45g66k7QfXadoZ/v7eodQErpvGQ==", "integrity": "sha512-dfH+Pt2GuF3rWOWGsf5XOhn3Zarvr4DoHwoI1arAsCGvpzoeud3DNGmWPy13tngj0r/YvQRcPTRBCRV4RP5CMw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rspack/core": "2.0.0-beta.3", "@rspack/core": "2.0.0-beta.0",
"@swc/helpers": "^0.5.19" "@swc/helpers": "^0.5.18",
"jiti": "^2.6.1"
}, },
"bin": { "bin": {
"rsbuild": "bin/rsbuild.js" "rsbuild": "bin/rsbuild.js"
@@ -158,28 +159,28 @@
} }
}, },
"node_modules/@rspack/binding": { "node_modules/@rspack/binding": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-beta.0.tgz",
"integrity": "sha512-GSj+d8AlLs1oElhYq32vIN/eAsxWG9jy0EiNgSxWTt5Gdamv87kcvsV4jwfWIjlltdnBIJgey2RnU+hDZlTAvw==", "integrity": "sha512-L6PPqhwZWC2vzwdhBItNPXw+7V4sq+MBDRXLdd8NMqaJSCB5iKdJIbpbEQucST9Nn7V28IYoQTXs6+ol5vWUBA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optionalDependencies": { "optionalDependencies": {
"@rspack/binding-darwin-arm64": "2.0.0-beta.3", "@rspack/binding-darwin-arm64": "2.0.0-beta.0",
"@rspack/binding-darwin-x64": "2.0.0-beta.3", "@rspack/binding-darwin-x64": "2.0.0-beta.0",
"@rspack/binding-linux-arm64-gnu": "2.0.0-beta.3", "@rspack/binding-linux-arm64-gnu": "2.0.0-beta.0",
"@rspack/binding-linux-arm64-musl": "2.0.0-beta.3", "@rspack/binding-linux-arm64-musl": "2.0.0-beta.0",
"@rspack/binding-linux-x64-gnu": "2.0.0-beta.3", "@rspack/binding-linux-x64-gnu": "2.0.0-beta.0",
"@rspack/binding-linux-x64-musl": "2.0.0-beta.3", "@rspack/binding-linux-x64-musl": "2.0.0-beta.0",
"@rspack/binding-wasm32-wasi": "2.0.0-beta.3", "@rspack/binding-wasm32-wasi": "2.0.0-beta.0",
"@rspack/binding-win32-arm64-msvc": "2.0.0-beta.3", "@rspack/binding-win32-arm64-msvc": "2.0.0-beta.0",
"@rspack/binding-win32-ia32-msvc": "2.0.0-beta.3", "@rspack/binding-win32-ia32-msvc": "2.0.0-beta.0",
"@rspack/binding-win32-x64-msvc": "2.0.0-beta.3" "@rspack/binding-win32-x64-msvc": "2.0.0-beta.0"
} }
}, },
"node_modules/@rspack/binding-darwin-arm64": { "node_modules/@rspack/binding-darwin-arm64": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-beta.0.tgz",
"integrity": "sha512-QebSomLWlCbFsC0sfDuGqLJtkgyrnr38vrCepWukaAXIY4ANy5QB49LDKdLpVv6bKlC95MpnW37NvSNWY5GMYA==", "integrity": "sha512-PPx1+SPEROSvDKmBuCbsE7W9tk07ajPosyvyuafv2wbBI6PW2rNcz62uzpIFS+FTgwwZ5u/06WXRtlD2xW9bKg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -191,9 +192,9 @@
] ]
}, },
"node_modules/@rspack/binding-darwin-x64": { "node_modules/@rspack/binding-darwin-x64": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-beta.0.tgz",
"integrity": "sha512-EysmBq+sz+Ph0bu0gXpU1uuZG9gXgjqY+w3MJel+ieTFyQO3L/R56V32McgssMbheJbYcviDDn7Tz4D+lTvdJA==", "integrity": "sha512-GucsfjrSKBZ9cuOTXmHWxeY2wPmaNyvGNxTyzttjRcfwqOWz8r+ku6PCsMSXUqxZRYWW1L9mvtTdlDrzTYJZ0w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -205,9 +206,9 @@
] ]
}, },
"node_modules/@rspack/binding-linux-arm64-gnu": { "node_modules/@rspack/binding-linux-arm64-gnu": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-beta.0.tgz",
"integrity": "sha512-iFPj4TQZKewnqWPfTbyk3F8QCBI/Edv7TVSRIPBHRnCM0lvYZl/8IZlUzXSamLvrtDpouF0nUzht/fktoWOhAg==", "integrity": "sha512-nTtYtklRZD4sb2RIFCF9YS8tZ/MjpqIBKVS3YIvdXcfHUdVfmQHTZGtwEuZGg6AxTC5L1hcvkYmTXCG0ok7auw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -219,9 +220,9 @@
] ]
}, },
"node_modules/@rspack/binding-linux-arm64-musl": { "node_modules/@rspack/binding-linux-arm64-musl": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-beta.0.tgz",
"integrity": "sha512-355mygfCNb0eF/y4HgtJcd0i9csNTG4Z15PCCplIkSAKJpFpkORM2xJb50BqsbhVafYl6AHoBlGWAo9iIzUb/w==", "integrity": "sha512-S2fshx0Rf7/XYwoMLaqFsVg4y+VAfHzubrczy8AW5xIs6UNC3eRLVTgShLerUPtF6SG+v6NQxQ9JI3vOo2qPOA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -233,9 +234,9 @@
] ]
}, },
"node_modules/@rspack/binding-linux-x64-gnu": { "node_modules/@rspack/binding-linux-x64-gnu": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-beta.0.tgz",
"integrity": "sha512-U8a+bcP/tkMyiwiO9XfeRYYO20YPGiZNxWWt7FEsdmRuRAl6M+EmWaJllJFQtKH+GG8IN93pNoVPMvARjLoJOQ==", "integrity": "sha512-yx5Fk1gl7lfkvqcjolNLCNeduIs6C2alMsQ/kZ1pLeP5MPquVOYNqs6EcDPIp+fUjo3lZYtnJBiZKK+QosbzYg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -247,9 +248,9 @@
] ]
}, },
"node_modules/@rspack/binding-linux-x64-musl": { "node_modules/@rspack/binding-linux-x64-musl": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-beta.0.tgz",
"integrity": "sha512-g81rqkaqDFRTID2VrHBYeM+xZe8yWov7IcryTrl9RGXXr61s+6Tu/mWyM378PuHOCyMNu7G3blVaSjLvKauG6Q==", "integrity": "sha512-sBX4b2W0PgehlAVT224k0Q6GaH6t9HP+hBNDrbX/g6d0hfxZN56gm5NfOTOD1Rien4v7OBEejJ3/uFbm1WjwYQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -261,9 +262,9 @@
] ]
}, },
"node_modules/@rspack/binding-wasm32-wasi": { "node_modules/@rspack/binding-wasm32-wasi": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-beta.0.tgz",
"integrity": "sha512-tzGd8H2oj5F3oR/Hxp+J68zVU/nG+9ndH2KK3/RieVjNAiVNHCR0/ZU9D47s6fnmvWOqAQ1qO8gnVoVLopC4YA==", "integrity": "sha512-o6OatnNvb4kCzXbCaomhENGaCsO3naIyAqqErew90HeAwa1lfY3NhRfDLeIyuANQ+xqFl34/R7n8q3ZDx3nd4Q==",
"cpu": [ "cpu": [
"wasm32" "wasm32"
], ],
@@ -275,9 +276,9 @@
} }
}, },
"node_modules/@rspack/binding-win32-arm64-msvc": { "node_modules/@rspack/binding-win32-arm64-msvc": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-beta.0.tgz",
"integrity": "sha512-TZZRSWa34sm5WyoQHwnyBjLJ4w3fcWRYA9ybYjSVWjUU6tVGdMiHiZp+WexUpIETvChLXU1JENNmBg/U7wvZEA==", "integrity": "sha512-neCzVllXzIqM8p8qKb89qV7wyk233gC/V9VrHIKbGeQjAEzpBsk5GOWlFbq5DDL6tivQ+uzYaTrZWm9tb2qxXg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -289,9 +290,9 @@
] ]
}, },
"node_modules/@rspack/binding-win32-ia32-msvc": { "node_modules/@rspack/binding-win32-ia32-msvc": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-beta.0.tgz",
"integrity": "sha512-VFnfdbJhyl6gNW1VzTyd1ZrHCboHPR7vrOalEsulQRqVNbtDkjm1sqLHtDcLmhTEv0a9r4lli8uubWDwmel8KQ==", "integrity": "sha512-/f0n2eO+DxMKQm9IebeMQJITx8M/+RvY/i8d3sAQZBgR53izn8y7EcDlidXpr24/2DvkLbiub8IyCKPlhLB+1A==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -303,9 +304,9 @@
] ]
}, },
"node_modules/@rspack/binding-win32-x64-msvc": { "node_modules/@rspack/binding-win32-x64-msvc": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-beta.0.tgz",
"integrity": "sha512-rwZ6Y3b3oqPj+ZDPPRxr3136HUPKDSlPQa4v7bBOPLDlrFDFOynMIEqDUUi5+8lPaUQ8WWR0aJK4cgcTTT0Siw==", "integrity": "sha512-dx4zgiAT88EQE7kEUpr7Z9EZAwLnO5FhzWzvd/cDK4bkqYsx+rTklgf/c0EYPBeroXCxlGiMsuC9wHAFNK7sFw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -317,19 +318,20 @@
] ]
}, },
"node_modules/@rspack/core": { "node_modules/@rspack/core": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-beta.0.tgz",
"integrity": "sha512-VuLteRIesuyFFTXZaciUY0lwDZiwMc7JcpE8guvjArztDhtpVvlaOcLlVBp/Yza8c/Tk8Dxwe1ARzFL7xG1/0w==", "integrity": "sha512-aEqlQQjiXixT5i9S4DFtiAap8ZjF6pOgfY2ALHOizins/QqWyB8dyLxSoXdzt7JixmKcFmHkbL9XahO28BlVUA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rspack/binding": "2.0.0-beta.3" "@rspack/binding": "2.0.0-beta.0",
"@rspack/lite-tapable": "1.1.0"
}, },
"engines": { "engines": {
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
}, },
"peerDependencies": { "peerDependencies": {
"@module-federation/runtime-tools": "^0.24.1 || ^2.0.0", "@module-federation/runtime-tools": ">=0.22.0",
"@swc/helpers": ">=0.5.1" "@swc/helpers": ">=0.5.1"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
@@ -341,6 +343,13 @@
} }
} }
}, },
"node_modules/@rspack/lite-tapable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.1.0.tgz",
"integrity": "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==",
"dev": true,
"license": "MIT"
},
"node_modules/@rspack/plugin-react-refresh": { "node_modules/@rspack/plugin-react-refresh": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.0.tgz", "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.0.tgz",
@@ -362,22 +371,22 @@
} }
}, },
"node_modules/@rspress/core": { "node_modules/@rspress/core": {
"version": "2.0.4", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.3.tgz",
"integrity": "sha512-OdeGMY75OFzyRZvXuBEMre3q8Y4/OjYJa4vVBDp4Z2E65LSt8+hYkzzkarEl6sFWqbp8c1o9qfSUf4xMctmKvw==", "integrity": "sha512-a+JJFiALqMxGJBqR38/lkN6tas42UF4jRIhu6RilC/3DdqpfqR8j6jjQFOmqoNKo6ZGXW2W+i1Pscn6drvoG3w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@mdx-js/mdx": "^3.1.1", "@mdx-js/mdx": "^3.1.1",
"@mdx-js/react": "^3.1.1", "@mdx-js/react": "^3.1.1",
"@rsbuild/core": "2.0.0-beta.6", "@rsbuild/core": "2.0.0-beta.3",
"@rsbuild/plugin-react": "~1.4.5", "@rsbuild/plugin-react": "~1.4.5",
"@rspress/shared": "2.0.4", "@rspress/shared": "2.0.3",
"@shikijs/rehype": "^4.0.1", "@shikijs/rehype": "^3.21.0",
"@types/unist": "^3.0.3", "@types/unist": "^3.0.3",
"@unhead/react": "^2.1.9", "@unhead/react": "^2.1.4",
"body-scroll-lock": "4.0.0-beta.0", "body-scroll-lock": "4.0.0-beta.0",
"cac": "^7.0.0", "cac": "^6.7.14",
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
"clsx": "2.1.1", "clsx": "2.1.1",
"copy-to-clipboard": "^3.3.3", "copy-to-clipboard": "^3.3.3",
@@ -395,8 +404,7 @@
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-lazy-with-preload": "^2.2.1", "react-lazy-with-preload": "^2.2.1",
"react-reconciler": "0.33.0", "react-reconciler": "0.33.0",
"react-render-to-markdown": "19.0.1", "react-router-dom": "^7.13.0",
"react-router-dom": "^7.13.1",
"rehype-external-links": "^3.0.0", "rehype-external-links": "^3.0.0",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
@@ -404,7 +412,7 @@
"remark-parse": "^11.0.0", "remark-parse": "^11.0.0",
"remark-stringify": "^11.0.0", "remark-stringify": "^11.0.0",
"scroll-into-view-if-needed": "^3.1.0", "scroll-into-view-if-needed": "^3.1.0",
"shiki": "^4.0.1", "shiki": "^3.21.0",
"tinyglobby": "^0.2.15", "tinyglobby": "^0.2.15",
"tinypool": "^1.1.1", "tinypool": "^1.1.1",
"unified": "^11.0.5", "unified": "^11.0.5",
@@ -420,162 +428,125 @@
} }
}, },
"node_modules/@rspress/plugin-client-redirects": { "node_modules/@rspress/plugin-client-redirects": {
"version": "2.0.4", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.3.tgz",
"integrity": "sha512-cm7VNfisVCHe+YHNjd9YrWt6/WtJ5I/oNRyjt+tqCeOcC1IJSX2LhNXpNN5h9az3wxYn37kVctBUjzqkj2FQ+A==", "integrity": "sha512-9+SoAbfoxM6OCRWx8jWHHi2zwJDcNaej/URx0CWZk8tvQ618yJW5mXJydknlac62399eYh/F7C3w8TZM3ORGVA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
}, },
"peerDependencies": { "peerDependencies": {
"@rspress/core": "^2.0.4" "@rspress/core": "^2.0.3"
} }
}, },
"node_modules/@rspress/plugin-sitemap": { "node_modules/@rspress/plugin-sitemap": {
"version": "2.0.4", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.3.tgz",
"integrity": "sha512-TKaj3/8+P1fP3sD5NOaWVMXvRvJFQmuJQlUBxhRM0oiUHhzNNkVy/2YXkjYJuXuMhFPLnOWCjrYjTG3xcZE7Wg==", "integrity": "sha512-SKa7YEAdkUqya2YjMKbakg3kcYMkXgXhTQdDsHd+QlJWN8j8cDPiCcctMZu8iIPeKZlb+hTJkTWvh27LSIKdOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
}, },
"peerDependencies": { "peerDependencies": {
"@rspress/core": "^2.0.4" "@rspress/core": "^2.0.3"
} }
}, },
"node_modules/@rspress/shared": { "node_modules/@rspress/shared": {
"version": "2.0.4", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.3.tgz",
"integrity": "sha512-os2nzsPgHKVFXjDoW7N53rmhLChCw/y2O2TGilT4w2A4HNJa2oJwRk0UryXbxxWD5C85HErTjovs2uBdhdOTtA==", "integrity": "sha512-yI9G4P165fSsmm6QoYTUrdgUis1aFnDh04GcM4SQIpL3itvEZhGtItgoeGkX9EWbnEjhriwI8mTqDDJIp+vrGA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rsbuild/core": "2.0.0-beta.6", "@rsbuild/core": "2.0.0-beta.3",
"@shikijs/rehype": "^4.0.1", "@shikijs/rehype": "^3.21.0",
"gray-matter": "4.0.3", "gray-matter": "4.0.3",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"unified": "^11.0.5" "unified": "^11.0.5"
} }
}, },
"node_modules/@shikijs/core": { "node_modules/@shikijs/core": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz",
"integrity": "sha512-vWvqi9JNgz1dRL9Nvog5wtx7RuNkf7MEPl2mU/cyUUxJeH1CAr3t+81h8zO8zs7DK6cKLMoU9TvukWIDjP4Lzg==", "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/primitive": "4.0.1", "@shikijs/types": "3.22.0",
"@shikijs/types": "4.0.1",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4", "@types/hast": "^3.0.4",
"hast-util-to-html": "^9.0.5" "hast-util-to-html": "^9.0.5"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/engine-javascript": { "node_modules/@shikijs/engine-javascript": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz",
"integrity": "sha512-DJK9NiwtGYqMuKCRO4Ip0FKNDQpmaiS+K5bFjJ7DWFn4zHueDWgaUG8kAofkrnXF6zPPYYQY7J5FYVW9MbZyBg==", "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.1", "@shikijs/types": "3.22.0",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"oniguruma-to-es": "^4.3.4" "oniguruma-to-es": "^4.3.4"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/engine-oniguruma": { "node_modules/@shikijs/engine-oniguruma": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz",
"integrity": "sha512-oCWdCTDch3J8Kc0OZJ98KuUPC02O1VqIE3W/e2uvrHqTxYRR21RGEJMtchrgrxhsoJJCzmIciKsqG+q/yD+Cxg==", "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.1", "@shikijs/types": "3.22.0",
"@shikijs/vscode-textmate": "^10.0.2" "@shikijs/vscode-textmate": "^10.0.2"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/langs": { "node_modules/@shikijs/langs": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz",
"integrity": "sha512-v/mluaybWdnGJR4GqAR6zh8qAZohW9k+cGYT28Y7M8+jLbC0l4yG085O1A+WkseHTn+awd+P3UBymb2+MXFc8w==", "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.1" "@shikijs/types": "3.22.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@shikijs/primitive": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.1.tgz",
"integrity": "sha512-ns0hHZc5eWZuvuIEJz2pTx3Qecz0aRVYumVQJ8JgWY2tq/dH8WxdcVM49Fc2NsHEILNIT6vfdW9MF26RANWiTA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.0.1",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/rehype": { "node_modules/@shikijs/rehype": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-3.22.0.tgz",
"integrity": "sha512-bx7bYA0/p/pgeEICaPO0jT6TXrXHmr9tGRUDhOMy1cAUN2YA0iANfXX7seBnImy8DGu/rxm1ij9/ZofYrAaUjQ==", "integrity": "sha512-69b2VPc6XBy/VmAJlpBU5By+bJSBdE2nvgRCZXav7zujbrjXuT0F60DIrjKuutjPqNufuizE+E8tIZr2Yn8Z+g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.1", "@shikijs/types": "3.22.0",
"@types/hast": "^3.0.4", "@types/hast": "^3.0.4",
"hast-util-to-string": "^3.0.1", "hast-util-to-string": "^3.0.1",
"shiki": "4.0.1", "shiki": "3.22.0",
"unified": "^11.0.5", "unified": "^11.0.5",
"unist-util-visit": "^5.1.0" "unist-util-visit": "^5.1.0"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/themes": { "node_modules/@shikijs/themes": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz",
"integrity": "sha512-FW41C/D6j/yKQkzVdjrRPiJCtgeDaYRJFEyCKFCINuRJRj9WcmubhP4KQHPZ4+9eT87jruSrYPyoblNRyDFzvA==", "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.1" "@shikijs/types": "3.22.0"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/types": { "node_modules/@shikijs/types": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz",
"integrity": "sha512-EaygPEn57+jJ76mw+nTLvIpJMAcMPokFbrF8lufsZP7Ukk+ToJYEcswN1G0e49nUZAq7aCQtoeW219A8HK1ZOw==", "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4" "@types/hast": "^3.0.4"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/@shikijs/vscode-textmate": { "node_modules/@shikijs/vscode-textmate": {
@@ -586,9 +557,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@swc/helpers": { "node_modules/@swc/helpers": {
"version": "0.5.19", "version": "0.5.18",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz",
"integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==", "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -693,13 +664,13 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/@unhead/react": { "node_modules/@unhead/react": {
"version": "2.1.10", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.10.tgz", "resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.4.tgz",
"integrity": "sha512-z9IzzkaCI1GyiBwVRMt4dGc2mOvsj9drbAdXGMy6DWpu9FwTR37ZTmAi7UeCVyIkpVdIaNalz7vkbvGG8afFng==", "integrity": "sha512-3DzMi5nJkUyLVfQF/q78smCvcSy84TTYgTwXVz5s3AjUcLyHro5Z7bLWriwk1dn5+YRfEsec8aPkLCMi5VjMZg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"unhead": "2.1.10" "unhead": "2.1.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/harlan-zw" "url": "https://github.com/sponsors/harlan-zw"
@@ -810,13 +781,13 @@
} }
}, },
"node_modules/cac": { "node_modules/cac": {
"version": "7.0.0", "version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
"integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=20.19.0" "node": ">=8"
} }
}, },
"node_modules/ccount": { "node_modules/ccount": {
@@ -1727,6 +1698,16 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "3.14.2", "version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
@@ -3038,23 +3019,10 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-render-to-markdown": {
"version": "19.0.1",
"resolved": "https://registry.npmjs.org/react-render-to-markdown/-/react-render-to-markdown-19.0.1.tgz",
"integrity": "sha512-BPv48o+ubcu2JyUDIktvJXFqLIZqR7hA4mvGu1eFIofz9fogT2me9UvXwRvqvGs9jEtNaJkxZIUKUX0oiK4hDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"react-reconciler": "0.33.0"
},
"peerDependencies": {
"react": ">=19"
}
},
"node_modules/react-router": { "node_modules/react-router": {
"version": "7.13.1", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
"integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3075,13 +3043,13 @@
} }
}, },
"node_modules/react-router-dom": { "node_modules/react-router-dom": {
"version": "7.13.1", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
"integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==", "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"react-router": "7.13.1" "react-router": "7.13.0"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
@@ -3377,23 +3345,20 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/shiki": { "node_modules/shiki": {
"version": "4.0.1", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.1.tgz", "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz",
"integrity": "sha512-EkAEhDTN5WhpoQFXFw79OHIrSAfHhlImeCdSyg4u4XvrpxKEmdo/9x/HWSowujAnUrFsGOwWiE58a6GVentMnQ==", "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/core": "4.0.1", "@shikijs/core": "3.22.0",
"@shikijs/engine-javascript": "4.0.1", "@shikijs/engine-javascript": "3.22.0",
"@shikijs/engine-oniguruma": "4.0.1", "@shikijs/engine-oniguruma": "3.22.0",
"@shikijs/langs": "4.0.1", "@shikijs/langs": "3.22.0",
"@shikijs/themes": "4.0.1", "@shikijs/themes": "3.22.0",
"@shikijs/types": "4.0.1", "@shikijs/types": "3.22.0",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4" "@types/hast": "^3.0.4"
},
"engines": {
"node": ">=20"
} }
}, },
"node_modules/source-map": { "node_modules/source-map": {
@@ -3598,9 +3563,9 @@
} }
}, },
"node_modules/unhead": { "node_modules/unhead": {
"version": "2.1.10", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.10.tgz", "resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.4.tgz",
"integrity": "sha512-We8l9uNF8zz6U8lfQaVG70+R/QBfQx1oPIgXin4BtZnK2IQpz6yazQ0qjMNVBDw2ADgF2ea58BtvSK+XX5AS7g==", "integrity": "sha512-+5091sJqtNNmgfQ07zJOgUnMIMKzVKAWjeMlSrTdSGPB6JSozhpjUKuMfWEoLxlMAfhIvgOU8Me0XJvmMA/0fA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+2 -10
View File
@@ -1,6 +1,6 @@
use std::fmt::Write; use std::fmt::Write;
use conduwuit::{Err, Result, utils::response::LimitReadExt}; use conduwuit::{Err, Result};
use futures::StreamExt; use futures::StreamExt;
use ruma::{OwnedRoomId, OwnedServerName, OwnedUserId}; use ruma::{OwnedRoomId, OwnedServerName, OwnedUserId};
@@ -55,15 +55,7 @@ pub(super) async fn fetch_support_well_known(&self, server_name: OwnedServerName
.send() .send()
.await?; .await?;
let text = response let text = response.text().await?;
.limit_read_text(
self.services
.config
.max_request_size
.try_into()
.expect("u64 fits into usize"),
)
.await?;
if text.is_empty() { if text.is_empty() {
return Err!("Response text/body is empty."); return Err!("Response text/body is empty.");
+1 -1
View File
@@ -40,7 +40,7 @@ pub enum MediaCommand {
/// * Delete all remote and local media from 3 days ago, up until now: /// * Delete all remote and local media from 3 days ago, up until now:
/// ///
/// `!admin media delete-past-remote-media -a 3d /// `!admin media delete-past-remote-media -a 3d
///--yes-i-want-to-delete-local-media` ///-yes-i-want-to-delete-local-media`
#[command(verbatim_doc_comment)] #[command(verbatim_doc_comment)]
DeletePastRemoteMedia { DeletePastRemoteMedia {
/// The relative time (e.g. 30s, 5m, 7d) from now within which to /// The relative time (e.g. 30s, 5m, 7d) from now within which to
-121
View File
@@ -1,121 +0,0 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Result, at};
use futures::StreamExt;
use ruma::api::client::dehydrated_device::{
delete_dehydrated_device::unstable as delete_dehydrated_device,
get_dehydrated_device::unstable as get_dehydrated_device, get_events::unstable as get_events,
put_dehydrated_device::unstable as put_dehydrated_device,
};
use crate::Ruma;
const MAX_BATCH_EVENTS: usize = 50;
/// # `PUT /_matrix/client/../dehydrated_device`
///
/// Creates or overwrites the user's dehydrated device.
#[tracing::instrument(skip_all, fields(%client))]
pub(crate) async fn put_dehydrated_device_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<put_dehydrated_device::Request>,
) -> Result<put_dehydrated_device::Response> {
let sender_user = body
.sender_user
.as_deref()
.expect("AccessToken authentication required");
let device_id = body.body.device_id.clone();
services
.users
.set_dehydrated_device(sender_user, body.body)
.await?;
Ok(put_dehydrated_device::Response { device_id })
}
/// # `DELETE /_matrix/client/../dehydrated_device`
///
/// Deletes the user's dehydrated device without replacement.
#[tracing::instrument(skip_all, fields(%client))]
pub(crate) async fn delete_dehydrated_device_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<delete_dehydrated_device::Request>,
) -> Result<delete_dehydrated_device::Response> {
let sender_user = body.sender_user();
let device_id = services.users.get_dehydrated_device_id(sender_user).await?;
services.users.remove_device(sender_user, &device_id).await;
Ok(delete_dehydrated_device::Response { device_id })
}
/// # `GET /_matrix/client/../dehydrated_device`
///
/// Gets the user's dehydrated device
#[tracing::instrument(skip_all, fields(%client))]
pub(crate) async fn get_dehydrated_device_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_dehydrated_device::Request>,
) -> Result<get_dehydrated_device::Response> {
let sender_user = body.sender_user();
let device = services.users.get_dehydrated_device(sender_user).await?;
Ok(get_dehydrated_device::Response {
device_id: device.device_id,
device_data: device.device_data,
})
}
/// # `GET /_matrix/client/../dehydrated_device/{device_id}/events`
///
/// Paginates the events of the dehydrated device.
#[tracing::instrument(skip_all, fields(%client))]
pub(crate) async fn get_dehydrated_events_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_events::Request>,
) -> Result<get_events::Response> {
let sender_user = body.sender_user();
let device_id = &body.body.device_id;
let existing_id = services.users.get_dehydrated_device_id(sender_user).await;
if existing_id.as_ref().is_err()
|| existing_id
.as_ref()
.is_ok_and(|existing_id| existing_id != device_id)
{
return Err!(Request(Forbidden("Not the dehydrated device_id.")));
}
let since: Option<u64> = body
.body
.next_batch
.as_deref()
.map(str::parse)
.transpose()?;
let mut next_batch: Option<u64> = None;
let events = services
.users
.get_to_device_events(sender_user, device_id, since, None)
.take(MAX_BATCH_EVENTS)
.inspect(|&(count, _)| {
next_batch.replace(count);
})
.map(at!(1))
.collect()
.await;
Ok(get_events::Response {
events,
next_batch: next_batch.as_ref().map(ToString::to_string),
})
}
-2
View File
@@ -6,7 +6,6 @@ pub(super) mod appservice;
pub(super) mod backup; pub(super) mod backup;
pub(super) mod capabilities; pub(super) mod capabilities;
pub(super) mod context; pub(super) mod context;
pub(super) mod dehydrated_device;
pub(super) mod device; pub(super) mod device;
pub(super) mod directory; pub(super) mod directory;
pub(super) mod filter; pub(super) mod filter;
@@ -50,7 +49,6 @@ pub(super) use appservice::*;
pub(super) use backup::*; pub(super) use backup::*;
pub(super) use capabilities::*; pub(super) use capabilities::*;
pub(super) use context::*; pub(super) use context::*;
pub(super) use dehydrated_device::*;
pub(super) use device::*; pub(super) use device::*;
pub(super) use directory::*; pub(super) use directory::*;
pub(super) use filter::*; pub(super) use filter::*;
+24 -14
View File
@@ -1,5 +1,5 @@
use conduwuit::{ use conduwuit::{
Event, PduEvent, Result, at, debug_warn, Event, PduCount, PduEvent, Result, at, debug_warn,
pdu::EventHash, pdu::EventHash,
trace, trace,
utils::{self, IterStream, future::ReadyEqExt, stream::WidebandExt as _}, utils::{self, IterStream, future::ReadyEqExt, stream::WidebandExt as _},
@@ -68,13 +68,9 @@ pub(super) async fn load_left_room(
return Ok(None); return Ok(None);
} }
// return early if: // return early if this is an incremental sync, and we've already synced this
// - this is an initial sync and the room filter doesn't include leaves, or // leave to the user, and `include_leave` isn't set on the filter.
// - this is an incremental sync, and we've already synced the leave, and the if !filter.room.include_leave && last_sync_end_count >= Some(left_count) {
// room filter doesn't include leaves
if last_sync_end_count.is_none_or(|last_sync_end_count| last_sync_end_count >= left_count)
&& !filter.room.include_leave
{
return Ok(None); return Ok(None);
} }
@@ -199,13 +195,27 @@ async fn build_left_state_and_timeline(
leave_shortstatehash: ShortStateHash, leave_shortstatehash: ShortStateHash,
prev_membership_event: PduEvent, prev_membership_event: PduEvent,
) -> Result<(TimelinePdus, Vec<PduEvent>)> { ) -> Result<(TimelinePdus, Vec<PduEvent>)> {
let SyncContext { syncing_user, filter, .. } = sync_context; let SyncContext {
syncing_user,
last_sync_end_count,
filter,
..
} = sync_context;
let timeline_start_count = services let timeline_start_count = if let Some(last_sync_end_count) = last_sync_end_count {
.rooms // for incremental syncs, start the timeline after `since`
.timeline PduCount::Normal(last_sync_end_count)
.get_pdu_count(&prev_membership_event.event_id) } else {
.await?; // for initial syncs, start the timeline after the previous membership
// event. we don't want to include the membership event itself
// because clients get confused when they see a `join`
// membership event in a `leave` room.
services
.rooms
.timeline
.get_pdu_count(&prev_membership_event.event_id)
.await?
};
// end the timeline at the user's leave event // end the timeline at the user's leave event
let timeline_end_count = services let timeline_end_count = services
+6 -13
View File
@@ -11,7 +11,7 @@ use std::{
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Result, at, extract_variant, Result, extract_variant,
utils::{ utils::{
ReadyExt, TryFutureExtExt, ReadyExt, TryFutureExtExt,
stream::{BroadbandExt, Tools, WidebandExt}, stream::{BroadbandExt, Tools, WidebandExt},
@@ -297,18 +297,12 @@ pub(crate) async fn build_sync_events(
.rooms .rooms
.state_cache .state_cache
.rooms_left(syncing_user) .rooms_left(syncing_user)
.broad_filter_map(|(room_id, leave_pdu)| async { .broad_filter_map(|(room_id, leave_pdu)| {
let left_room = load_left_room(services, context, room_id.clone(), leave_pdu).await; load_left_room(services, context, room_id.clone(), leave_pdu)
.map_ok(move |left_room| (room_id, left_room))
match left_room { .ok()
| Ok(Some(left_room)) => Some((room_id, left_room)),
| Ok(None) => None,
| Err(err) => {
warn!(?err, %room_id, "error loading joined room");
None
},
}
}) })
.ready_filter_map(|(room_id, left_room)| left_room.map(|left_room| (room_id, left_room)))
.collect(); .collect();
let invited_rooms = services let invited_rooms = services
@@ -391,7 +385,6 @@ pub(crate) async fn build_sync_events(
last_sync_end_count, last_sync_end_count,
Some(current_count), Some(current_count),
) )
.map(at!(1))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let device_one_time_keys_count = services let device_one_time_keys_count = services
-1
View File
@@ -1029,7 +1029,6 @@ async fn collect_to_device(
events: services events: services
.users .users
.get_to_device_events(sender_user, sender_device, None, Some(next_batch)) .get_to_device_events(sender_user, sender_device, None, Some(next_batch))
.map(at!(1))
.collect() .collect()
.await, .await,
}) })
-1
View File
@@ -50,7 +50,6 @@ pub(crate) async fn get_supported_versions_route(
("org.matrix.msc2836".to_owned(), true), /* threading/threads (https://github.com/matrix-org/matrix-spec-proposals/pull/2836) */ ("org.matrix.msc2836".to_owned(), true), /* threading/threads (https://github.com/matrix-org/matrix-spec-proposals/pull/2836) */
("org.matrix.msc2946".to_owned(), true), /* spaces/hierarchy summaries (https://github.com/matrix-org/matrix-spec-proposals/pull/2946) */ ("org.matrix.msc2946".to_owned(), true), /* spaces/hierarchy summaries (https://github.com/matrix-org/matrix-spec-proposals/pull/2946) */
("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */ ("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */
("org.matrix.msc3814".to_owned(), true), /* dehydrated devices */
("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */ ("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */
("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */ ("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */
("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */ ("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */
-4
View File
@@ -160,10 +160,6 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::update_device_route) .ruma_route(&client::update_device_route)
.ruma_route(&client::delete_device_route) .ruma_route(&client::delete_device_route)
.ruma_route(&client::delete_devices_route) .ruma_route(&client::delete_devices_route)
.ruma_route(&client::put_dehydrated_device_route)
.ruma_route(&client::delete_dehydrated_device_route)
.ruma_route(&client::get_dehydrated_device_route)
.ruma_route(&client::get_dehydrated_events_route)
.ruma_route(&client::get_tags_route) .ruma_route(&client::get_tags_route)
.ruma_route(&client::update_tag_route) .ruma_route(&client::update_tag_route)
.ruma_route(&client::delete_tag_route) .ruma_route(&client::delete_tag_route)
-1
View File
@@ -174,7 +174,6 @@ pub fn check(config: &Config) -> Result {
if config.allow_registration if config.allow_registration
&& config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse && config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
&& config.registration_token.is_none() && config.registration_token.is_none()
&& config.registration_token_file.is_none()
{ {
warn!( warn!(
"Open registration is enabled via setting \ "Open registration is enabled via setting \
+7 -13
View File
@@ -609,25 +609,19 @@ pub struct Config {
pub yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse: bool, pub yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse: bool,
/// A static registration token that new users will have to provide when /// A static registration token that new users will have to provide when
/// creating an account. This token does not supersede tokens from other /// creating an account. If unset and `allow_registration` is true,
/// sources, such as the `!admin token` command or the /// you must set
/// `registration_token_file` configuration option. /// `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
/// to true to allow open registration without any conditions.
///
/// If you do not want to set a static token, the `!admin token` commands
/// may also be used to manage registration tokens.
/// ///
/// example: "o&^uCtes4HPf0Vu@F20jQeeWE7" /// example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
/// ///
/// display: sensitive /// display: sensitive
pub registration_token: Option<String>, pub registration_token: Option<String>,
/// A path to a file containing static registration tokens, one per line.
/// Tokens in this file do not supersede tokens from other sources, such as
/// the `!admin token` command or the `registration_token` configuration
/// option.
///
/// The file will be read once, when Continuwuity starts. It is not
/// currently reread when the server configuration is reloaded. If the file
/// cannot be read, Continuwuity will fail to start.
pub registration_token_file: Option<PathBuf>,
/// The public site key for reCaptcha. If this is provided, reCaptcha /// The public site key for reCaptcha. If this is provided, reCaptcha
/// becomes required during registration. If both captcha *and* /// becomes required during registration. If both captcha *and*
/// registration token are enabled, both will be required during /// registration token are enabled, both will be required during
-1
View File
@@ -191,7 +191,6 @@ impl Error {
| Self::Reqwest(error) => error.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR), | Self::Reqwest(error) => error.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
| Self::Conflict(_) => StatusCode::CONFLICT, | Self::Conflict(_) => StatusCode::CONFLICT,
| Self::Io(error) => response::io_error_code(error.kind()), | Self::Io(error) => response::io_error_code(error.kind()),
| Self::Uiaa(_) => StatusCode::UNAUTHORIZED,
| _ => StatusCode::INTERNAL_SERVER_ERROR, | _ => StatusCode::INTERNAL_SERVER_ERROR,
} }
} }
-1
View File
@@ -11,7 +11,6 @@ pub mod json;
pub mod math; pub mod math;
pub mod mutex_map; pub mod mutex_map;
pub mod rand; pub mod rand;
pub mod response;
pub mod result; pub mod result;
pub mod set; pub mod set;
pub mod stream; pub mod stream;
-51
View File
@@ -1,51 +0,0 @@
use futures::StreamExt;
use num_traits::ToPrimitive;
use crate::Err;
/// Reads the response body while enforcing a maximum size limit to prevent
/// memory exhaustion.
pub async fn limit_read(response: reqwest::Response, max_size: u64) -> crate::Result<Vec<u8>> {
if response.content_length().is_some_and(|len| len > max_size) {
return Err!(BadServerResponse("Response too large"));
}
let mut data = Vec::new();
let mut reader = response.bytes_stream();
while let Some(chunk) = reader.next().await {
let chunk = chunk?;
data.extend_from_slice(&chunk);
if data.len() > max_size.to_usize().expect("max_size must fit in usize") {
return Err!(BadServerResponse("Response too large"));
}
}
Ok(data)
}
/// Reads the response body as text while enforcing a maximum size limit to
/// prevent memory exhaustion.
pub async fn limit_read_text(
response: reqwest::Response,
max_size: u64,
) -> crate::Result<String> {
let text = String::from_utf8(limit_read(response, max_size).await?)?;
Ok(text)
}
#[allow(async_fn_in_trait)]
pub trait LimitReadExt {
async fn limit_read(self, max_size: u64) -> crate::Result<Vec<u8>>;
async fn limit_read_text(self, max_size: u64) -> crate::Result<String>;
}
impl LimitReadExt for reqwest::Response {
async fn limit_read(self, max_size: u64) -> crate::Result<Vec<u8>> {
limit_read(self, max_size).await
}
async fn limit_read_text(self, max_size: u64) -> crate::Result<String> {
limit_read_text(self, max_size).await
}
}
-4
View File
@@ -362,10 +362,6 @@ pub(super) static MAPS: &[Descriptor] = &[
name: "userid_blurhash", name: "userid_blurhash",
..descriptor::RANDOM_SMALL ..descriptor::RANDOM_SMALL
}, },
Descriptor {
name: "userid_dehydrateddevice",
..descriptor::RANDOM_SMALL
},
Descriptor { Descriptor {
name: "userid_devicelistversion", name: "userid_devicelistversion",
..descriptor::RANDOM_SMALL ..descriptor::RANDOM_SMALL
+1 -15
View File
@@ -530,12 +530,7 @@ impl Service {
Ok(()) Ok(())
} }
pub async fn is_admin_command<E>( pub async fn is_admin_command<E>(&self, event: &E, body: &str) -> Option<InvocationSource>
&self,
event: &E,
body: &str,
sent_locally: bool,
) -> Option<InvocationSource>
where where
E: Event + Send + Sync, E: Event + Send + Sync,
{ {
@@ -585,15 +580,6 @@ impl Service {
return None; return None;
} }
// Escaped commands must be sent locally (via client API), not via federation
if !sent_locally {
conduwuit::warn!(
"Ignoring escaped admin command from {} that arrived via federation",
event.sender()
);
return None;
}
// Looks good // Looks good
Some(InvocationSource::EscapedCommand) Some(InvocationSource::EscapedCommand)
} }
+2 -2
View File
@@ -18,7 +18,7 @@
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use async_trait::async_trait; use async_trait::async_trait;
use conduwuit::{Result, Server, debug, error, utils::response::LimitReadExt, warn}; use conduwuit::{Result, Server, debug, error, warn};
use database::{Deserialized, Map}; use database::{Deserialized, Map};
use ruma::events::{Mentions, room::message::RoomMessageEventContent}; use ruma::events::{Mentions, room::message::RoomMessageEventContent};
use serde::Deserialize; use serde::Deserialize;
@@ -137,7 +137,7 @@ impl Service {
.get(CHECK_FOR_ANNOUNCEMENTS_URL) .get(CHECK_FOR_ANNOUNCEMENTS_URL)
.send() .send()
.await? .await?
.limit_read_text(1024 * 1024) .text()
.await?; .await?;
let response = serde_json::from_str::<CheckForAnnouncementsResponse>(&response)?; let response = serde_json::from_str::<CheckForAnnouncementsResponse>(&response)?;
+4 -3
View File
@@ -19,9 +19,10 @@ impl Service {
/// Get the registration token set in the config file, if it exists. /// Get the registration token set in the config file, if it exists.
#[must_use] #[must_use]
pub fn get_config_file_token(&self) -> Option<ValidToken> { pub fn get_config_file_token(&self) -> Option<ValidToken> {
self.registration_token self.registration_token.clone().map(|token| ValidToken {
.clone() token,
.map(|token| ValidToken { token, source: ValidTokenSource::Config }) source: ValidTokenSource::ConfigFile,
})
} }
} }
+10 -26
View File
@@ -2,8 +2,8 @@ use std::{fmt::Debug, mem};
use bytes::Bytes; use bytes::Bytes;
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err, implement, Err, Error, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err,
trace, utils::response::LimitReadExt, error::inspect_debug_log, implement, trace,
}; };
use http::{HeaderValue, header::AUTHORIZATION}; use http::{HeaderValue, header::AUTHORIZATION};
use ipaddress::IPAddress; use ipaddress::IPAddress;
@@ -133,22 +133,7 @@ async fn handle_response<T>(
where where
T: OutgoingRequest + Send, T: OutgoingRequest + Send,
{ {
const HUGE_ENDPOINTS: [&str; 2] = let response = into_http_response(dest, actual, method, url, response).await?;
["/_matrix/federation/v2/send_join/", "/_matrix/federation/v2/state/"];
let size_limit: u64 = if HUGE_ENDPOINTS.iter().any(|e| url.path().starts_with(e)) {
// Some federation endpoints can return huge response bodies, so we'll bump the
// limit for those endpoints specifically.
self.services
.server
.config
.max_request_size
.saturating_mul(10)
} else {
self.services.server.config.max_request_size
}
.try_into()
.expect("size_limit (usize) should fit within a u64");
let response = into_http_response(dest, actual, method, url, response, size_limit).await?;
T::IncomingResponse::try_from_http_response(response) T::IncomingResponse::try_from_http_response(response)
.map_err(|e| err!(BadServerResponse("Server returned bad 200 response: {e:?}"))) .map_err(|e| err!(BadServerResponse("Server returned bad 200 response: {e:?}")))
@@ -160,7 +145,6 @@ async fn into_http_response(
method: &Method, method: &Method,
url: &Url, url: &Url,
mut response: Response, mut response: Response,
max_size: u64,
) -> Result<http::Response<Bytes>> { ) -> Result<http::Response<Bytes>> {
let status = response.status(); let status = response.status();
trace!( trace!(
@@ -183,14 +167,14 @@ async fn into_http_response(
); );
trace!("Waiting for response body..."); trace!("Waiting for response body...");
let body = response
.bytes()
.await
.inspect_err(inspect_debug_log)
.unwrap_or_else(|_| Vec::new().into());
let http_response = http_response_builder let http_response = http_response_builder
.body( .body(body)
response
.limit_read(max_size)
.await
.unwrap_or_default()
.into(),
)
.expect("reqwest body is valid http body"); .expect("reqwest body is valid http body");
debug!("Got {status:?} for {method} {url}"); debug!("Got {status:?} for {method} {url}");
+21 -31
View File
@@ -7,7 +7,7 @@
use std::time::SystemTime; use std::time::SystemTime;
use conduwuit::{Err, Result, debug, err, utils::response::LimitReadExt}; use conduwuit::{Err, Result, debug, err};
use conduwuit_core::implement; use conduwuit_core::implement;
use ipaddress::IPAddress; use ipaddress::IPAddress;
use serde::Serialize; use serde::Serialize;
@@ -112,22 +112,8 @@ pub async fn download_image(&self, url: &str) -> Result<UrlPreviewData> {
use image::ImageReader; use image::ImageReader;
use ruma::Mxc; use ruma::Mxc;
let image = self let image = self.services.client.url_preview.get(url).send().await?;
.services let image = image.bytes().await?;
.client
.url_preview
.get(url)
.send()
.await?
.limit_read(
self.services
.server
.config
.max_request_size
.try_into()
.expect("u64 should fit in usize"),
)
.await?;
let mxc = Mxc { let mxc = Mxc {
server_name: self.services.globals.server_name(), server_name: self.services.globals.server_name(),
media_id: &random_string(super::MXC_LENGTH), media_id: &random_string(super::MXC_LENGTH),
@@ -165,20 +151,24 @@ async fn download_html(&self, url: &str) -> Result<UrlPreviewData> {
use webpage::HTML; use webpage::HTML;
let client = &self.services.client.url_preview; let client = &self.services.client.url_preview;
let body = client let mut response = client.get(url).send().await?;
.get(url)
.send() let mut bytes: Vec<u8> = Vec::new();
.await? while let Some(chunk) = response.chunk().await? {
.limit_read_text( bytes.extend_from_slice(&chunk);
self.services if bytes.len() > self.services.globals.url_preview_max_spider_size() {
.server debug!(
.config "Response body from URL {} exceeds url_preview_max_spider_size ({}), not \
.max_request_size processing the rest of the response body and assuming our necessary data is in \
.try_into() this range.",
.expect("u64 should fit in usize"), url,
) self.services.globals.url_preview_max_spider_size()
.await?; );
let Ok(html) = HTML::from_string(body.clone(), Some(url.to_owned())) else { break;
}
}
let body = String::from_utf8_lossy(&bytes);
let Ok(html) = HTML::from_string(body.to_string(), Some(url.to_owned())) else {
return Err!(Request(Unknown("Failed to parse HTML"))); return Err!(Request(Unknown("Failed to parse HTML")));
}; };
+4 -9
View File
@@ -2,7 +2,7 @@ use std::{fmt::Debug, time::Duration};
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug_warn, err, implement, Err, Error, Result, debug_warn, err, implement,
utils::{content_disposition::make_content_disposition, response::LimitReadExt}, utils::content_disposition::make_content_disposition,
}; };
use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE, HeaderValue}; use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE, HeaderValue};
use ruma::{ use ruma::{
@@ -286,15 +286,10 @@ async fn location_request(&self, location: &str) -> Result<FileMeta> {
.and_then(Result::ok); .and_then(Result::ok);
response response
.limit_read( .bytes()
self.services
.server
.config
.max_request_size
.try_into()
.expect("u64 should fit in usize"),
)
.await .await
.map(Vec::from)
.map_err(Into::into)
.map(|content| FileMeta { .map(|content| FileMeta {
content: Some(content), content: Some(content),
content_type: content_type.clone(), content_type: content_type.clone(),
+2 -13
View File
@@ -1,7 +1,6 @@
use std::{fmt::Debug, mem, sync::Arc}; use std::{fmt::Debug, mem, sync::Arc};
use bytes::BytesMut; use bytes::BytesMut;
use conduwuit::utils::response::LimitReadExt;
use conduwuit_core::{ use conduwuit_core::{
Err, Event, Result, debug_warn, err, trace, Err, Event, Result, debug_warn, err, trace,
utils::{stream::TryIgnore, string_from_bytes}, utils::{stream::TryIgnore, string_from_bytes},
@@ -31,7 +30,7 @@ use ruma::{
uint, uint,
}; };
use crate::{Dep, client, config, globals, rooms, sending, users}; use crate::{Dep, client, globals, rooms, sending, users};
pub struct Service { pub struct Service {
db: Data, db: Data,
@@ -40,7 +39,6 @@ pub struct Service {
struct Services { struct Services {
globals: Dep<globals::Service>, globals: Dep<globals::Service>,
config: Dep<config::Service>,
client: Dep<client::Service>, client: Dep<client::Service>,
state_accessor: Dep<rooms::state_accessor::Service>, state_accessor: Dep<rooms::state_accessor::Service>,
state_cache: Dep<rooms::state_cache::Service>, state_cache: Dep<rooms::state_cache::Service>,
@@ -63,7 +61,6 @@ impl crate::Service for Service {
services: Services { services: Services {
globals: args.depend::<globals::Service>("globals"), globals: args.depend::<globals::Service>("globals"),
client: args.depend::<client::Service>("client"), client: args.depend::<client::Service>("client"),
config: args.depend::<config::Service>("config"),
state_accessor: args state_accessor: args
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"), .depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"), state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"),
@@ -248,15 +245,7 @@ impl Service {
.expect("http::response::Builder is usable"), .expect("http::response::Builder is usable"),
); );
let body = response let body = response.bytes().await?;
.limit_read(
self.services
.config
.max_request_size
.try_into()
.expect("usize fits into u64"),
)
.await?;
if !status.is_success() { if !status.is_success() {
debug_warn!("Push gateway response body: {:?}", string_from_bytes(&body)); debug_warn!("Push gateway response body: {:?}", string_from_bytes(&body));
+10 -50
View File
@@ -2,7 +2,7 @@ mod data;
use std::{future::ready, pin::Pin, sync::Arc}; use std::{future::ready, pin::Pin, sync::Arc};
use conduwuit::{Err, Result, err, utils}; use conduwuit::{Err, Result, utils};
use data::Data; use data::Data;
pub use data::{DatabaseTokenInfo, TokenExpires}; pub use data::{DatabaseTokenInfo, TokenExpires};
use futures::{ use futures::{
@@ -18,9 +18,6 @@ const RANDOM_TOKEN_LENGTH: usize = 16;
pub struct Service { pub struct Service {
db: Data, db: Data,
services: Services, services: Services,
/// The registration tokens which were read from the registration token
/// file, if one is configured.
registration_tokens_from_file: Vec<String>,
} }
struct Services { struct Services {
@@ -48,54 +45,34 @@ impl PartialEq<str> for ValidToken {
/// The source of a valid database token. /// The source of a valid database token.
#[derive(Debug)] #[derive(Debug)]
pub enum ValidTokenSource { pub enum ValidTokenSource {
/// The static token set in the homeserver's config file. /// The static token set in the homeserver's config file, which is
Config, /// always valid.
ConfigFile,
/// A database token which has been checked to be valid. /// A database token which has been checked to be valid.
Database(DatabaseTokenInfo), Database(DatabaseTokenInfo),
/// The single-use token which may be used to create the homeserver's first /// The single-use token which may be used to create the homeserver's first
/// account. /// account.
FirstAccount, FirstAccount,
/// A registration token read from the registration token file set in the
/// config.
TokenFile,
} }
impl std::fmt::Display for ValidTokenSource { impl std::fmt::Display for ValidTokenSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
| Self::Config => write!(f, "Static token set in the server configuration."), | Self::ConfigFile => write!(f, "Token defined in config."),
| Self::Database(info) => info.fmt(f), | Self::Database(info) => info.fmt(f),
| Self::FirstAccount => write!(f, "Initial setup token."), | Self::FirstAccount => write!(f, "Initial setup token."),
| Self::TokenFile => write!(f, "Static token set in the registration token file."),
} }
} }
} }
impl crate::Service for Service { impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> { fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
let registration_tokens_from_file = args.server.config.registration_token_file
.clone()
// If the token file option was set, read the path it points to
.map(std::fs::read_to_string)
.transpose()
.map_err(|err| err!("Failed to read registration token file: {err}"))
.map(|tokens| {
if let Some(tokens) = tokens {
// If the token file option was set, return the file's lines as tokens
tokens.lines().map(ToOwned::to_owned).collect()
} else {
// Otherwise, if the option wasn't set, return no tokens
vec![]
}
})?;
Ok(Arc::new(Self { Ok(Arc::new(Self {
db: Data::new(args.db), db: Data::new(args.db),
services: Services { services: Services {
config: args.depend::<config::Service>("config"), config: args.depend::<config::Service>("config"),
firstrun: args.depend::<firstrun::Service>("firstrun"), firstrun: args.depend::<firstrun::Service>("firstrun"),
}, },
registration_tokens_from_file,
})) }))
} }
@@ -120,23 +97,12 @@ impl Service {
(token, info) (token, info)
} }
/// Get all the static registration tokens that aren't defined in the /// Get all the "special" registration tokens that aren't defined in the
/// database. /// database.
fn iterate_static_tokens(&self) -> impl Iterator<Item = ValidToken> { fn iterate_static_tokens(&self) -> impl Iterator<Item = ValidToken> {
// This does not include the first-account token, because it has special // This does not include the first-account token, because it's special:
// behavior: no other registration tokens are valid when it is set. // no other registration tokens are valid when it is set.
self.services self.services.config.get_config_file_token().into_iter()
.config
.get_config_file_token()
.into_iter()
.chain(
self.registration_tokens_from_file
.iter()
.map(|token_string| ValidToken {
token: token_string.clone(),
source: ValidTokenSource::TokenFile,
}),
)
} }
/// Validate a registration token. /// Validate a registration token.
@@ -192,7 +158,7 @@ impl Service {
/// revoked. /// revoked.
pub fn revoke_token(&self, ValidToken { token, source }: ValidToken) -> Result { pub fn revoke_token(&self, ValidToken { token, source }: ValidToken) -> Result {
match source { match source {
| ValidTokenSource::Config => { | ValidTokenSource::ConfigFile => {
Err!( Err!(
"The token set in the config file cannot be revoked. Edit the config file \ "The token set in the config file cannot be revoked. Edit the config file \
to change it." to change it."
@@ -205,12 +171,6 @@ impl Service {
| ValidTokenSource::FirstAccount => { | ValidTokenSource::FirstAccount => {
Err!("The initial setup token cannot be revoked.") Err!("The initial setup token cannot be revoked.")
}, },
| ValidTokenSource::TokenFile => {
Err!(
"Tokens set in the registration token file cannot be revoked. Edit the \
registration token file and restart Continuwuity to change them."
)
},
} }
} }
+6 -4
View File
@@ -1,6 +1,4 @@
use conduwuit::{ use conduwuit::{Result, debug, debug_error, debug_info, debug_warn, implement, trace};
Result, debug, debug_error, debug_info, implement, trace, utils::response::LimitReadExt,
};
#[implement(super::Service)] #[implement(super::Service)]
#[tracing::instrument(name = "well-known", level = "debug", skip(self, dest))] #[tracing::instrument(name = "well-known", level = "debug", skip(self, dest))]
@@ -26,8 +24,12 @@ pub(super) async fn request_well_known(&self, dest: &str) -> Result<Option<Strin
return Ok(None); return Ok(None);
} }
let text = response.limit_read_text(8192).await?; let text = response.text().await?;
trace!("response text: {text:?}"); trace!("response text: {text:?}");
if text.len() >= 12288 {
debug_warn!("response contains junk");
return Ok(None);
}
let body: serde_json::Value = serde_json::from_str(&text).unwrap_or_default(); let body: serde_json::Value = serde_json::from_str(&text).unwrap_or_default();
-6
View File
@@ -94,12 +94,6 @@ impl Service {
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub async fn remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> Result<()> { pub async fn remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> Result<()> {
if alias == self.services.globals.admin_alias
&& user_id != self.services.globals.server_user
{
return Err!(Request(Forbidden("Only the server user can remove this alias")));
}
if !self.user_can_remove_alias(alias, user_id).await? { if !self.user_can_remove_alias(alias, user_id).await? {
return Err!(Request(Forbidden("User is not permitted to remove this alias."))); return Err!(Request(Forbidden("User is not permitted to remove this alias.")));
} }
@@ -134,7 +134,7 @@ pub async fn handle_incoming_pdu<'a>(
); );
return Err!(Request(TooLarge("PDU is too large"))); return Err!(Request(TooLarge("PDU is too large")));
} }
trace!("processing incoming PDU from {origin} for room {room_id} with event id {event_id}"); trace!("processing incoming pdu from {origin} for room {room_id} with event id {event_id}");
// 1.1 Check we even know about the room // 1.1 Check we even know about the room
let meta_exists = self.services.metadata.exists(room_id).map(Ok); let meta_exists = self.services.metadata.exists(room_id).map(Ok);
@@ -164,7 +164,7 @@ pub async fn handle_incoming_pdu<'a>(
sender_acl_check.map(|o| o.unwrap_or(Ok(()))), sender_acl_check.map(|o| o.unwrap_or(Ok(()))),
) )
.await .await
.inspect_err(|e| debug_error!(%origin, "failed to handle incoming PDU {event_id}: {e}"))?; .inspect_err(|e| debug_error!("failed to handle incoming PDU: {e}"))?;
if is_disabled { if is_disabled {
return Err!(Request(Forbidden("Federation of this room is disabled by this server."))); return Err!(Request(Forbidden("Federation of this room is disabled by this server.")));
@@ -195,7 +195,6 @@ pub async fn handle_incoming_pdu<'a>(
} }
info!( info!(
%origin, %origin,
%room_id,
"Dropping inbound PDU for room we aren't participating in" "Dropping inbound PDU for room we aren't participating in"
); );
return Err!(Request(NotFound("This server is not participating in that room."))); return Err!(Request(NotFound("This server is not participating in that room.")));
+9 -20
View File
@@ -72,26 +72,6 @@ where
.append_pdu(pdu, pdu_json, new_room_leaves, state_lock, room_id) .append_pdu(pdu, pdu_json, new_room_leaves, state_lock, room_id)
.await?; .await?;
// Process admin commands for federation events
if *pdu.kind() == TimelineEventType::RoomMessage {
let content: ExtractBody = pdu.get_content()?;
if let Some(body) = content.body {
if let Some(source) = self
.services
.admin
.is_admin_command(pdu, &body, false)
.await
{
self.services.admin.command_with_sender(
body,
Some(pdu.event_id().into()),
source,
pdu.sender.clone().into(),
)?;
}
}
}
Ok(Some(pdu_id)) Ok(Some(pdu_id))
} }
@@ -354,6 +334,15 @@ where
let content: ExtractBody = pdu.get_content()?; let content: ExtractBody = pdu.get_content()?;
if let Some(body) = content.body { if let Some(body) = content.body {
self.services.search.index_pdu(shortroomid, &pdu_id, &body); self.services.search.index_pdu(shortroomid, &pdu_id, &body);
if let Some(source) = self.services.admin.is_admin_command(pdu, &body).await {
self.services.admin.command_with_sender(
body,
Some((pdu.event_id()).into()),
source,
pdu.sender.clone().into(),
)?;
}
} }
}, },
| _ => {}, | _ => {},
+1 -23
View File
@@ -18,7 +18,7 @@ use ruma::{
}, },
}; };
use super::{ExtractBody, RoomMutexGuard}; use super::RoomMutexGuard;
/// Creates a new persisted data unit and adds it to a room. This function /// Creates a new persisted data unit and adds it to a room. This function
/// takes a roomid_mutex_state, meaning that only this function is able to /// takes a roomid_mutex_state, meaning that only this function is able to
@@ -126,26 +126,6 @@ pub async fn build_and_append_pdu(
.boxed() .boxed()
.await?; .await?;
// Process admin commands for locally sent events
if *pdu.kind() == TimelineEventType::RoomMessage {
let content: ExtractBody = pdu.get_content()?;
if let Some(body) = content.body {
if let Some(source) = self
.services
.admin
.is_admin_command(&pdu, &body, true)
.await
{
self.services.admin.command_with_sender(
body,
Some(pdu.event_id().into()),
source,
pdu.sender.clone().into(),
)?;
}
}
}
// We set the room state after inserting the pdu, so that we never have a moment // We set the room state after inserting the pdu, so that we never have a moment
// in time where events in the current room state do not exist // in time where events in the current room state do not exist
trace!("Setting room state for room {room_id}"); trace!("Setting room state for room {room_id}");
@@ -187,8 +167,6 @@ pub async fn build_and_append_pdu(
Ok(pdu.event_id().to_owned()) Ok(pdu.event_id().to_owned())
} }
/// Assert invariants about the admin room, to prevent (for example) all admins
/// from leaving or being banned from the room
#[implement(super::Service)] #[implement(super::Service)]
#[tracing::instrument(skip_all, level = "debug")] #[tracing::instrument(skip_all, level = "debug")]
async fn check_pdu_for_admin_room<Pdu>(&self, pdu: &Pdu, sender: &UserId) -> Result async fn check_pdu_for_admin_room<Pdu>(&self, pdu: &Pdu, sender: &UserId) -> Result
+2 -2
View File
@@ -1,7 +1,7 @@
use std::{fmt::Debug, mem}; use std::{fmt::Debug, mem};
use bytes::BytesMut; use bytes::BytesMut;
use conduwuit::{Err, Result, debug_error, err, utils, utils::response::LimitReadExt, warn}; use conduwuit::{Err, Result, debug_error, err, utils, warn};
use reqwest::Client; use reqwest::Client;
use ruma::api::{IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken}; use ruma::api::{IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken};
@@ -38,7 +38,7 @@ where
.expect("http::response::Builder is usable"), .expect("http::response::Builder is usable"),
); );
let body = response.limit_read(65535).await?; // TODO: handle timeout let body = response.bytes().await?; // TODO: handle timeout
if !status.is_success() { if !status.is_success() {
debug_error!("Antispam response bytes: {:?}", utils::string_from_bytes(&body)); debug_error!("Antispam response bytes: {:?}", utils::string_from_bytes(&body));
+2 -12
View File
@@ -1,9 +1,7 @@
use std::{fmt::Debug, mem}; use std::{fmt::Debug, mem};
use bytes::BytesMut; use bytes::BytesMut;
use conduwuit::{ use conduwuit::{Err, Result, debug_error, err, implement, trace, utils, warn};
Err, Result, debug_error, err, implement, trace, utils, utils::response::LimitReadExt, warn,
};
use ruma::api::{ use ruma::api::{
IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken, appservice::Registration, IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken, appservice::Registration,
}; };
@@ -79,15 +77,7 @@ where
.expect("http::response::Builder is usable"), .expect("http::response::Builder is usable"),
); );
let body = response let body = response.bytes().await?;
.limit_read(
self.server
.config
.max_request_size
.try_into()
.expect("usize fits into u64"),
)
.await?;
if !status.is_success() { if !status.is_success() {
debug_error!("Appservice response bytes: {:?}", utils::string_from_bytes(&body)); debug_error!("Appservice response bytes: {:?}", utils::string_from_bytes(&body));
+2 -2
View File
@@ -10,7 +10,7 @@ use std::{
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use conduwuit_core::{ use conduwuit_core::{
Error, Event, Result, at, debug, err, error, Error, Event, Result, debug, err, error,
result::LogErr, result::LogErr,
trace, trace,
utils::{ utils::{
@@ -175,7 +175,7 @@ impl Service {
if !new_events.is_empty() { if !new_events.is_empty() {
self.db.mark_as_active(new_events.iter()); self.db.mark_as_active(new_events.iter());
let new_events_vec = new_events.into_iter().map(at!(1)).collect(); let new_events_vec = new_events.into_iter().map(|(_, event)| event).collect();
futures.push(self.send_events(dest.clone(), new_events_vec)); futures.push(self.send_events(dest.clone(), new_events_vec));
} else { } else {
statuses.remove(dest); statuses.remove(dest);
-149
View File
@@ -1,149 +0,0 @@
use conduwuit::{Err, Result, implement, trace};
use conduwuit_database::{Deserialized, Json};
use ruma::{
DeviceId, OwnedDeviceId, UserId,
api::client::dehydrated_device::{
DehydratedDeviceData, put_dehydrated_device::unstable::Request,
},
serde::Raw,
};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DehydratedDevice {
/// Unique ID of the device.
pub device_id: OwnedDeviceId,
/// Contains serialized and encrypted private data.
pub device_data: Raw<DehydratedDeviceData>,
}
/// Creates or recreates the user's dehydrated device.
#[implement(super::Service)]
#[tracing::instrument(
level = "info",
skip_all,
fields(
%user_id,
device_id = %request.device_id,
display_name = ?request.initial_device_display_name,
)
)]
pub async fn set_dehydrated_device(&self, user_id: &UserId, request: Request) -> Result {
assert!(
self.exists(user_id).await,
"Tried to create dehydrated device for non-existent user"
);
let existing_id = self.get_dehydrated_device_id(user_id).await;
if existing_id.is_err()
&& self
.get_device_metadata(user_id, &request.device_id)
.await
.is_ok()
{
return Err!("A hydrated device already exists with that ID.");
}
if let Ok(existing_id) = existing_id {
self.remove_device(user_id, &existing_id).await;
}
self.create_device(
user_id,
&request.device_id,
"",
request.initial_device_display_name.clone(),
None,
)
.await?;
trace!(device_data = ?request.device_data);
self.db.userid_dehydrateddevice.raw_put(
user_id,
Json(&DehydratedDevice {
device_id: request.device_id.clone(),
device_data: request.device_data,
}),
);
trace!(device_keys = ?request.device_keys);
self.add_device_keys(user_id, &request.device_id, &request.device_keys)
.await;
trace!(one_time_keys = ?request.one_time_keys);
for (one_time_key_key, one_time_key_value) in &request.one_time_keys {
self.add_one_time_key(user_id, &request.device_id, one_time_key_key, one_time_key_value)
.await?;
}
Ok(())
}
/// Removes a user's dehydrated device.
///
/// Calling this directly will remove the dehydrated data but leak the frontage
/// device. Thus this is called by the regular device interface such that the
/// dehydrated data will not leak instead.
///
/// If device_id is given, the user's dehydrated device must match or this is a
/// no-op, but an Err is still returned to indicate that. Otherwise returns the
/// removed dehydrated device_id.
#[implement(super::Service)]
#[tracing::instrument(
level = "debug",
skip_all,
fields(
%user_id,
device_id = ?maybe_device_id,
)
)]
pub(super) async fn remove_dehydrated_device(
&self,
user_id: &UserId,
maybe_device_id: Option<&DeviceId>,
) -> Result<OwnedDeviceId> {
let Ok(device_id) = self.get_dehydrated_device_id(user_id).await else {
return Err!(Request(NotFound("No dehydrated device for this user.")));
};
if let Some(maybe_device_id) = maybe_device_id {
if maybe_device_id != device_id {
return Err!(Request(NotFound("Not the user's dehydrated device.")));
}
}
self.db.userid_dehydrateddevice.remove(user_id);
Ok(device_id)
}
/// Get the device_id of the user's dehydrated device.
#[implement(super::Service)]
#[tracing::instrument(
level = "debug",
skip_all,
fields(%user_id)
)]
pub async fn get_dehydrated_device_id(&self, user_id: &UserId) -> Result<OwnedDeviceId> {
self.get_dehydrated_device(user_id)
.await
.map(|device| device.device_id)
}
/// Get the dehydrated device private data
#[implement(super::Service)]
#[tracing::instrument(
level = "debug",
skip_all,
fields(%user_id),
ret,
)]
pub async fn get_dehydrated_device(&self, user_id: &UserId) -> Result<DehydratedDevice> {
self.db
.userid_dehydrateddevice
.get(user_id)
.await
.deserialized()
}
+3 -12
View File
@@ -1,5 +1,3 @@
pub(super) mod dehydrated_device;
#[cfg(feature = "ldap")] #[cfg(feature = "ldap")]
use std::collections::HashMap; use std::collections::HashMap;
use std::{collections::BTreeMap, mem, net::IpAddr, sync::Arc}; use std::{collections::BTreeMap, mem, net::IpAddr, sync::Arc};
@@ -7,7 +5,7 @@ use std::{collections::BTreeMap, mem, net::IpAddr, sync::Arc};
#[cfg(feature = "ldap")] #[cfg(feature = "ldap")]
use conduwuit::result::LogErr; use conduwuit::result::LogErr;
use conduwuit::{ use conduwuit::{
Err, Error, Result, Server, debug_warn, err, is_equal_to, trace, Err, Error, Result, Server, at, debug_warn, err, is_equal_to, trace,
utils::{self, ReadyExt, stream::TryIgnore, string::Unquoted}, utils::{self, ReadyExt, stream::TryIgnore, string::Unquoted},
}; };
#[cfg(feature = "ldap")] #[cfg(feature = "ldap")]
@@ -72,7 +70,6 @@ struct Data {
userfilterid_filter: Arc<Map>, userfilterid_filter: Arc<Map>,
userid_avatarurl: Arc<Map>, userid_avatarurl: Arc<Map>,
userid_blurhash: Arc<Map>, userid_blurhash: Arc<Map>,
userid_dehydrateddevice: Arc<Map>,
userid_devicelistversion: Arc<Map>, userid_devicelistversion: Arc<Map>,
userid_displayname: Arc<Map>, userid_displayname: Arc<Map>,
userid_lastonetimekeyupdate: Arc<Map>, userid_lastonetimekeyupdate: Arc<Map>,
@@ -113,7 +110,6 @@ impl crate::Service for Service {
userfilterid_filter: args.db["userfilterid_filter"].clone(), userfilterid_filter: args.db["userfilterid_filter"].clone(),
userid_avatarurl: args.db["userid_avatarurl"].clone(), userid_avatarurl: args.db["userid_avatarurl"].clone(),
userid_blurhash: args.db["userid_blurhash"].clone(), userid_blurhash: args.db["userid_blurhash"].clone(),
userid_dehydrateddevice: args.db["userid_dehydrateddevice"].clone(),
userid_devicelistversion: args.db["userid_devicelistversion"].clone(), userid_devicelistversion: args.db["userid_devicelistversion"].clone(),
userid_displayname: args.db["userid_displayname"].clone(), userid_displayname: args.db["userid_displayname"].clone(),
userid_lastonetimekeyupdate: args.db["userid_lastonetimekeyupdate"].clone(), userid_lastonetimekeyupdate: args.db["userid_lastonetimekeyupdate"].clone(),
@@ -484,11 +480,6 @@ impl Service {
/// Removes a device from a user. /// Removes a device from a user.
pub async fn remove_device(&self, user_id: &UserId, device_id: &DeviceId) { pub async fn remove_device(&self, user_id: &UserId, device_id: &DeviceId) {
// Remove dehydrated device if this is the dehydrated device
let _: Result<_> = self
.remove_dehydrated_device(user_id, Some(device_id))
.await;
let userdeviceid = (user_id, device_id); let userdeviceid = (user_id, device_id);
// Remove tokens // Remove tokens
@@ -1012,7 +1003,7 @@ impl Service {
device_id: &'a DeviceId, device_id: &'a DeviceId,
since: Option<u64>, since: Option<u64>,
to: Option<u64>, to: Option<u64>,
) -> impl Stream<Item = (u64, Raw<AnyToDeviceEvent>)> + Send + 'a { ) -> impl Stream<Item = Raw<AnyToDeviceEvent>> + Send + 'a {
type Key<'a> = (&'a UserId, &'a DeviceId, u64); type Key<'a> = (&'a UserId, &'a DeviceId, u64);
let from = (user_id, device_id, since.map_or(0, |since| since.saturating_add(1))); let from = (user_id, device_id, since.map_or(0, |since| since.saturating_add(1)));
@@ -1026,7 +1017,7 @@ impl Service {
&& device_id == *device_id_ && device_id == *device_id_
&& to.is_none_or(|to| *count <= to) && to.is_none_or(|to| *count <= to)
}) })
.map(|((_, _, count), event)| (count, event)) .map(at!(1))
} }
pub async fn remove_to_device_events<Until>( pub async fn remove_to_device_events<Until>(