mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-07-16 19:31:59 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64c0964102 | ||
|
|
c1629c96af | ||
|
|
142e95b5a1 | ||
|
|
21918e7ba4 | ||
|
|
a24019a494 |
@@ -71,7 +71,7 @@ runs:
|
||||
|
||||
- name: Install timelord-cli and git-warp-time
|
||||
if: steps.check-binaries.outputs.need-install == 'true'
|
||||
uses: https://github.com/taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853 # v2
|
||||
uses: https://github.com/taiki-e/install-action@50414676f9f5d50a65992c6dd2ed02641263226c # v2
|
||||
with:
|
||||
tool: git-warp-time,timelord-cli@3.0.1
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
path: binaries
|
||||
merge-multiple: true
|
||||
- name: Create Release and Upload
|
||||
uses: https://github.com/softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3
|
||||
uses: https://github.com/softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3
|
||||
with:
|
||||
draft: true
|
||||
files: binaries/*
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.runner-env.outputs.node_major == '' || steps.runner-env.outputs.node_major < '20'
|
||||
uses: https://github.com/actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
uses: https://github.com/actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 📦 Setup Node.js
|
||||
uses: https://github.com/actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
uses: https://github.com/actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ jobs:
|
||||
path: binaries
|
||||
merge-multiple: true
|
||||
- name: Create Release and Upload
|
||||
uses: https://github.com/softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3
|
||||
uses: https://github.com/softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3
|
||||
with:
|
||||
draft: true
|
||||
files: binaries/*
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
name: Renovate
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/renovatebot/renovate:43.260.2@sha256:60d830108d9ff6408d11a55d5f110ee0976bed78a6b82b08211e3ddb72f72952
|
||||
image: ghcr.io/renovatebot/renovate:43.252.1@sha256:121eb04ef758537019fb58f587aa53c99e5fda4e703993ce2ca01bd4b3926bd4
|
||||
options: --tmpfs /tmp:exec
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
# Continuwuity 26.6.2 (2026-07-12)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Fixed the server returning 500 errors if `admin_console_automatic` is enabled and no TTY is available. Contributed by @s1lv3r. (#1975)
|
||||
- Fixed `global.oauth.compatibility_mode` being required, despite being ignored, when the `[global.oauth.oidc]` config section is provided.
|
||||
- Fixed an issue with a migration that could cause user accounts imported from an identity provider to be marked as deactivated when the server started. If you have accounts affected by this issue, use `!admin users reset-password --convert-to-local-account` to reactivate them.
|
||||
|
||||
|
||||
# Continuwuity 26.6.1 (2026-07-12)
|
||||
|
||||
## Features
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ ### Pre-commit Checks
|
||||
|
||||
|
||||
```bash
|
||||
# Install prek using cargo-binstall https://github.com/cargo-bins/cargo-binstall
|
||||
# Install prek using cargo-binstall
|
||||
cargo binstall prek
|
||||
|
||||
# Install git hooks to run checks automatically
|
||||
@@ -155,7 +155,7 @@ ### Creating pull requests
|
||||
|
||||
Before submitting a pull request, please ensure:
|
||||
1. Your code passes all CI checks (formatting, linting, typo detection, etc.)
|
||||
2. Your code follows the [code style guide](docs/development/code_style.mdx)
|
||||
2. Your code follows the [code style guide](/development/code_style.md)
|
||||
3. Your commit messages follow the conventional commits format
|
||||
4. Tests are added for new functionality
|
||||
5. Documentation is updated if needed
|
||||
|
||||
Generated
+232
-135
@@ -259,9 +259,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.17.1"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad"
|
||||
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
@@ -269,15 +269,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.42.0"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444"
|
||||
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -558,9 +557,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.1"
|
||||
version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424"
|
||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
@@ -576,9 +575,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.12.1"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
|
||||
|
||||
[[package]]
|
||||
name = "bytesize"
|
||||
@@ -642,9 +641,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.67"
|
||||
version = "1.2.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
|
||||
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -827,7 +826,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"clap",
|
||||
@@ -865,7 +864,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_admin"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"clap",
|
||||
@@ -891,7 +890,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_api"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"async-trait",
|
||||
@@ -929,7 +928,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_build_metadata"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
dependencies = [
|
||||
"built",
|
||||
"cargo_metadata",
|
||||
@@ -937,7 +936,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_core"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"arrayvec",
|
||||
@@ -1005,7 +1004,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_database"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"conduwuit_core",
|
||||
@@ -1026,7 +1025,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_macros"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
dependencies = [
|
||||
"cargo_toml",
|
||||
"itertools 0.15.0",
|
||||
@@ -1037,7 +1036,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_router"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"axum",
|
||||
@@ -1075,7 +1074,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_service"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"assign",
|
||||
@@ -1127,7 +1126,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_web"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"assign",
|
||||
@@ -1362,18 +1361,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.16"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.7"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
@@ -1381,27 +1380,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.20"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.13"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26"
|
||||
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.22"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
@@ -1464,9 +1463,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "1.0.8"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb22e947478ccf9dc44d8922042c677a63fbb88f2cb468521d1145816e5087cb"
|
||||
checksum = "01334b89b69ff726750c5ce5073fc8bd860e99aa9a8fc5ca11b04730e3aee97a"
|
||||
dependencies = [
|
||||
"link-section",
|
||||
"linktime-proc-macro",
|
||||
@@ -1514,14 +1513,38 @@ version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "817fa642fb0ee7fe42e95783e00e0969927b96091bdd4b9b1af082acd943913b"
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
dependencies = [
|
||||
"darling_core 0.20.11",
|
||||
"darling_macro 0.20.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
"darling_core 0.23.0",
|
||||
"darling_macro 0.23.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1537,13 +1560,24 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
dependencies = [
|
||||
"darling_core 0.20.11",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_core 0.23.0",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
@@ -2088,11 +2122,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
"rand_core 0.10.1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2258,7 +2290,7 @@ dependencies = [
|
||||
"http",
|
||||
"httpdate",
|
||||
"mime",
|
||||
"sha1 0.10.7",
|
||||
"sha1 0.10.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2443,9 +2475,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.4"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
|
||||
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -2874,11 +2906,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.35"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"getrandom 0.3.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -3028,9 +3060,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "link-section"
|
||||
version = "0.19.0"
|
||||
version = "0.18.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e333fe507b738576d6da5bb3f1a7d7a1c80307ed9ef31624c057d844c19c93e9"
|
||||
checksum = "24670b639492630905459a6c7d47f063d33c2d4fcd5362f6e5827c5613976c9f"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
@@ -3167,20 +3199,19 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
|
||||
[[package]]
|
||||
name = "memory-serve"
|
||||
version = "2.3.0"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18718e8f7eefb3194d346474ac95f50ada065ff043e0074e5b8e85b85148122f"
|
||||
checksum = "81b5bbad2035f57b1e95f66da606832edd935b47d82312e38e1ccffbcfb8a427"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"brotli",
|
||||
"flate2",
|
||||
"fs-err",
|
||||
"mime_guess",
|
||||
"sha256",
|
||||
"tracing",
|
||||
@@ -3364,9 +3395,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.8"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
|
||||
checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
@@ -3383,7 +3414,7 @@ dependencies = [
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand 0.8.7",
|
||||
"rand 0.8.6",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -3414,10 +3445,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.46"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
|
||||
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
@@ -3463,7 +3495,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"getrandom 0.2.17",
|
||||
"http",
|
||||
"rand 0.8.7",
|
||||
"rand 0.8.6",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -3668,7 +3700,7 @@ dependencies = [
|
||||
"oauth2",
|
||||
"p256",
|
||||
"p384",
|
||||
"rand 0.8.7",
|
||||
"rand 0.8.6",
|
||||
"rsa",
|
||||
"serde",
|
||||
"serde-value",
|
||||
@@ -3759,7 +3791,7 @@ dependencies = [
|
||||
"opentelemetry",
|
||||
"percent-encoding",
|
||||
"portable-atomic",
|
||||
"rand 0.9.5",
|
||||
"rand 0.9.4",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -3936,7 +3968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand 0.8.7",
|
||||
"rand 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4080,22 +4112,22 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr3"
|
||||
version = "3.0.2"
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34e4dd828515431dd6c4a030d26f7eaed7dd4778226e9d2bb968d65ca4ec3d4d"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error3"
|
||||
version = "3.0.2"
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee475e440453418ff1335189eddf7101ba502cd818ab7ae04209bc83aa925aa"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr3",
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -4175,9 +4207,9 @@ checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.30"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
|
||||
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
@@ -4208,16 +4240,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.16"
|
||||
version = "0.11.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
|
||||
checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"bytes",
|
||||
"getrandom 0.4.3",
|
||||
"getrandom 0.3.4",
|
||||
"lru-slab",
|
||||
"rand 0.10.2",
|
||||
"rand_pcg",
|
||||
"rand 0.9.4",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
@@ -4231,16 +4262,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.15"
|
||||
version = "0.5.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
|
||||
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4272,9 +4303,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.7"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
@@ -4283,9 +4314,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.5"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
|
||||
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.5",
|
||||
@@ -4346,15 +4377,6 @@ version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
|
||||
dependencies = [
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "recaptcha-verify"
|
||||
version = "0.2.0"
|
||||
@@ -4397,9 +4419,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.13.0"
|
||||
version = "1.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2"
|
||||
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -4409,9 +4431,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.15"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -4777,7 +4799,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruminuwuity"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"ruma",
|
||||
@@ -4815,9 +4837,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.28"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
|
||||
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
@@ -4849,9 +4871,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.42"
|
||||
version = "0.23.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138"
|
||||
checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
@@ -4926,9 +4948,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "rustyline-async"
|
||||
@@ -5114,7 +5136,7 @@ version = "0.48.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8c44ee52fa3d30581f951b57aec754ef9cd70186df5fb788367804360c48097"
|
||||
dependencies = [
|
||||
"rand 0.9.5",
|
||||
"rand 0.9.4",
|
||||
"sentry-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -5187,7 +5209,7 @@ checksum = "1be93ef0435a5ab91f88178fb7681249437e875d004f996ecb35be94dc99a0e0"
|
||||
dependencies = [
|
||||
"debugid",
|
||||
"hex",
|
||||
"rand 0.9.5",
|
||||
"rand 0.9.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
@@ -5367,7 +5389,7 @@ version = "3.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.23.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -5375,9 +5397,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.7"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
@@ -5700,9 +5722,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termimad"
|
||||
version = "0.35.1"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d53d4b1294b87e81925b7ae7f8f4d000376e3a5b3349d978429665428a793fcb"
|
||||
checksum = "903906afabf58273a7a90b47e0e60701cbd01731ca72ba88508b38b5539647f4"
|
||||
dependencies = [
|
||||
"coolor",
|
||||
"crokey",
|
||||
@@ -5766,9 +5788,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.10"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@@ -5843,9 +5865,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.12.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
|
||||
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
@@ -6208,7 +6230,7 @@ dependencies = [
|
||||
"futures",
|
||||
"http",
|
||||
"parking_lot",
|
||||
"rand 0.9.5",
|
||||
"rand 0.9.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
@@ -6422,9 +6444,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.23.5"
|
||||
version = "1.23.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a"
|
||||
checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53"
|
||||
dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"js-sys",
|
||||
@@ -6450,12 +6472,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "validator_derive"
|
||||
version = "0.20.1"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240e4b81c20a1d6d50d1d7265c658dfbd204e8b9ac4d80f3c931f39462196335"
|
||||
checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro-error3",
|
||||
"darling 0.20.11",
|
||||
"once_cell",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -6756,7 +6779,16 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6774,14 +6806,31 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
"windows_i686_gnullvm 0.53.1",
|
||||
"windows_i686_msvc 0.53.1",
|
||||
"windows_x86_64_gnu 0.53.1",
|
||||
"windows_x86_64_gnullvm 0.53.1",
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6790,48 +6839,96 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
@@ -6875,7 +6972,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"cargo_metadata",
|
||||
@@ -6915,18 +7012,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.54"
|
||||
version = "0.8.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
|
||||
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.54"
|
||||
version = "0.8.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
|
||||
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6995,9 +7092,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.22"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd2f034a4bebf216c9e4b7083603e024cf930873fd67830cfb083c9fa33129d9"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ license = "Apache-2.0"
|
||||
# See also `rust-toolchain.toml`
|
||||
readme = "README.md"
|
||||
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
||||
version = "26.6.2"
|
||||
version = "26.6.1"
|
||||
|
||||
[workspace.metadata.crane]
|
||||
name = "conduwuit"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Appservices are now properly able to create devices for E2EE.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed `global.oauth.compatibility_mode` being required, despite being ignored, when the `[global.oauth.oidc]` config section is provided.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed an issue with a migration that could cause user accounts imported from an identity provider to be marked as deactivated when the server started. If you have accounts affected by this issue, use `!admin users reset-password --convert-to-local-account` to reactivate them.
|
||||
@@ -1 +0,0 @@
|
||||
Fixed newly created rooms failing to sync properly in clients using legacy sync.
|
||||
@@ -1 +0,0 @@
|
||||
Appservices may now specify both the unstable and stable `device_id` query parameters in a request. The stable parameter will take priority. Contributed by @ginger.
|
||||
@@ -1 +0,0 @@
|
||||
Fixed the deeplink redirect for deleting devices. Contributed by @koen
|
||||
@@ -1 +0,0 @@
|
||||
Fix status code for oauth registration. Contributed by @n00byking
|
||||
@@ -1 +0,0 @@
|
||||
Exempt m.room.create from auth_events check. Contributed by @eleboucher
|
||||
@@ -6,10 +6,10 @@
|
||||
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"id": 15,
|
||||
"mention_room": true,
|
||||
"date": "2026-07-13",
|
||||
"message": "[Continuwuity 26.6.2](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v26.6.1) has just been released! This release fixes a severe bug with OIDC that could cause users' accounts to be flagged as deactivated. If you use OIDC, please update as soon as possible."
|
||||
"date": "2026-07-10",
|
||||
"message": "[Continuwuity 26.6.1](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v26.6.1) has just been released! This release contains a bunch of bugfixes for bugs found initially after 26.6.0's release - please read the changelog before updating!"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+21
-21
@@ -3,11 +3,11 @@
|
||||
"advisory-db": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1783840254,
|
||||
"narHash": "sha256-XjyvZk0f3YiinVHmkGOotmLBAzvK+LwEJYj2QqJ5pn8=",
|
||||
"lastModified": 1781566179,
|
||||
"narHash": "sha256-Tqv8I586fYzWpEW/Smq/JqESFa3DVVzVWsnAMtvhy/I=",
|
||||
"owner": "rustsec",
|
||||
"repo": "advisory-db",
|
||||
"rev": "6e3286f4efa8c142fb33e5ea4342c8db6693cf34",
|
||||
"rev": "74e084413d979d52d2f93b1d93b1ab7b9ee648f5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -18,11 +18,11 @@
|
||||
},
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1783203018,
|
||||
"narHash": "sha256-G6R9IT/xwFuu+CYBWDUAok6AdC4ERC4ZfPPFtEpxnZE=",
|
||||
"lastModified": 1780532242,
|
||||
"narHash": "sha256-D+BsdpxmtUwtqGoY0IXPhHgTlmqgcZKCEo1oMyn7ep0=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "80db5bdc391be8a1794f6d8a2d56e3a84ebcede2",
|
||||
"rev": "59a82a1222dd3b2080b5cc52a1a2e8d5f1b77f37",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -39,11 +39,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1783844668,
|
||||
"narHash": "sha256-3MOpw4y3reoErRLvFBDz0t9aG6FWXUj7leKvUns5eQA=",
|
||||
"lastModified": 1781527054,
|
||||
"narHash": "sha256-1fX9ev2Fh5QoKQ41G9dYutjo5j/jywu6tZse5Eb1Ck4=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "4e49ef62eedaa5c149d62b63b3f53844e1cc45d7",
|
||||
"rev": "8c2e51dffefc040a21975da7abf6f252c8c9b783",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -74,11 +74,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1782949081,
|
||||
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
|
||||
"lastModified": 1778716662,
|
||||
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
|
||||
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -89,11 +89,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1783776592,
|
||||
"narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=",
|
||||
"lastModified": 1781074563,
|
||||
"narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e7a3ca8092b61ff85b6a45bf863ea2b2d6a661b3",
|
||||
"rev": "9ae611a455b90cf061d8f332b977e387bda8e1ca",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -105,11 +105,11 @@
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1782614948,
|
||||
"narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=",
|
||||
"lastModified": 1777168982,
|
||||
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c",
|
||||
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -132,11 +132,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1783779020,
|
||||
"narHash": "sha256-vpm418WZa9l1KUl6HRs+Ga+SIfE+D2/sV4olwTlilYc=",
|
||||
"lastModified": 1781453968,
|
||||
"narHash": "sha256-+V3nK4pCngbmgyVGXY6Kkrlevp4ocPkJJLf2aqwkDNA=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "5be5e89cf0145d73a85c805726821b4adfc3af48",
|
||||
"rev": "cc272809a173c2c11d0e479d639c811c1eacf049",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
Generated
+48
-48
@@ -510,14 +510,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/core": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.3.1.tgz",
|
||||
"integrity": "sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.2.0.tgz",
|
||||
"integrity": "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/primitive": "4.3.1",
|
||||
"@shikijs/types": "4.3.1",
|
||||
"@shikijs/primitive": "4.2.0",
|
||||
"@shikijs/types": "4.2.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4",
|
||||
"hast-util-to-html": "^9.0.5"
|
||||
@@ -527,13 +527,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/engine-javascript": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.3.1.tgz",
|
||||
"integrity": "sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.2.0.tgz",
|
||||
"integrity": "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.3.1",
|
||||
"@shikijs/types": "4.2.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"oniguruma-to-es": "^4.3.6"
|
||||
},
|
||||
@@ -542,13 +542,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/engine-oniguruma": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.3.1.tgz",
|
||||
"integrity": "sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.2.0.tgz",
|
||||
"integrity": "sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.3.1",
|
||||
"@shikijs/types": "4.2.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -556,26 +556,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/langs": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.3.1.tgz",
|
||||
"integrity": "sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.2.0.tgz",
|
||||
"integrity": "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.3.1"
|
||||
"@shikijs/types": "4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/primitive": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.3.1.tgz",
|
||||
"integrity": "sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.2.0.tgz",
|
||||
"integrity": "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.3.1",
|
||||
"@shikijs/types": "4.2.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4"
|
||||
},
|
||||
@@ -584,16 +584,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/rehype": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.3.1.tgz",
|
||||
"integrity": "sha512-oshrlfUF3VPUJfnp5K1lLwsS/SRBKrIxONpdWebSKZXdBE3UsZnxgqpvRUA8UsofS7vmjFOCAHIT71ECbmOxTw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.2.0.tgz",
|
||||
"integrity": "sha512-ST3EWye/dwF1gWskczJNBnwFtDzEQ9ceytXZtyc/GfwR5V0qJrkoSGZO55O3SAKDDsXkTDcsfwd9pVe7ROlAHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.3.1",
|
||||
"@shikijs/types": "4.2.0",
|
||||
"@types/hast": "^3.0.4",
|
||||
"hast-util-to-string": "^3.0.1",
|
||||
"shiki": "4.3.1",
|
||||
"shiki": "4.2.0",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.1.0"
|
||||
},
|
||||
@@ -602,22 +602,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/themes": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.3.1.tgz",
|
||||
"integrity": "sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.2.0.tgz",
|
||||
"integrity": "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.3.1"
|
||||
"@shikijs/types": "4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/types": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.3.1.tgz",
|
||||
"integrity": "sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz",
|
||||
"integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -684,9 +684,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz",
|
||||
"integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==",
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
||||
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1076,9 +1076,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz",
|
||||
"integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz",
|
||||
"integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -3589,18 +3589,18 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shiki": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.3.1.tgz",
|
||||
"integrity": "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.2.0.tgz",
|
||||
"integrity": "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/core": "4.3.1",
|
||||
"@shikijs/engine-javascript": "4.3.1",
|
||||
"@shikijs/engine-oniguruma": "4.3.1",
|
||||
"@shikijs/langs": "4.3.1",
|
||||
"@shikijs/themes": "4.3.1",
|
||||
"@shikijs/types": "4.3.1",
|
||||
"@shikijs/core": "4.2.0",
|
||||
"@shikijs/engine-javascript": "4.2.0",
|
||||
"@shikijs/engine-oniguruma": "4.2.0",
|
||||
"@shikijs/langs": "4.2.0",
|
||||
"@shikijs/themes": "4.2.0",
|
||||
"@shikijs/types": "4.2.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4"
|
||||
},
|
||||
|
||||
@@ -514,7 +514,12 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
.services
|
||||
.rooms
|
||||
.membership
|
||||
.join_room(&user_id, &room_id, Some(String::from(BULK_JOIN_REASON)), &servers)
|
||||
.join_room(
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some(String::from(BULK_JOIN_REASON)),
|
||||
servers.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Ok(_res) => {
|
||||
@@ -596,7 +601,12 @@ pub(super) async fn force_join_all_local_users(
|
||||
.services
|
||||
.rooms
|
||||
.membership
|
||||
.join_room(user_id, &room_id, Some(String::from(BULK_JOIN_REASON)), &servers)
|
||||
.join_room(
|
||||
user_id,
|
||||
&room_id,
|
||||
Some(String::from(BULK_JOIN_REASON)),
|
||||
servers.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Ok(_res) => {
|
||||
@@ -643,7 +653,7 @@ pub(super) async fn force_join_room(
|
||||
self.services
|
||||
.rooms
|
||||
.membership
|
||||
.join_room(&user_id, &room_id, None, &servers)
|
||||
.join_room(&user_id, &room_id, None, servers)
|
||||
.await?;
|
||||
|
||||
self.write_str(&format!("{user_id} has been joined to {room_id}."))
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
};
|
||||
use service::{mailer::messages, uiaa::UiaaInitiator, users::HashedPassword};
|
||||
|
||||
use super::DEVICE_ID_LENGTH;
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::{Ruma, router::ClientIdentity};
|
||||
|
||||
pub(crate) mod register;
|
||||
|
||||
@@ -20,12 +20,9 @@
|
||||
assign,
|
||||
};
|
||||
use serde_json::value::RawValue;
|
||||
use service::{
|
||||
mailer::messages,
|
||||
users::{DeviceToken, HashedPassword},
|
||||
};
|
||||
use service::{mailer::messages, users::HashedPassword};
|
||||
|
||||
use super::DEVICE_ID_LENGTH;
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `POST /_matrix/client/v3/register`
|
||||
@@ -122,7 +119,7 @@ pub(crate) async fn register_route(
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
// Generate new token for the device
|
||||
let new_token = DeviceToken::new_random();
|
||||
let new_token = utils::random_string(TOKEN_LENGTH);
|
||||
|
||||
// Create device for this account
|
||||
services
|
||||
@@ -130,7 +127,8 @@ pub(crate) async fn register_route(
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
Some(new_token.clone()),
|
||||
&new_token,
|
||||
None,
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
@@ -144,7 +142,7 @@ pub(crate) async fn register_route(
|
||||
debug_info!(%user_id, ?device, "New account created via legacy registration");
|
||||
|
||||
Ok(assign!(register::v3::Response::new(user_id), {
|
||||
access_token: token.map(DeviceToken::into_token),
|
||||
access_token: token,
|
||||
device_id: device,
|
||||
refresh_token: None,
|
||||
expires_in: None,
|
||||
|
||||
@@ -89,7 +89,14 @@ pub(crate) async fn update_device_route(
|
||||
|
||||
services
|
||||
.users
|
||||
.create_device(sender_user, &device_id, None, None, Some(client.to_string()))
|
||||
.create_device(
|
||||
sender_user,
|
||||
&device_id,
|
||||
&appservice.registration.as_token,
|
||||
None,
|
||||
None,
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
return Ok(update_device::v3::Response::new());
|
||||
|
||||
@@ -75,7 +75,7 @@ pub(crate) async fn join_room_by_id_route(
|
||||
let room_id = services
|
||||
.rooms
|
||||
.membership
|
||||
.join_room(sender_user, &body.room_id, body.reason.clone(), &servers)
|
||||
.join_room(sender_user, &body.room_id, body.reason.clone(), servers)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
@@ -195,7 +195,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
||||
let room_id = services
|
||||
.rooms
|
||||
.membership
|
||||
.join_room(sender_user, &room_id, body.reason.clone(), &servers)
|
||||
.join_room(sender_user, &room_id, body.reason.clone(), servers)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ pub(crate) async fn knock_room_route(
|
||||
},
|
||||
};
|
||||
|
||||
knock_room_by_id_helper(&services, sender_user, &room_id, body.reason.clone(), &servers)
|
||||
knock_room_by_id_helper(&services, sender_user, &room_id, body.reason.clone(), servers)
|
||||
.boxed()
|
||||
.await
|
||||
}
|
||||
@@ -151,7 +151,7 @@ async fn knock_room_by_id_helper(
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
reason: Option<String>,
|
||||
servers: &[OwnedServerName],
|
||||
servers: Vec<OwnedServerName>,
|
||||
) -> Result<knock_room::v3::Response> {
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
|
||||
@@ -242,7 +242,7 @@ async fn knock_room_by_id_helper(
|
||||
match services
|
||||
.rooms
|
||||
.membership
|
||||
.join_room(sender_user, room_id, reason.clone(), servers)
|
||||
.join_room(sender_user, room_id, reason.clone(), servers.clone())
|
||||
.await
|
||||
{
|
||||
| Ok(_) => return Ok(knock_room::v3::Response::new(room_id.to_owned())),
|
||||
@@ -326,7 +326,7 @@ async fn knock_room_helper_local(
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
reason: Option<String>,
|
||||
servers: &[OwnedServerName],
|
||||
servers: Vec<OwnedServerName>,
|
||||
state_lock: RoomMutexGuard,
|
||||
) -> Result {
|
||||
debug_info!("We can knock locally");
|
||||
@@ -486,7 +486,7 @@ async fn knock_room_helper_remote(
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
reason: Option<String>,
|
||||
servers: &[OwnedServerName],
|
||||
servers: Vec<OwnedServerName>,
|
||||
state_lock: RoomMutexGuard,
|
||||
) -> Result {
|
||||
info!("Knocking {room_id} over federation.");
|
||||
@@ -694,7 +694,7 @@ async fn make_knock_request(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
servers: &[OwnedServerName],
|
||||
servers: Vec<OwnedServerName>,
|
||||
) -> Result<(federation::membership::prepare_knock_event::v1::Response, OwnedServerName)> {
|
||||
let mut make_knock_response_and_server =
|
||||
Err!(BadServerResponse("No server available to assist in knocking."));
|
||||
@@ -702,7 +702,7 @@ async fn make_knock_request(
|
||||
let mut make_knock_counter: usize = 0;
|
||||
|
||||
for remote_server in servers {
|
||||
if services.globals.server_is_ours(remote_server) {
|
||||
if services.globals.server_is_ours(&remote_server) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -716,7 +716,7 @@ async fn make_knock_request(
|
||||
|
||||
let make_knock_response = services
|
||||
.sending
|
||||
.send_federation_request(remote_server, request)
|
||||
.send_federation_request(&remote_server, request)
|
||||
.await;
|
||||
|
||||
trace!("make_knock response: {make_knock_response:?}");
|
||||
|
||||
@@ -24,5 +24,5 @@ pub(crate) async fn register_client_route(
|
||||
.await
|
||||
.map_err(|err| (StatusCode::BAD_REQUEST, Json(err)).into_response())?;
|
||||
|
||||
Ok((StatusCode::CREATED, Json(RegisteredClient { client_id, metadata })).into_response())
|
||||
Ok(Json(RegisteredClient { client_id, metadata }).into_response())
|
||||
}
|
||||
|
||||
@@ -29,9 +29,8 @@
|
||||
},
|
||||
assign,
|
||||
};
|
||||
use service::users::DeviceToken;
|
||||
|
||||
use super::DEVICE_ID_LENGTH;
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `GET /_matrix/client/v3/login`
|
||||
@@ -197,8 +196,8 @@ pub(crate) async fn login_route(
|
||||
.clone()
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
// Generate a new token for the device
|
||||
let token = DeviceToken::new_random();
|
||||
// Generate a new token for the device (ensuring no collisions)
|
||||
let token = services.users.generate_unique_token().await;
|
||||
|
||||
// Determine if device_id was provided and exists in the db for this user
|
||||
let device_exists = if body.device_id.is_some() {
|
||||
@@ -214,7 +213,7 @@ pub(crate) async fn login_route(
|
||||
if device_exists {
|
||||
services
|
||||
.users
|
||||
.set_token(&user_id, &device_id, token.clone())
|
||||
.set_token(&user_id, &device_id, &token, None)
|
||||
.await?;
|
||||
} else {
|
||||
services
|
||||
@@ -222,7 +221,8 @@ pub(crate) async fn login_route(
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
Some(token.clone()),
|
||||
&token,
|
||||
None,
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
@@ -241,7 +241,7 @@ pub(crate) async fn login_route(
|
||||
info!("{user_id} logged in");
|
||||
|
||||
#[allow(deprecated)]
|
||||
Ok(assign!(login::v3::Response::new(user_id, token.into_token(), device_id), {
|
||||
Ok(assign!(login::v3::Response::new(user_id, token, device_id), {
|
||||
well_known: client_discovery_info,
|
||||
expires_in: None,
|
||||
home_server: Some(services.config.server_name.clone()),
|
||||
@@ -273,7 +273,7 @@ pub(crate) async fn login_token_route(
|
||||
.authenticate_password(&body.auth, sender_user, body.identity.sender_device(), None)
|
||||
.await?;
|
||||
|
||||
let login_token = DeviceToken::new_random().into_token();
|
||||
let login_token = utils::random_string(TOKEN_LENGTH);
|
||||
let expires_in = services.users.create_login_token(sender_user, &login_token);
|
||||
|
||||
Ok(get_login_token::v1::Response::new(
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct AuthQueryParams {
|
||||
pub(super) user_id: Option<String>,
|
||||
/// Device ID for appservice device masquerading (MSC3202/MSC4190).
|
||||
/// Can be provided as `device_id` or `org.matrix.msc3202.device_id`.
|
||||
#[serde(alias = "org.matrix.msc3202.device_id")]
|
||||
pub(super) device_id: Option<String>,
|
||||
#[serde(rename = "org.matrix.msc3202.device_id")]
|
||||
pub(super) legacy_device_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Extractor for Ruma request structs
|
||||
|
||||
+18
-22
@@ -219,29 +219,25 @@ async fn verify<B: AsRef<[u8]> + Sync>(
|
||||
// MSC3202/MSC4190: Handle device_id masquerading for appservices.
|
||||
// The device_id can be provided via `device_id` or
|
||||
// `org.matrix.msc3202.device_id` query parameter.
|
||||
let sender_device = if let Some(device_id) = query
|
||||
.device_id
|
||||
.or(query.legacy_device_id)
|
||||
.as_deref()
|
||||
.map(Into::into)
|
||||
{
|
||||
// Verify the device exists for this user
|
||||
if services
|
||||
.users
|
||||
.get_device_metadata(&sender_user, device_id)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return Err!(Request(Forbidden(
|
||||
"Device does not exist for user or appservice cannot masquerade as this \
|
||||
device."
|
||||
)));
|
||||
}
|
||||
let sender_device =
|
||||
if let Some(device_id) = query.device_id.as_deref().map(Into::into) {
|
||||
// Verify the device exists for this user
|
||||
if services
|
||||
.users
|
||||
.get_device_metadata(&sender_user, device_id)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return Err!(Request(Forbidden(
|
||||
"Device does not exist for user or appservice cannot masquerade as \
|
||||
this device."
|
||||
)));
|
||||
}
|
||||
|
||||
Some(device_id.to_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some(device_id.to_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(ClientIdentity::Appservice {
|
||||
sender_user,
|
||||
|
||||
@@ -307,7 +307,7 @@ async fn validate_invite_state(
|
||||
"PDU in invite state (index {idx}) violates the room event format"
|
||||
)));
|
||||
};
|
||||
let (state_event_room_id, state_event_id, state_event_json) = services
|
||||
let (state_event_room_id, state_event_id, mut state_event_json) = services
|
||||
.rooms
|
||||
.event_handler
|
||||
.parse_incoming_pdu(&raw_pdu, Some(room_version_rules))
|
||||
@@ -351,6 +351,7 @@ async fn validate_invite_state(
|
||||
entry.key(),
|
||||
))),
|
||||
| Entry::Vacant(entry) => {
|
||||
state_event_json.insert("event_id".to_owned(), state_event_id.to_string().into());
|
||||
if entry.key().0 == StateEventType::RoomCreate {
|
||||
// Ensure this is a legal create event.
|
||||
let pdu_event =
|
||||
|
||||
@@ -91,7 +91,7 @@ pub(crate) async fn validate_any_membership_event(
|
||||
expected_room_id: OwnedRoomId,
|
||||
expected_event_id: OwnedEventId,
|
||||
) -> Result<(CanonicalJsonObject, MembershipState, OwnedUserId, OwnedUserId)> {
|
||||
let (template_room_id, template_event_id, pdu) = services
|
||||
let (template_room_id, template_event_id, mut pdu) = services
|
||||
.rooms
|
||||
.event_handler
|
||||
.parse_incoming_pdu(body, Some(room_version_rules))
|
||||
@@ -164,5 +164,6 @@ pub(crate) async fn validate_any_membership_event(
|
||||
err!(Request(InvalidParam("Membership event violates the room event format: {e}")))
|
||||
})?;
|
||||
|
||||
// pdu.insert("event_id".to_owned(), template_event_id.to_string().into());
|
||||
Ok((pdu, membership.into(), sender_user, recipient_user))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use std::io::IsTerminal;
|
||||
|
||||
use conduwuit::{Err, Result, debug, debug_info, error, info, warn};
|
||||
use conduwuit::{Err, Result, debug, debug_info, error, info};
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
use tokio::time::{Duration, sleep};
|
||||
|
||||
@@ -9,16 +7,10 @@
|
||||
pub(super) const SIGNAL: &str = "SIGUSR2";
|
||||
|
||||
impl super::Service {
|
||||
/// Possibly spawn the terminal console at startup if configured and a TTY
|
||||
/// is available.
|
||||
/// Possibly spawn the terminal console at startup if configured.
|
||||
pub(super) async fn console_auto_start(&self) {
|
||||
#[cfg(feature = "console")]
|
||||
if self.services.server.config.admin_console_automatic {
|
||||
if !std::io::stdin().is_terminal() {
|
||||
warn!("Console enabled without a tty available; Not enabling console");
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow more of the startup sequence to execute before spawning
|
||||
tokio::task::yield_now().await;
|
||||
self.console.start().await;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
OAuthError, ResponseMode, Scope, TokenRequest, TokenResponse, TokenType,
|
||||
},
|
||||
},
|
||||
users::{self, DeviceToken},
|
||||
users,
|
||||
};
|
||||
|
||||
pub mod client_metadata;
|
||||
@@ -343,7 +343,7 @@ async fn create_session(
|
||||
client_name: Option<String>,
|
||||
client_id: String,
|
||||
) -> Result<TokenResponse, OAuthError> {
|
||||
let access_token = DeviceToken::new_random().with_max_age(Self::ACCESS_TOKEN_MAX_AGE);
|
||||
let access_token = Self::generate_token();
|
||||
let refresh_token = Self::generate_token();
|
||||
|
||||
let device_id = requested_scopes
|
||||
@@ -376,7 +376,8 @@ async fn create_session(
|
||||
.create_device(
|
||||
&authorizing_user,
|
||||
device_id,
|
||||
Some(access_token.clone()),
|
||||
&access_token,
|
||||
Some(Self::ACCESS_TOKEN_MAX_AGE),
|
||||
client_name,
|
||||
None,
|
||||
)
|
||||
@@ -412,7 +413,7 @@ async fn create_session(
|
||||
);
|
||||
|
||||
Ok(TokenResponse {
|
||||
access_token: access_token.into_token(),
|
||||
access_token,
|
||||
token_type: TokenType::Bearer,
|
||||
expires_in: Self::ACCESS_TOKEN_MAX_AGE.as_secs(),
|
||||
scope: requested_scopes.iter().join(" "),
|
||||
@@ -448,7 +449,7 @@ async fn refresh_session(
|
||||
|
||||
assert_eq!(&client_id, &session_info.client_id, "session info client id mismatch");
|
||||
|
||||
let new_access_token = DeviceToken::new_random().with_max_age(Self::ACCESS_TOKEN_MAX_AGE);
|
||||
let new_access_token = Self::generate_token();
|
||||
let new_refresh_token = Self::generate_token();
|
||||
let scope = session_info.scopes.iter().join(" ");
|
||||
session_info
|
||||
@@ -460,7 +461,8 @@ async fn refresh_session(
|
||||
.set_token(
|
||||
&refresh_token_info.user_id,
|
||||
&refresh_token_info.device_id,
|
||||
new_access_token.clone(),
|
||||
&new_access_token,
|
||||
Some(Self::ACCESS_TOKEN_MAX_AGE),
|
||||
)
|
||||
.await
|
||||
.expect("should be able to set token");
|
||||
@@ -477,7 +479,7 @@ async fn refresh_session(
|
||||
.raw_put(&new_refresh_token, Json(refresh_token_info));
|
||||
|
||||
Ok(TokenResponse {
|
||||
access_token: new_access_token.into_token(),
|
||||
access_token: new_access_token,
|
||||
token_type: TokenType::Bearer,
|
||||
expires_in: Self::ACCESS_TOKEN_MAX_AGE.as_secs(),
|
||||
scope,
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
};
|
||||
use futures::future::ready;
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, ServerName,
|
||||
api::error::ErrorKind, canonical_json::redact, events::StateEventType,
|
||||
room_version_rules::RoomVersionRules,
|
||||
CanonicalJsonObject, EventId, OwnedEventId, ServerName, api::error::ErrorKind,
|
||||
canonical_json::redact, events::StateEventType, room_version_rules::RoomVersionRules,
|
||||
};
|
||||
|
||||
use crate::rooms::{
|
||||
@@ -43,27 +42,11 @@ pub fn pdu_format_check_1(
|
||||
return Err!(Request(BadJson("PDU has too many auth events")));
|
||||
}
|
||||
|
||||
// The m.room.create event is the genesis event and has empty auth_events
|
||||
// by definition, so it is exempt from the checks below requiring or
|
||||
// forbidding the create event in auth_events (it cannot reference itself).
|
||||
let Some(event_type) = pdu_json.get("type").and_then(CanonicalJsonValue::as_str) else {
|
||||
return Err!(Request(BadJson("PDU is missing a type")));
|
||||
};
|
||||
let state_key = pdu_json
|
||||
.get("state_key")
|
||||
.and_then(CanonicalJsonValue::as_str);
|
||||
|
||||
let is_create_event = event_type == "m.room.create" && state_key == Some("");
|
||||
|
||||
if !is_create_event {
|
||||
let create_event_in_auth_events = auth_events.iter().any(|id| id == create_event_id);
|
||||
if !event_format.allow_room_create_in_auth_events && create_event_in_auth_events {
|
||||
return Err!(Request(BadJson("PDU references a create event")));
|
||||
} else if event_format.allow_room_create_in_auth_events
|
||||
&& !create_event_in_auth_events
|
||||
{
|
||||
return Err!(Request(BadJson("PDU does not reference the room create event")));
|
||||
}
|
||||
let create_event_in_auth_events = auth_events.iter().any(|id| id == create_event_id);
|
||||
if !event_format.allow_room_create_in_auth_events && create_event_in_auth_events {
|
||||
return Err!(Request(BadJson("PDU references a create event")));
|
||||
} else if event_format.allow_room_create_in_auth_events && !create_event_in_auth_events {
|
||||
return Err!(Request(BadJson("PDU does not reference the room create event")));
|
||||
}
|
||||
|
||||
let prev_events = expect_event_id_array(pdu_json, "prev_events")?;
|
||||
@@ -83,6 +66,12 @@ pub async fn signature_hash_check_2_3(
|
||||
pdu_json: CanonicalJsonObject,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
) -> Result<CanonicalJsonObject> {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if pdu_json.contains_key("event_id") {
|
||||
conduwuit::warn!("Checking signature of a PDU that has an `event_id` present");
|
||||
}
|
||||
}
|
||||
match self
|
||||
.services
|
||||
.server_keys
|
||||
@@ -141,7 +130,7 @@ pub async fn auth_state_check_4(
|
||||
/// Returns a boolean indicating whether the event is authorised, and also
|
||||
/// the resolved state before the event for later use. Returns an error if
|
||||
/// state fetching or auth checking fails.
|
||||
pub(super) async fn state_before_check_5(
|
||||
pub async fn state_before_check_5(
|
||||
&self,
|
||||
incoming_pdu: &PduEvent,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
use conduwuit::{Result, err, utils, utils::TryFutureExtExt};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedUserId, RoomId, UserId,
|
||||
canonical_json::{redact, to_canonical_value},
|
||||
events::{
|
||||
StateEventType,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
room_version_rules::RoomVersionRules,
|
||||
};
|
||||
|
||||
impl super::Service {
|
||||
/// Creates a membership event locally but seeds it with data from a remote
|
||||
/// template. This means we retain total control over the created PDU, but
|
||||
/// still use the data from the remote server that we have to blindly trust,
|
||||
/// like auth/prev events and depth.
|
||||
pub(super) async fn seed_local_membership_pdu(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
membership: MembershipState,
|
||||
reason: Option<String>,
|
||||
template: CanonicalJsonObject,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
) -> Result<CanonicalJsonObject> {
|
||||
// First we redact the PDU to remove any unrecognised fields.
|
||||
let mut template = redact(template, &room_version_rules.redaction, None)
|
||||
.map_err(|e| err!(Request(BadJson("Failed to redact membership template: {e:?}"))))?;
|
||||
|
||||
// Remove fields that are not necessary but not covered by redaction.
|
||||
template.remove("event_id");
|
||||
template.remove("hashes");
|
||||
template.remove("prev_state");
|
||||
template.remove("origin");
|
||||
template.remove("membership");
|
||||
template.remove("redacts");
|
||||
|
||||
// Force set some fields to known good values.
|
||||
let join_authorized_via_users_server = {
|
||||
if room_version_rules
|
||||
.signatures
|
||||
.check_join_authorised_via_users_server
|
||||
{
|
||||
template
|
||||
.get("content")
|
||||
.map(|s| {
|
||||
s.as_object()?
|
||||
.get("join_authorised_via_users_server")?
|
||||
.as_str()
|
||||
})
|
||||
.and_then(|s| OwnedUserId::try_from(s.unwrap_or_default()).ok())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let mut content = RoomMemberEventContent::new(membership);
|
||||
let (dn, av) = tokio::join!(
|
||||
self.services.users.displayname(user_id).ok(),
|
||||
self.services.users.avatar_url(user_id).ok()
|
||||
);
|
||||
content.displayname = dn;
|
||||
content.avatar_url = av;
|
||||
content.reason = reason;
|
||||
content
|
||||
.join_authorized_via_users_server
|
||||
.clone_from(&join_authorized_via_users_server);
|
||||
template.insert("content".to_owned(), to_canonical_value(content)?);
|
||||
template.insert(
|
||||
"origin_server_ts".to_owned(),
|
||||
CanonicalJsonValue::Integer(
|
||||
utils::millis_since_unix_epoch()
|
||||
.try_into()
|
||||
.expect("Timestamp is valid js_int value"),
|
||||
),
|
||||
);
|
||||
template.insert("room_id".to_owned(), room_id.as_str().into());
|
||||
template.insert("sender".to_owned(), user_id.to_string().into());
|
||||
template.insert("state_key".to_owned(), user_id.to_string().into());
|
||||
template.insert("type".to_owned(), StateEventType::RoomMember.to_string().into());
|
||||
|
||||
Ok(template)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,782 @@
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::{BTreeMap, HashMap},
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use assign::assign;
|
||||
use conduwuit::{
|
||||
Err, Event, PduEvent, Result, debug, debug_error, debug_info, err, error, info,
|
||||
matrix::{StateKey, event::gen_event_id},
|
||||
state_res,
|
||||
state_res::EventTypeExt,
|
||||
trace,
|
||||
utils::{
|
||||
IterStream,
|
||||
stream::{BroadbandExt, WidebandExt, automatic_width},
|
||||
},
|
||||
warn,
|
||||
};
|
||||
use futures::{StreamExt, future::ready};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, EventId, OwnedEventId, OwnedRoomId, RoomId, RoomVersionId, ServerName,
|
||||
UserId,
|
||||
api::{
|
||||
error::{ErrorKind, IncompatibleRoomVersionErrorData},
|
||||
federation::membership::{create_join_event, prepare_join_event},
|
||||
},
|
||||
events::{StateEventType, room::member::MembershipState},
|
||||
room_version_rules::RoomVersionRules,
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
|
||||
use crate::rooms::{
|
||||
event_handler::{DagBuilderTree, build_local_dag},
|
||||
membership::validate_remote_member_event_stub,
|
||||
state::RoomMutexGuard,
|
||||
state_compressor::{CompressedState, HashSetCompressStateEvent},
|
||||
};
|
||||
|
||||
type StateTypeKey = (StateEventType, StateKey);
|
||||
|
||||
pub enum MakeJoinResult {
|
||||
/// A make_join request returned and the response was validated
|
||||
/// successfully.
|
||||
Success((CanonicalJsonObject, RoomVersionId)),
|
||||
/// A make_join response indicated further attempts to join should not be
|
||||
/// made (e.g. Forbidden).
|
||||
Fatal(conduwuit::Error),
|
||||
/// A make_join request failed (e.g. remote server unreachable, or unable to
|
||||
/// fulfill restricted join requirements), but another join attempt through
|
||||
/// another remote should be attempted.
|
||||
Retry,
|
||||
}
|
||||
|
||||
fn state_key_from_json(obj: &CanonicalJsonObject) -> Option<(StateEventType, StateKey)> {
|
||||
let event_type = StateEventType::from(obj.get("type")?.as_str()?);
|
||||
let state_key = StateKey::from_str(obj.get("state_key")?.as_str()?);
|
||||
Some((event_type, state_key))
|
||||
}
|
||||
|
||||
impl super::Service {
|
||||
/// Performs `POST /_matrix/federation/v1/make_join/{room_id}/{user_id}`.
|
||||
///
|
||||
/// If the request is successful, the resulting template is validated to
|
||||
/// ensure the remote server isn't returning a bad event, and then the
|
||||
/// template and room version are returned.
|
||||
/// If validation fails, `Ok(None)` is returned, in
|
||||
///
|
||||
/// If the request is unsuccessful, either an `Err` or `Ok(None)` will be
|
||||
/// returned, depending on the reason for the failure. If the join loop
|
||||
/// should terminate (for example, a `M_FORBIDDEN` error was returned,
|
||||
/// indicating we cannot join), then an `Err` will be returned. If the join
|
||||
/// loop should continue because the request failed due to conditional
|
||||
/// problems (e.g. server offline or unable to grant join), then `Ok(None)`
|
||||
/// will be returned.
|
||||
pub async fn make_join(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
via: &ServerName,
|
||||
) -> MakeJoinResult {
|
||||
assert!(
|
||||
self.services.globals.user_is_local(user_id),
|
||||
"can only make_join for local users"
|
||||
);
|
||||
|
||||
let request = assign!(
|
||||
prepare_join_event::v1::Request::new(room_id.to_owned(), user_id.to_owned()),
|
||||
{ ver: self.services.server.supported_room_versions().collect() }
|
||||
);
|
||||
|
||||
info!("Asking {via} for make_join");
|
||||
let make_join_response = self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(via, request)
|
||||
.await
|
||||
.inspect(|_| info!("{via} finished make_join successfully"))
|
||||
.inspect_err(|e| debug_info!("{via} failed to make_join: {e:?}"));
|
||||
|
||||
let make_join_response = match make_join_response {
|
||||
| Ok(r) => r,
|
||||
| Err(e) => return Self::handle_make_join_error(room_id, via, e),
|
||||
};
|
||||
|
||||
// Make sure the remote server hasn't sent us garbage
|
||||
let Ok(template) = serde_json::from_str(make_join_response.event.get())
|
||||
.inspect_err(|e| warn!("{via} returned an invalid membership template: {e}"))
|
||||
else {
|
||||
return MakeJoinResult::Retry;
|
||||
};
|
||||
if let Err(e) =
|
||||
validate_remote_member_event_stub(&MembershipState::Join, user_id, room_id, &template)
|
||||
{
|
||||
warn!("{via} returned an illegal membership template event: {e:?}");
|
||||
return MakeJoinResult::Retry;
|
||||
}
|
||||
|
||||
MakeJoinResult::Success((
|
||||
template,
|
||||
make_join_response.room_version.unwrap_or(RoomVersionId::V1),
|
||||
))
|
||||
}
|
||||
|
||||
/// Handles an error response from make_join, determining whether it should
|
||||
/// be fatal (and returning `MakeJoinResult::Fatal(e)`), or
|
||||
/// temporary/situational and returning `MakeJoinResult::Retry`.
|
||||
fn handle_make_join_error(
|
||||
room_id: &RoomId,
|
||||
via: &ServerName,
|
||||
e: conduwuit::Error,
|
||||
) -> MakeJoinResult {
|
||||
match e.kind() {
|
||||
| ErrorKind::UnableToAuthorizeJoin => {
|
||||
info!(
|
||||
"{via} was unable to verify the joining user satisfied restricted join \
|
||||
requirements: {e}."
|
||||
);
|
||||
MakeJoinResult::Retry
|
||||
},
|
||||
| ErrorKind::UnableToGrantJoin => {
|
||||
info!(
|
||||
"{via} believes the joining user satisfies restricted join rules, but is \
|
||||
unable to authorise a join for us."
|
||||
);
|
||||
MakeJoinResult::Retry
|
||||
},
|
||||
| ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData {
|
||||
room_version,
|
||||
..
|
||||
}) => {
|
||||
warn!(
|
||||
"{via} reports the room we are trying to join is v{room_version}, which we \
|
||||
do not support."
|
||||
);
|
||||
MakeJoinResult::Fatal(e)
|
||||
},
|
||||
| ErrorKind::Forbidden => {
|
||||
warn!("{via} refuses to let us join: {e}.");
|
||||
MakeJoinResult::Fatal(e)
|
||||
},
|
||||
| ErrorKind::NotFound => {
|
||||
info!("{via} does not know about {room_id}: {e}.");
|
||||
MakeJoinResult::Retry
|
||||
},
|
||||
| ErrorKind::Unknown if e.status_code().is_server_error() => {
|
||||
match e.status_code() {
|
||||
| http::StatusCode::BAD_GATEWAY | http::StatusCode::SERVICE_UNAVAILABLE =>
|
||||
info!("{via} is unavailable: {e}"),
|
||||
| http::StatusCode::GATEWAY_TIMEOUT =>
|
||||
info!("{via} timed out while handling make_join: {e}"),
|
||||
| _ => info!("{via} encountered an internal server error: {e}."),
|
||||
}
|
||||
MakeJoinResult::Retry
|
||||
},
|
||||
| _ => {
|
||||
info!("{via} unexpectedly failed to make_join: {e}.");
|
||||
MakeJoinResult::Retry
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_join(
|
||||
&self,
|
||||
room_id: OwnedRoomId,
|
||||
event_id: OwnedEventId,
|
||||
event: &CanonicalJsonObject,
|
||||
via: &ServerName,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
state_lock: &RoomMutexGuard,
|
||||
) -> Result<Option<()>> {
|
||||
let send_join_request = create_join_event::v2::Request::new(
|
||||
room_id.clone(),
|
||||
event_id.clone(),
|
||||
self.services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(event.clone())
|
||||
.await,
|
||||
);
|
||||
|
||||
info!("Asking {via} for send_join in room {room_id}");
|
||||
let Ok(send_join_response) = self
|
||||
.services
|
||||
.sending
|
||||
.send_slow_federation_request(via, send_join_request)
|
||||
.await
|
||||
.inspect(|resp| {
|
||||
info!(
|
||||
"{via} finished send_join successfully, returning {} state events and {} \
|
||||
authentication events.",
|
||||
resp.room_state.state.len(),
|
||||
resp.room_state.auth_chain.len(),
|
||||
);
|
||||
})
|
||||
.inspect_err(|e| debug_error!("{via} failed to send_join: {e:?}"))
|
||||
else {
|
||||
return Ok(None); // Try another server
|
||||
};
|
||||
self.services
|
||||
.short
|
||||
.get_or_create_shortroomid(&room_id)
|
||||
.await;
|
||||
|
||||
info!("Acquiring server signing keys for room state events");
|
||||
self.services
|
||||
.server_keys
|
||||
.acquire_events_pubkeys(
|
||||
send_join_response
|
||||
.room_state
|
||||
.state
|
||||
.iter()
|
||||
.chain(send_join_response.room_state.auth_chain.iter()),
|
||||
)
|
||||
.await;
|
||||
|
||||
info!("Building state map");
|
||||
let (create_event, untrusted_state_before) = match self
|
||||
.build_state_map(&send_join_response, &room_id, room_version_rules)
|
||||
.await
|
||||
{
|
||||
| Ok(result) => result,
|
||||
| Err(e) => {
|
||||
error!("Failed to build state map: {e}");
|
||||
// This usually happens when the remote server sends a malformed response (e.g.
|
||||
// forgotten m.room.create event). In this case, we might succeed with another
|
||||
// server, so we will return Ok(None) to encourage the caller to re-attempt.
|
||||
return Ok(None);
|
||||
},
|
||||
};
|
||||
|
||||
info!(
|
||||
"Handling returned room state authentication events ({} events)",
|
||||
send_join_response.room_state.auth_chain.len()
|
||||
);
|
||||
let auth_map = self
|
||||
.process_send_join_auth_chain(
|
||||
&send_join_response,
|
||||
&room_id,
|
||||
room_version_rules,
|
||||
&create_event,
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Handling returned room state ({} events)", untrusted_state_before.len());
|
||||
let untrusted_state_before_by_id = untrusted_state_before
|
||||
.values()
|
||||
.map(|value| {
|
||||
(
|
||||
gen_event_id(value, room_version_rules)
|
||||
.expect("must be able to generate event ID"),
|
||||
value,
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let state_before = self
|
||||
.process_send_join_state(
|
||||
untrusted_state_before_by_id,
|
||||
&auth_map,
|
||||
room_version_rules,
|
||||
&create_event,
|
||||
)
|
||||
.await?;
|
||||
let state_before_by_id = state_before
|
||||
.values()
|
||||
.map(|value| (value.event_id().to_owned(), value))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
info!("Authorizing join event");
|
||||
let final_membership_event =
|
||||
if let Some(event) = send_join_response.room_state.event.as_ref() {
|
||||
event
|
||||
} else {
|
||||
&to_raw_value(&event)
|
||||
.expect("must be able to convert CanonicalJsonObject to RawJsonValue")
|
||||
};
|
||||
|
||||
let (membership_pdu, membership_pdu_json) = self
|
||||
.process_send_join_membership_event(
|
||||
final_membership_event,
|
||||
&event_id,
|
||||
&state_before,
|
||||
&state_before_by_id,
|
||||
room_version_rules,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// We've successfully joined now, and can update our local state cache.
|
||||
info!("Compressing resolved state");
|
||||
let compressed_state_map = state_before
|
||||
.into_iter()
|
||||
.stream()
|
||||
.broad_filter_map(|(key, pdu)| async move {
|
||||
let ssk = self
|
||||
.services
|
||||
.short
|
||||
.get_or_create_shortstatekey(&key.0, key.1.as_str())
|
||||
.await;
|
||||
Some((ssk, pdu.event_id.clone()))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
let compressed: CompressedState = self
|
||||
.services
|
||||
.state_compressor
|
||||
.compress_state_events(
|
||||
compressed_state_map
|
||||
.iter()
|
||||
.map(|(ssk, eid)| (ssk, eid.borrow())),
|
||||
)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
debug!("Saving compressed state");
|
||||
let HashSetCompressStateEvent {
|
||||
shortstatehash: statehash_before_join,
|
||||
added,
|
||||
removed,
|
||||
} = self
|
||||
.services
|
||||
.state_compressor
|
||||
.save_state(&room_id, Arc::new(compressed))
|
||||
.await?;
|
||||
|
||||
debug!("Updating state cache");
|
||||
self.services
|
||||
.state
|
||||
.force_state(&room_id, statehash_before_join, added, removed, state_lock)
|
||||
.await?;
|
||||
|
||||
let statehash_after_join = self
|
||||
.services
|
||||
.state
|
||||
.append_to_state(&membership_pdu, &room_id)
|
||||
.await?;
|
||||
|
||||
debug!("Promoting membership event");
|
||||
self.services
|
||||
.timeline
|
||||
.append_pdu(
|
||||
&membership_pdu,
|
||||
membership_pdu_json,
|
||||
std::iter::once(membership_pdu.event_id()),
|
||||
state_lock,
|
||||
&room_id,
|
||||
)
|
||||
.await
|
||||
.inspect(|_| info!("Finished joining {room_id}"))?;
|
||||
self.services
|
||||
.metadata
|
||||
.maybe_set_mindepth(&room_id, membership_pdu.depth.into())
|
||||
.await;
|
||||
|
||||
info!("Setting final room state for new room");
|
||||
// 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
|
||||
self.services
|
||||
.state
|
||||
.set_room_state(&room_id, statehash_after_join, state_lock);
|
||||
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn build_state_map(
|
||||
&self,
|
||||
response: &create_join_event::v2::Response,
|
||||
room_id: &RoomId,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
) -> Result<(PduEvent, HashMap<StateTypeKey, CanonicalJsonObject>)> {
|
||||
// NOTE: this is untrusted as we haven't performed any of the PDU checks yet.
|
||||
// Events in this map should not be persisted blindly.
|
||||
let untrusted_state_before = response
|
||||
.room_state
|
||||
.state
|
||||
.iter()
|
||||
.stream()
|
||||
.wide_filter_map(async |pdu| {
|
||||
let (event_room_id, event_id, value) = self
|
||||
.services
|
||||
.event_handler
|
||||
.parse_incoming_pdu(pdu, Some(room_version_rules))
|
||||
.await
|
||||
.inspect_err(|e| warn!("Invalid PDU in room state (dropping): {e:?}"))
|
||||
.ok()?;
|
||||
if event_room_id != room_id {
|
||||
warn!(%event_id, expected=%room_id, actual=%event_room_id, "PDU in room state belongs to a different room (dropping)");
|
||||
return None;
|
||||
}
|
||||
// value.insert("event_id".to_owned(), event_id.to_string().into());
|
||||
if self.services.pdu_metadata.is_event_rejected(&event_id).await {
|
||||
// Rejection will be re-evaluated later
|
||||
trace!(%event_id, "Un-rejecting event");
|
||||
self.services.pdu_metadata.unmark_event_rejected(&event_id);
|
||||
}
|
||||
Some((state_key_from_json(&value)?, value))
|
||||
})
|
||||
.collect::<HashMap<(StateEventType, StateKey), CanonicalJsonObject>>()
|
||||
.await;
|
||||
|
||||
let create_event_json = untrusted_state_before
|
||||
.get(&StateEventType::RoomCreate.with_state_key(""))
|
||||
.ok_or_else(|| {
|
||||
err!("Room state returned from send_join did not contain a room create event")
|
||||
})?;
|
||||
let create_event_id = gen_event_id(create_event_json, room_version_rules)
|
||||
.map_err(|e| err!("Failed to generate event ID for room create event: {e:?}"))?;
|
||||
let create_event = PduEvent::from_id_val(&create_event_id, create_event_json.clone())?;
|
||||
|
||||
Ok((create_event, untrusted_state_before))
|
||||
}
|
||||
|
||||
async fn process_send_join_auth_chain(
|
||||
&self,
|
||||
response: &create_join_event::v2::Response,
|
||||
room_id: &RoomId,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
create_event: &PduEvent,
|
||||
) -> Result<BTreeMap<OwnedEventId, PduEvent>> {
|
||||
let auth_chain_timing_start = Instant::now();
|
||||
let unauthed_auth_chain = response
|
||||
.room_state
|
||||
.auth_chain
|
||||
.iter()
|
||||
.stream()
|
||||
.broad_filter_map(|value| async move {
|
||||
self
|
||||
.services
|
||||
.event_handler
|
||||
.parse_incoming_pdu(
|
||||
&to_raw_value(&value)
|
||||
.expect("CanonicalJsonObject just convert to RawJsonValue"),
|
||||
Some(room_version_rules),
|
||||
)
|
||||
.await
|
||||
.inspect_err(|e| warn!("Invalid PDU in send_join auth chain (dropping): {e:?}"))
|
||||
.ok()
|
||||
})
|
||||
.broad_filter_map(|(auth_room_id, event_id, value)| async move {
|
||||
if self.services.pdu_metadata.is_event_rejected(&event_id).await {
|
||||
// Rejection will be re-evaluated later
|
||||
trace!(%event_id, "Un-rejecting event");
|
||||
self.services.pdu_metadata.unmark_event_rejected(&event_id);
|
||||
}
|
||||
trace!(%event_id, "Validating event");
|
||||
if auth_room_id != room_id {
|
||||
warn!(%event_id, %room_id, %auth_room_id, "PDU in send_join auth chain belongs to a different room (dropping)");
|
||||
return None;
|
||||
}
|
||||
|
||||
crate::rooms::event_handler::Service::pdu_format_check_1(&value, room_version_rules, create_event.event_id()).inspect_err(|e| {
|
||||
warn!(%event_id, "PDU in send_join auth chain failed format check (dropping): {e:?}");
|
||||
}).ok()?;
|
||||
|
||||
let mut value = self.services.event_handler.signature_hash_check_2_3(value, room_version_rules).await.inspect_err(|e| {
|
||||
warn!(%event_id, "PDU in send_join auth chain failed signature check (dropping): {e:?}");
|
||||
}).ok()?;
|
||||
|
||||
value.insert("event_id".to_owned(), event_id.as_str().into());
|
||||
Some((event_id, value))
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
.await;
|
||||
debug!(
|
||||
elapsed=?auth_chain_timing_start.elapsed(),
|
||||
"Finished validating auth chain ({}/{} events passed validation)",
|
||||
unauthed_auth_chain.len(),
|
||||
response.room_state.auth_chain.len()
|
||||
);
|
||||
let auth_chain = build_local_dag(&unauthed_auth_chain, DagBuilderTree::AuthEvents)
|
||||
.await?
|
||||
.into_iter()
|
||||
.stream()
|
||||
.wide_filter_map(async |event_id| {
|
||||
// Perform PDU check 4 to make sure the event doesn't need
|
||||
// rejecting
|
||||
let pdu =
|
||||
PduEvent::from_id_val(&event_id, unauthed_auth_chain[&event_id].clone())
|
||||
.expect("We already validated auth chian PDUs");
|
||||
let auth_state = pdu
|
||||
.auth_events()
|
||||
.filter_map(|event_id| {
|
||||
PduEvent::from_id_val(
|
||||
event_id,
|
||||
unauthed_auth_chain.get(event_id)?.clone(),
|
||||
)
|
||||
.map(|pdu| {
|
||||
Some((
|
||||
pdu.kind().with_state_key(
|
||||
pdu.state_key()
|
||||
.expect("Auth chain events must have a state key"),
|
||||
),
|
||||
pdu,
|
||||
))
|
||||
})
|
||||
.ok()?
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
self.services
|
||||
.event_handler
|
||||
.auth_state_check_4(
|
||||
&pdu,
|
||||
room_version_rules,
|
||||
create_event.as_pdu(),
|
||||
&auth_state,
|
||||
)
|
||||
.await
|
||||
.inspect(|result| {
|
||||
if !*result {
|
||||
// NOTE: usually we don't *drop* events that fail PDU check 4, but
|
||||
// send_join handling is special. Instead of rejecting this event and
|
||||
// consequently every other event that depends on it, we simply drop
|
||||
// it from the chain
|
||||
warn!(%event_id, "Auth chain event failed self-authorization (dropping)");
|
||||
}
|
||||
})
|
||||
.inspect_err(
|
||||
|e| error!(%event_id, ?e, "Failed to run auth check on auth chain event"),
|
||||
)
|
||||
.unwrap_or_default()
|
||||
.then_some((event_id, pdu))
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>()
|
||||
.await;
|
||||
debug!(
|
||||
elapsed=?auth_chain_timing_start.elapsed(),
|
||||
"Finished authentication auth chain ({}/{} events passed authentication)",
|
||||
auth_chain.len(),
|
||||
unauthed_auth_chain.len()
|
||||
);
|
||||
// Now we need to persist them
|
||||
auth_chain
|
||||
.keys()
|
||||
.stream()
|
||||
.for_each_concurrent(automatic_width(), async |event_id| {
|
||||
if self.services.timeline.get_pdu(event_id).await.is_err() {
|
||||
// Only add events that we don't already have
|
||||
self.services
|
||||
.outlier
|
||||
.add_pdu_outlier(event_id, &unauthed_auth_chain[event_id]);
|
||||
}
|
||||
})
|
||||
.await;
|
||||
info!(elapsed=?auth_chain_timing_start.elapsed(), "Finished processing authentication events");
|
||||
|
||||
Ok(auth_chain)
|
||||
}
|
||||
|
||||
async fn process_send_join_state(
|
||||
&self,
|
||||
untrusted_state_before: HashMap<OwnedEventId, &CanonicalJsonObject>,
|
||||
auth_chain: &BTreeMap<OwnedEventId, PduEvent>,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
create_event: &PduEvent,
|
||||
) -> Result<HashMap<StateTypeKey, PduEvent>> {
|
||||
let state_timing_start = Instant::now();
|
||||
let state_size = untrusted_state_before.len();
|
||||
// NOTE: unlike the auth chain, we need to run PDU checks 1 through 4.
|
||||
let state_before = untrusted_state_before
|
||||
.into_iter()
|
||||
.stream()
|
||||
.broad_filter_map(|(event_id, value)| async move {
|
||||
crate::rooms::event_handler::Service::pdu_format_check_1(
|
||||
value,
|
||||
room_version_rules,
|
||||
create_event.event_id(),
|
||||
)
|
||||
.inspect_err(|e| {
|
||||
warn!(%event_id, "PDU in send_join state failed format check (dropping): {e:?}");
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
let mut value = self
|
||||
.services
|
||||
.event_handler
|
||||
.signature_hash_check_2_3(value.clone(), room_version_rules)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!(%event_id, "PDU in send_join state failed signature check (dropping): {e:?}");
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
value.insert("event_id".to_owned(), event_id.as_str().into());
|
||||
|
||||
let pdu = PduEvent::from_id_val(&event_id, value.clone())
|
||||
.expect("We already validated this state event");
|
||||
let auth_state = pdu
|
||||
.auth_events()
|
||||
.filter_map(|event_id| {
|
||||
auth_chain.get(event_id).map(|p| {
|
||||
(
|
||||
p.kind().with_state_key(
|
||||
p.state_key()
|
||||
.expect("auth chain events must have a state key"),
|
||||
),
|
||||
p.clone(),
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
self.services
|
||||
.event_handler
|
||||
.auth_state_check_4(
|
||||
&pdu,
|
||||
room_version_rules,
|
||||
create_event.as_pdu(),
|
||||
&auth_state,
|
||||
)
|
||||
.await
|
||||
.inspect(|result| {
|
||||
if !*result {
|
||||
warn!(%event_id, "State event failed self-authorization (dropping)");
|
||||
}
|
||||
})
|
||||
.inspect_err(
|
||||
|e| error!(%event_id, ?e, "Failed to run auth check on state event"),
|
||||
)
|
||||
.unwrap_or_default()
|
||||
.then_some((pdu, value))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
debug!(
|
||||
elapsed=?state_timing_start.elapsed(),
|
||||
"Finished validation and authentication of room state ({}/{} events retained)",
|
||||
state_before.len(),
|
||||
state_size,
|
||||
);
|
||||
|
||||
state_before
|
||||
.iter()
|
||||
.stream()
|
||||
.for_each_concurrent(automatic_width(), async |(pdu, pdu_json)| {
|
||||
if self
|
||||
.services
|
||||
.timeline
|
||||
.get_pdu(pdu.event_id())
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
// Only add events that we don't already have
|
||||
self.services
|
||||
.outlier
|
||||
.add_pdu_outlier(pdu.event_id(), pdu_json);
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
info!(
|
||||
elapsed=?state_timing_start.elapsed(),
|
||||
"Finished processing send join state ({}/{} events)",
|
||||
state_before.len(),
|
||||
state_size,
|
||||
);
|
||||
|
||||
let state_before_by_key = state_before
|
||||
.into_iter()
|
||||
.map(|(pdu, _)| {
|
||||
(
|
||||
pdu.kind().with_state_key(
|
||||
pdu.state_key().expect("state event must have a state key"),
|
||||
),
|
||||
pdu,
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
Ok(state_before_by_key)
|
||||
}
|
||||
|
||||
async fn process_send_join_membership_event(
|
||||
&self,
|
||||
membership_event: &serde_json::value::RawValue,
|
||||
event_id: &EventId,
|
||||
state_before: &HashMap<StateTypeKey, PduEvent>,
|
||||
state_before_by_id: &HashMap<OwnedEventId, &PduEvent>,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
) -> Result<(PduEvent, CanonicalJsonObject)> {
|
||||
let (_, calculated_event_id, value) = self
|
||||
.services
|
||||
.event_handler
|
||||
.parse_incoming_pdu(membership_event, Some(room_version_rules))
|
||||
.await?;
|
||||
|
||||
if calculated_event_id != event_id {
|
||||
return Err!(Request(InvalidParam(debug_warn!(
|
||||
expected=%event_id,
|
||||
actual=%calculated_event_id,
|
||||
"Remote server returned a different membership event to the one we sent it"
|
||||
))));
|
||||
}
|
||||
|
||||
if self.services.pdu_metadata.is_event_rejected(event_id).await {
|
||||
// Rejection will be re-evaluated later
|
||||
trace!(%event_id, "Un-rejecting event");
|
||||
self.services.pdu_metadata.unmark_event_rejected(event_id);
|
||||
}
|
||||
|
||||
let create_event = state_before
|
||||
.get(&StateEventType::RoomCreate.with_state_key(""))
|
||||
.expect("create event must be in state before");
|
||||
crate::rooms::event_handler::Service::pdu_format_check_1(
|
||||
&value,
|
||||
room_version_rules,
|
||||
create_event.event_id(),
|
||||
)?;
|
||||
|
||||
let mut value = self
|
||||
.services
|
||||
.event_handler
|
||||
.signature_hash_check_2_3(value, room_version_rules)
|
||||
.await?;
|
||||
|
||||
value.insert("event_id".to_owned(), event_id.as_str().into());
|
||||
|
||||
let pdu = PduEvent::from_id_val(event_id, value.clone())
|
||||
.expect("We already validated this state event");
|
||||
|
||||
// Perform check 4 to make sure the PDU is self-authorised
|
||||
let auth_state = pdu
|
||||
.auth_events()
|
||||
.filter_map(|event_id| {
|
||||
state_before_by_id
|
||||
.get(event_id)
|
||||
.copied()
|
||||
.map(|p| (p.kind().with_state_key(p.state_key().unwrap()), p.clone()))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
if !self
|
||||
.services
|
||||
.event_handler
|
||||
.auth_state_check_4(&pdu, room_version_rules, create_event.as_pdu(), &auth_state)
|
||||
.await?
|
||||
{
|
||||
return Err!(Request(Forbidden("Membership event was not self-authorised")));
|
||||
}
|
||||
|
||||
// We can also perform check 5 (state before), since the state returned in the
|
||||
// response is the state just before our join. However, state_before_check_5
|
||||
// (part of the event_handler service) won't understand this, so we have to do
|
||||
// the check manually.
|
||||
let state_fetch =
|
||||
|k: StateEventType, s: StateKey| ready(state_before.get(&(k, s)).cloned());
|
||||
|
||||
// PDU check: 5
|
||||
let auth_check = state_res::event_auth::auth_check(
|
||||
room_version_rules,
|
||||
&pdu,
|
||||
None, // TODO: third party invite
|
||||
|ty, sk| state_fetch(ty.clone(), sk.into()),
|
||||
create_event.as_pdu(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
|
||||
if !auth_check {
|
||||
return Err!(Request(Forbidden(
|
||||
"Membership event was not authorised based on the state before it"
|
||||
)));
|
||||
}
|
||||
|
||||
Ok((pdu, value))
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,17 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
mod event;
|
||||
mod join;
|
||||
|
||||
use std::{collections::VecDeque, sync::Arc};
|
||||
|
||||
use conduwuit::{
|
||||
Err, Event, Pdu, Result, Server, debug, debug_info, debug_warn, err, error, info, is_true,
|
||||
matrix::{
|
||||
StateKey,
|
||||
event::{gen_event_id, gen_event_id_canonical_json},
|
||||
},
|
||||
pdu::PartialPdu,
|
||||
state_res, trace,
|
||||
utils::{self, IterStream, ReadyExt, to_canonical_object},
|
||||
warn,
|
||||
Err, Result, Server, debug, debug_info, debug_warn, info, is_true,
|
||||
matrix::event::gen_event_id, pdu::PartialPdu, warn,
|
||||
};
|
||||
use database::Database;
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt, join};
|
||||
use futures::{FutureExt, StreamExt, join};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
|
||||
RoomVersionId, UserId,
|
||||
api::{
|
||||
error::{ErrorKind, IncompatibleRoomVersionErrorData},
|
||||
federation,
|
||||
},
|
||||
canonical_json::to_canonical_value,
|
||||
events::{
|
||||
StateEventType, StaticEventContent,
|
||||
room::{
|
||||
@@ -34,11 +25,13 @@
|
||||
use crate::{
|
||||
Dep, antispam, globals,
|
||||
rooms::{
|
||||
event_handler, metadata, outlier, pdu_metadata, short,
|
||||
event_handler,
|
||||
membership::join::MakeJoinResult,
|
||||
metadata, outlier, pdu_metadata, short,
|
||||
state::{self, RoomMutexGuard},
|
||||
state_accessor, state_cache,
|
||||
state_compressor::{self, CompressedState, HashSetCompressStateEvent},
|
||||
timeline::{self, pdu_fits},
|
||||
state_compressor::{self},
|
||||
timeline,
|
||||
},
|
||||
sending, server_keys, sync, users,
|
||||
};
|
||||
@@ -99,18 +92,19 @@ fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||
}
|
||||
|
||||
impl Service {
|
||||
/// Join a local user to a room.
|
||||
/// Join a local user to a room. If the room is not local, this will attempt
|
||||
/// to join via federation. If the room cannot be joined locally, a
|
||||
/// federation join may be attempted. Users trying to join a room they're
|
||||
/// already joined to will short-circuit.
|
||||
pub async fn join_room(
|
||||
&self,
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
reason: Option<String>,
|
||||
servers: &[OwnedServerName],
|
||||
servers: Vec<OwnedServerName>,
|
||||
) -> Result<OwnedRoomId> {
|
||||
assert!(self.services.globals.user_is_local(sender_user), "user should be local");
|
||||
|
||||
let state_lock = self.services.state.mutex.lock(room_id).await;
|
||||
|
||||
if self
|
||||
.services
|
||||
.state_cache
|
||||
@@ -187,12 +181,12 @@ pub async fn join_room(
|
||||
}
|
||||
|
||||
if server_in_room {
|
||||
self.join_local_room(sender_user, room_id, reason, servers, state_lock)
|
||||
self.join_local_room(sender_user, room_id, reason, servers)
|
||||
.boxed()
|
||||
.await?;
|
||||
} else {
|
||||
// Ask a remote server if we are not participating in this room
|
||||
self.join_remote_room(sender_user, room_id, reason, servers, state_lock)
|
||||
self.join_remote_room(sender_user, room_id, reason, servers)
|
||||
.boxed()
|
||||
.await?;
|
||||
}
|
||||
@@ -206,11 +200,11 @@ async fn join_local_room(
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
reason: Option<String>,
|
||||
servers: &[OwnedServerName],
|
||||
state_lock: RoomMutexGuard,
|
||||
servers: Vec<OwnedServerName>,
|
||||
) -> Result {
|
||||
info!("Joining room locally");
|
||||
|
||||
let state_lock = self.services.state.mutex.lock(room_id).await;
|
||||
let (room_version, join_rules, is_invited) = join!(
|
||||
self.services.state.get_room_version(room_id),
|
||||
self.services.state_accessor.get_join_rules(room_id),
|
||||
@@ -264,6 +258,7 @@ async fn join_local_room(
|
||||
info!("Joined room locally");
|
||||
return Ok(());
|
||||
};
|
||||
drop(state_lock);
|
||||
|
||||
if servers.is_empty()
|
||||
|| servers.len() == 1 && self.services.globals.server_is_ours(&servers[0])
|
||||
@@ -286,7 +281,7 @@ async fn join_local_room(
|
||||
remote_servers = %servers.len(),
|
||||
"Could not join room locally, attempting remote join",
|
||||
);
|
||||
Box::pin(self.join_remote_room(sender_user, room_id, reason, servers, state_lock)).await
|
||||
Box::pin(self.join_remote_room(sender_user, room_id, reason, servers)).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote_room", level = "info")]
|
||||
@@ -295,104 +290,83 @@ pub async fn join_remote_room(
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
reason: Option<String>,
|
||||
servers: &[OwnedServerName],
|
||||
state_lock: RoomMutexGuard,
|
||||
vias: Vec<OwnedServerName>,
|
||||
) -> Result {
|
||||
// public so the admin command force-join-room-remotely works
|
||||
info!("Joining {room_id} over federation.");
|
||||
|
||||
let (make_join_response, remote_server) = self
|
||||
.make_join_request(sender_user, room_id, servers)
|
||||
.await?;
|
||||
// Treat the via list as a "priority queue", and each time a make_join fails,
|
||||
// move the server to the back of the queue. This way, if a server is down, we
|
||||
// will eventually try all servers in the list.
|
||||
let mut priority_queue = VecDeque::from(vias.clone());
|
||||
let mut template: Option<CanonicalJsonObject> = None;
|
||||
let mut room_version: Option<RoomVersionId> = None;
|
||||
let via_count = vias.len();
|
||||
|
||||
for via in vias {
|
||||
let response = self.make_join(room_id, sender_user, &via).await;
|
||||
match response {
|
||||
| MakeJoinResult::Success((t, r)) => {
|
||||
template = Some(t);
|
||||
room_version = Some(r);
|
||||
break;
|
||||
},
|
||||
| MakeJoinResult::Fatal(e) => {
|
||||
return Err(e);
|
||||
},
|
||||
| MakeJoinResult::Retry => {
|
||||
// Pop the front to remove this server from the queue, and
|
||||
// then push it to the back.
|
||||
// We need to do this vec iter + dequeue to avoid infinite
|
||||
// looping when we reprioritise.
|
||||
let _ = priority_queue.pop_front();
|
||||
priority_queue.push_back(via);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if template.is_none() {
|
||||
info!("All {} servers were unable to assist in joining {room_id} :(", via_count);
|
||||
return Err!(BadServerResponse("No server available to assist in joining."));
|
||||
}
|
||||
let room_version =
|
||||
room_version.expect("room_version cannot be None while template is Some");
|
||||
|
||||
info!("make_join finished");
|
||||
|
||||
let room_version = make_join_response.room_version.unwrap_or(RoomVersionId::V1);
|
||||
let room_version_rules = room_version
|
||||
.rules()
|
||||
.expect("room version should have defined rules");
|
||||
|
||||
if !self.services.server.supported_room_version(&room_version) {
|
||||
// How did we get here?
|
||||
return Err!(BadServerResponse(
|
||||
"Remote room version {room_version} is not supported"
|
||||
));
|
||||
}
|
||||
let room_version_rules = room_version
|
||||
.rules()
|
||||
.expect("room version should have defined rules");
|
||||
|
||||
let mut join_event_stub: CanonicalJsonObject =
|
||||
serde_json::from_str(make_join_response.event.get()).map_err(|e| {
|
||||
err!(BadServerResponse(warn!(
|
||||
"Invalid make_join event json received from server: {e:?}"
|
||||
)))
|
||||
})?;
|
||||
|
||||
let join_authorized_via_users_server = {
|
||||
if room_version_rules
|
||||
.signatures
|
||||
.check_join_authorised_via_users_server
|
||||
{
|
||||
join_event_stub
|
||||
.get("content")
|
||||
.map(|s| {
|
||||
s.as_object()?
|
||||
.get("join_authorised_via_users_server")?
|
||||
.as_str()
|
||||
})
|
||||
.and_then(|s| OwnedUserId::try_from(s.unwrap_or_default()).ok())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
join_event_stub.insert(
|
||||
"origin_server_ts".to_owned(),
|
||||
CanonicalJsonValue::Integer(
|
||||
utils::millis_since_unix_epoch()
|
||||
.try_into()
|
||||
.expect("Timestamp is valid js_int value"),
|
||||
),
|
||||
);
|
||||
|
||||
let mut join_content = RoomMemberEventContent::new(MembershipState::Join);
|
||||
join_content.displayname = self.services.users.displayname(sender_user).await.ok();
|
||||
join_content.avatar_url = self.services.users.avatar_url(sender_user).await.ok();
|
||||
join_content.reason = reason;
|
||||
join_content
|
||||
.join_authorized_via_users_server
|
||||
.clone_from(&join_authorized_via_users_server);
|
||||
|
||||
join_event_stub.insert(
|
||||
"content".to_owned(),
|
||||
to_canonical_value(join_content).expect("event is valid, we just created it"),
|
||||
);
|
||||
|
||||
// Remove event id if it exists
|
||||
join_event_stub.remove("event_id");
|
||||
let mut template = self
|
||||
.seed_local_membership_pdu(
|
||||
room_id,
|
||||
sender_user,
|
||||
MembershipState::Join,
|
||||
reason,
|
||||
template.unwrap(),
|
||||
&room_version_rules,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// In order to create a compatible ref hash (EventID) the `hashes` field needs
|
||||
// to be present
|
||||
self.services
|
||||
.server_keys
|
||||
.hash_and_sign_event(&mut join_event_stub, &room_version_rules)?;
|
||||
.hash_and_sign_event(&mut template, &room_version_rules)?;
|
||||
|
||||
// Generate event id
|
||||
let event_id = gen_event_id(&join_event_stub, &room_version_rules)?;
|
||||
let event_id = gen_event_id(&template, &room_version_rules)?;
|
||||
|
||||
// Add event_id back
|
||||
join_event_stub
|
||||
template
|
||||
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
|
||||
|
||||
// It has enough fields to be called a proper event now
|
||||
let mut join_event = join_event_stub;
|
||||
let send_join_request = federation::membership::create_join_event::v2::Request::new(
|
||||
room_id.to_owned(),
|
||||
event_id.clone(),
|
||||
self.services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(join_event.clone())
|
||||
.await,
|
||||
);
|
||||
|
||||
// NOTE: send_join can take a long time to respond, but from the point of view
|
||||
// of other servers, we may already have finished joining. This means they
|
||||
// sometimes end up sending PDUs to us that we aren't yet ready to accept, and
|
||||
@@ -404,368 +378,32 @@ pub async fn join_remote_room(
|
||||
.mutex_federation
|
||||
.lock(room_id.as_str())
|
||||
.await;
|
||||
info!("Asking {remote_server} for send_join in room {room_id}");
|
||||
let send_join_response = match self
|
||||
.services
|
||||
.sending
|
||||
.send_slow_federation_request(&remote_server, send_join_request)
|
||||
.await
|
||||
{
|
||||
| Ok(response) => response,
|
||||
| Err(e) => {
|
||||
error!("send_join failed: {e}");
|
||||
return Err(e);
|
||||
},
|
||||
};
|
||||
|
||||
info!("send_join finished");
|
||||
|
||||
if join_authorized_via_users_server.is_some() {
|
||||
if let Some(signed_raw) = &send_join_response.room_state.event {
|
||||
debug_info!(
|
||||
"There is a signed event with join_authorized_via_users_server. This room \
|
||||
is probably using restricted joins. Adding signature to our event"
|
||||
);
|
||||
|
||||
let (signed_event_id, signed_value) =
|
||||
gen_event_id_canonical_json(signed_raw, &room_version_rules).map_err(
|
||||
|e| {
|
||||
err!(Request(BadJson(warn!(
|
||||
"Could not convert event to canonical JSON: {e}"
|
||||
))))
|
||||
},
|
||||
)?;
|
||||
|
||||
if signed_event_id != event_id {
|
||||
return Err!(Request(BadJson(warn!(
|
||||
%signed_event_id, %event_id,
|
||||
"Server {remote_server} sent event with wrong event ID"
|
||||
))));
|
||||
}
|
||||
|
||||
match signed_value["signatures"]
|
||||
.as_object()
|
||||
.ok_or_else(|| {
|
||||
err!(BadServerResponse(warn!(
|
||||
"Server {remote_server} sent invalid signatures type"
|
||||
)))
|
||||
})
|
||||
.and_then(|e| {
|
||||
e.get(remote_server.as_str()).ok_or_else(|| {
|
||||
err!(BadServerResponse(warn!(
|
||||
"Server {remote_server} did not send its signature for a \
|
||||
restricted room"
|
||||
)))
|
||||
})
|
||||
}) {
|
||||
| Ok(signature) => {
|
||||
join_event
|
||||
.get_mut("signatures")
|
||||
.expect("we created a valid pdu")
|
||||
.as_object_mut()
|
||||
.expect("we created a valid pdu")
|
||||
.insert(remote_server.to_string(), signature.clone());
|
||||
},
|
||||
| Err(e) => {
|
||||
warn!(
|
||||
"Server {remote_server} sent invalid signature in send_join \
|
||||
signatures for event {signed_value:?}: {e:?}",
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.services.short.get_or_create_shortroomid(room_id).await;
|
||||
|
||||
info!("Parsing join event");
|
||||
let parsed_join_pdu = Pdu::from_id_val(&event_id, join_event.clone())
|
||||
.map_err(|e| err!(BadServerResponse("Invalid join event PDU: {e:?}")))?;
|
||||
|
||||
info!("Acquiring server signing keys for response events");
|
||||
let resp_events = &send_join_response.room_state;
|
||||
let resp_state = &resp_events.state;
|
||||
let resp_auth = &resp_events.auth_chain;
|
||||
self.services
|
||||
.server_keys
|
||||
.acquire_events_pubkeys(resp_auth.iter().chain(resp_state.iter()))
|
||||
.await;
|
||||
|
||||
info!("Going through send_join response room_state");
|
||||
let cork = self.services.db.cork_and_flush();
|
||||
let state = send_join_response
|
||||
.room_state
|
||||
.state
|
||||
.iter()
|
||||
.stream()
|
||||
.then(|pdu| {
|
||||
self.services
|
||||
.server_keys
|
||||
.validate_and_add_event_id_no_fetch(pdu, &room_version_rules)
|
||||
.inspect_err(|e| {
|
||||
debug_warn!(
|
||||
"Could not validate send_join response room_state event: {e:?}"
|
||||
);
|
||||
})
|
||||
.inspect(|_| {
|
||||
debug!("Completed validating send_join response room_state event");
|
||||
})
|
||||
})
|
||||
.ready_filter_map(Result::ok)
|
||||
.fold(HashMap::new(), |mut state, (event_id, value)| async move {
|
||||
let pdu = match Pdu::from_id_val(&event_id, value.clone()) {
|
||||
| Ok(pdu) => pdu,
|
||||
| Err(e) => {
|
||||
debug_warn!("Invalid PDU in send_join response: {e:?}: {value:#?}");
|
||||
return state;
|
||||
},
|
||||
};
|
||||
if !pdu_fits(&value.clone()) {
|
||||
warn!(
|
||||
"dropping incoming PDU {event_id} in room {room_id} from room join \
|
||||
because it exceeds 65535 bytes or is otherwise too large."
|
||||
);
|
||||
return state;
|
||||
}
|
||||
self.services.outlier.add_pdu_outlier(&event_id, &value);
|
||||
self.services.pdu_metadata.clear_pdu_markers(&event_id);
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
let shortstatekey = self
|
||||
.services
|
||||
.short
|
||||
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)
|
||||
.await;
|
||||
|
||||
state.insert(shortstatekey, pdu.event_id.clone());
|
||||
}
|
||||
state
|
||||
})
|
||||
.await;
|
||||
|
||||
drop(cork);
|
||||
|
||||
info!("Going through send_join response auth_chain");
|
||||
let cork = self.services.db.cork_and_flush();
|
||||
send_join_response
|
||||
.room_state
|
||||
.auth_chain
|
||||
.iter()
|
||||
.stream()
|
||||
.then(|pdu| {
|
||||
self.services
|
||||
.server_keys
|
||||
.validate_and_add_event_id_no_fetch(pdu, &room_version_rules)
|
||||
})
|
||||
.ready_filter_map(Result::ok)
|
||||
.ready_for_each(|(event_id, value)| {
|
||||
trace!(%event_id, "Adding PDU as an outlier from send_join auth_chain");
|
||||
self.services.outlier.add_pdu_outlier(&event_id, &value);
|
||||
self.services.pdu_metadata.clear_pdu_markers(&event_id);
|
||||
})
|
||||
.await;
|
||||
|
||||
drop(cork);
|
||||
|
||||
debug!("Running send_join auth check");
|
||||
let fetch_state = &state;
|
||||
let state_fetch = |k: StateEventType, s: StateKey| async move {
|
||||
let shortstatekey = self.services.short.get_shortstatekey(&k, &s).await.ok()?;
|
||||
|
||||
let event_id = fetch_state.get(&shortstatekey)?;
|
||||
self.services.timeline.get_pdu(event_id).await.ok()
|
||||
};
|
||||
|
||||
let auth_check = state_res::event_auth::auth_check(
|
||||
&room_version.rules().unwrap(),
|
||||
&parsed_join_pdu,
|
||||
None, // TODO: third party invite
|
||||
|k, s| state_fetch(k.clone(), s.into()),
|
||||
&state_fetch(StateEventType::RoomCreate, "".into())
|
||||
.await
|
||||
.expect("create event is missing from send_join auth"),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| err!(Request(Forbidden(warn!("Auth check failed: {e:?}")))))?;
|
||||
|
||||
if !auth_check {
|
||||
return Err!(Request(Forbidden("Auth check failed")));
|
||||
}
|
||||
let resident_before = self
|
||||
.services
|
||||
.state_cache
|
||||
.server_in_room(self.services.globals.server_name(), room_id)
|
||||
.await;
|
||||
|
||||
let cork = self.services.db.cork_and_flush();
|
||||
info!("Compressing state from send_join");
|
||||
let compressed: CompressedState = self
|
||||
.services
|
||||
.state_compressor
|
||||
.compress_state_events(state.iter().map(|(ssk, eid)| (ssk, eid.as_ref())))
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
debug!("Saving compressed state");
|
||||
let HashSetCompressStateEvent {
|
||||
shortstatehash: statehash_before_join,
|
||||
added,
|
||||
removed,
|
||||
} = self
|
||||
.services
|
||||
.state_compressor
|
||||
.save_state(room_id, Arc::new(compressed))
|
||||
.await?;
|
||||
|
||||
debug!("Forcing state for new room");
|
||||
self.services
|
||||
.state
|
||||
.force_state(room_id, statehash_before_join, added, removed, &state_lock)
|
||||
.await?;
|
||||
|
||||
debug!("Updating joined counts for new room");
|
||||
self.services.state_cache.update_joined_count(room_id).await;
|
||||
|
||||
// We append to state before appending the pdu, so we don't have a moment in
|
||||
// time with the pdu without it's state. This is okay because append_pdu can't
|
||||
// fail.
|
||||
let statehash_after_join = self
|
||||
.services
|
||||
.state
|
||||
.append_to_state(&parsed_join_pdu, room_id)
|
||||
.await?;
|
||||
|
||||
info!("Appending new room join event");
|
||||
self.services
|
||||
.timeline
|
||||
.append_pdu(
|
||||
&parsed_join_pdu,
|
||||
join_event,
|
||||
std::iter::once(parsed_join_pdu.event_id.as_ref()),
|
||||
&state_lock,
|
||||
room_id,
|
||||
)
|
||||
.await?;
|
||||
self.services
|
||||
.metadata
|
||||
.maybe_set_mindepth(room_id, parsed_join_pdu.depth.into())
|
||||
.await;
|
||||
|
||||
info!("Setting final room state for new room");
|
||||
// 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
|
||||
self.services
|
||||
.state
|
||||
.set_room_state(room_id, statehash_after_join, &state_lock);
|
||||
if !resident_before {
|
||||
// NOTE: We replace local extremities for this room if we were not a resident
|
||||
// before. We might be doing a remote join to satisfy restricted join rules,
|
||||
// so we don't want to do this if we're already a resident. Otherwise, we
|
||||
// want to replace our forward extremities whole-sale in case we were
|
||||
// desynced.
|
||||
info!("Replacing local forward extremities");
|
||||
self.services
|
||||
.state
|
||||
.set_forward_extremities(
|
||||
room_id,
|
||||
std::iter::once(parsed_join_pdu.event_id()),
|
||||
let state_lock = self.services.state.mutex.lock(room_id).await;
|
||||
while let Some(via) = priority_queue.pop_front() {
|
||||
let cork = self.services.db.cork_and_sync();
|
||||
let result = self
|
||||
.send_join(
|
||||
room_id.to_owned(),
|
||||
event_id.clone(),
|
||||
&template,
|
||||
&via,
|
||||
&room_version_rules,
|
||||
&state_lock,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
drop(cork);
|
||||
|
||||
self.services.sync.wake_all_joined(room_id).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn make_join_request(
|
||||
&self,
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
servers: &[OwnedServerName],
|
||||
) -> Result<(federation::membership::prepare_join_event::v1::Response, OwnedServerName)> {
|
||||
let mut make_join_counter: usize = 1;
|
||||
|
||||
for remote_server in servers {
|
||||
if self.services.globals.server_is_ours(remote_server) {
|
||||
.await?;
|
||||
if result.is_none() {
|
||||
continue;
|
||||
}
|
||||
info!(
|
||||
"Asking {remote_server} for make_join (attempt {make_join_counter}/{})",
|
||||
servers.len()
|
||||
);
|
||||
|
||||
let mut request = federation::membership::prepare_join_event::v1::Request::new(
|
||||
room_id.to_owned(),
|
||||
sender_user.to_owned(),
|
||||
);
|
||||
request.ver = self.services.server.supported_room_versions().collect();
|
||||
|
||||
let make_join_response = self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(remote_server, request)
|
||||
.await;
|
||||
|
||||
trace!("make_join response: {:?}", make_join_response);
|
||||
make_join_counter = make_join_counter.saturating_add(1);
|
||||
|
||||
match make_join_response {
|
||||
| Ok(response) => {
|
||||
info!("Received make_join response from {remote_server}");
|
||||
if let Err(e) = validate_remote_member_event_stub(
|
||||
&MembershipState::Join,
|
||||
sender_user,
|
||||
room_id,
|
||||
&to_canonical_object(&response.event)?,
|
||||
) {
|
||||
warn!("make_join response from {remote_server} failed validation: {e}");
|
||||
continue;
|
||||
}
|
||||
return Ok((response, remote_server.clone()));
|
||||
},
|
||||
| Err(e) => match e.kind() {
|
||||
| ErrorKind::UnableToAuthorizeJoin => {
|
||||
info!(
|
||||
"{remote_server} was unable to verify the joining user satisfied \
|
||||
restricted join requirements: {e}. Will continue trying."
|
||||
);
|
||||
},
|
||||
| ErrorKind::UnableToGrantJoin => {
|
||||
info!(
|
||||
"{remote_server} believes the joining user satisfies restricted \
|
||||
join rules, but is unable to authorise a join for us. Will \
|
||||
continue trying."
|
||||
);
|
||||
},
|
||||
| ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData {
|
||||
room_version,
|
||||
..
|
||||
}) => {
|
||||
warn!(
|
||||
"{remote_server} reports the room we are trying to join is \
|
||||
v{room_version}, which we do not support."
|
||||
);
|
||||
return Err(e);
|
||||
},
|
||||
| ErrorKind::Forbidden => {
|
||||
warn!("{remote_server} refuses to let us join: {e}.");
|
||||
return Err(e);
|
||||
},
|
||||
| ErrorKind::NotFound => {
|
||||
info!(
|
||||
"{remote_server} does not know about {room_id}: {e}. Will continue \
|
||||
trying."
|
||||
);
|
||||
},
|
||||
| _ => {
|
||||
info!("{remote_server} failed to make_join: {e}. Will continue trying.");
|
||||
},
|
||||
},
|
||||
}
|
||||
info!("send_join finished");
|
||||
drop(cork);
|
||||
self.services.sync.wake_all_joined(room_id).await;
|
||||
return Ok(());
|
||||
}
|
||||
info!("All {} servers were unable to assist in joining {room_id} :(", servers.len());
|
||||
|
||||
info!(
|
||||
"Despite producing a membership template, no server was capable of actually \
|
||||
completing the join for us."
|
||||
);
|
||||
Err!(BadServerResponse("No server available to assist in joining."))
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@ pub async fn get_pdu_outlier(&self, event_id: &EventId) -> Result<PduEvent> {
|
||||
|
||||
/// Append the PDU as an outlier.
|
||||
pub fn add_pdu_outlier(&self, event_id: &EventId, pdu: &CanonicalJsonObject) {
|
||||
debug_assert!(
|
||||
pdu.get("event_id").is_some(),
|
||||
"event_id must be present in database events"
|
||||
);
|
||||
self.db.eventid_outlierpdu.raw_put(event_id, Json(pdu));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use conduwuit::{
|
||||
Err, PduCount, PduEvent, Result, at, err,
|
||||
Err, Event, PduCount, PduEvent, Result, at, err,
|
||||
result::NotFound,
|
||||
utils::{self, stream::TryReadyExt},
|
||||
};
|
||||
@@ -176,6 +176,15 @@ pub(super) async fn append_pdu(
|
||||
count: PduCount,
|
||||
) {
|
||||
debug_assert!(matches!(count, PduCount::Normal(_)), "PduCount not Normal");
|
||||
debug_assert!(
|
||||
json.get("event_id").is_some(),
|
||||
"event_id must be present in database events"
|
||||
);
|
||||
debug_assert_eq!(
|
||||
json.get("event_id").unwrap().as_str().unwrap(),
|
||||
pdu.event_id().as_str(),
|
||||
"event ID mismatch between PDU JSON and PDU object"
|
||||
);
|
||||
|
||||
self.pduid_pdu.raw_put(pdu_id, Json(json));
|
||||
self.eventid_pduid.insert(pdu.event_id.as_bytes(), pdu_id);
|
||||
@@ -188,6 +197,10 @@ pub(super) fn prepend_backfill_pdu(
|
||||
event_id: &EventId,
|
||||
json: &CanonicalJsonObject,
|
||||
) {
|
||||
debug_assert!(
|
||||
json.get("event_id").is_some(),
|
||||
"event_id must be present in database events"
|
||||
);
|
||||
self.pduid_pdu.raw_put(pdu_id, Json(json));
|
||||
self.eventid_pduid.insert(event_id, pdu_id);
|
||||
self.eventid_outlierpdu.remove(event_id);
|
||||
@@ -202,7 +215,10 @@ pub(super) async fn replace_pdu(
|
||||
if self.pduid_pdu.get(pdu_id).await.is_not_found() {
|
||||
return Err!(Request(NotFound("PDU does not exist.")));
|
||||
}
|
||||
|
||||
debug_assert!(
|
||||
pdu_json.get("event_id").is_some(),
|
||||
"event_id must be present in database events"
|
||||
);
|
||||
self.pduid_pdu.raw_put(pdu_id, Json(pdu_json));
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -18,6 +18,12 @@ pub fn hash_and_sign_event(
|
||||
object: &mut CanonicalJsonObject,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
) -> Result {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if object.contains_key("event_id") {
|
||||
conduwuit::warn!("Signing a PDU that has an `event_id` present");
|
||||
}
|
||||
}
|
||||
hash_and_sign_event(
|
||||
self.services.globals.server_name().as_str(),
|
||||
self.keypair(),
|
||||
|
||||
@@ -244,7 +244,7 @@ pub async fn create_local_account(
|
||||
user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[
|
||||
vec![
|
||||
self.services.globals.server_name().to_owned(),
|
||||
room_server_name.to_owned(),
|
||||
],
|
||||
|
||||
@@ -41,6 +41,7 @@ pub async fn set_dehydrated_device(&self, user_id: &UserId, request: Request) ->
|
||||
self.create_device(
|
||||
user_id,
|
||||
&request.device_id,
|
||||
"",
|
||||
None,
|
||||
request.initial_device_display_name.clone(),
|
||||
None,
|
||||
|
||||
+29
-38
@@ -11,44 +11,20 @@
|
||||
use futures::{Stream, StreamExt};
|
||||
use ruma::{
|
||||
DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, UserId,
|
||||
api::client::device::Device, assign, events::AnyToDeviceEvent, serde::Raw, uint,
|
||||
api::client::device::Device, events::AnyToDeviceEvent, serde::Raw, uint,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::users::increment;
|
||||
|
||||
#[must_use]
|
||||
#[derive(Clone)]
|
||||
pub struct DeviceToken {
|
||||
token: String,
|
||||
max_age: Option<Duration>,
|
||||
}
|
||||
|
||||
impl DeviceToken {
|
||||
const DEVICE_ID_LENGTH: usize = 10;
|
||||
|
||||
pub fn new(token: String) -> Self { Self { token, max_age: None } }
|
||||
|
||||
pub fn new_random() -> Self { Self::new(utils::random_string(Self::DEVICE_ID_LENGTH)) }
|
||||
|
||||
pub fn with_max_age(self, max_age: Duration) -> Self {
|
||||
assign!(self, { max_age: Some(max_age) })
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn into_token(self) -> String { self.token }
|
||||
}
|
||||
|
||||
impl super::Service {
|
||||
/// Adds a new device to a user.
|
||||
///
|
||||
/// If no `token` is provided, the device will not be able to be logged
|
||||
/// into.
|
||||
pub async fn create_device(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
token: Option<DeviceToken>,
|
||||
token: &str,
|
||||
token_max_age: Option<Duration>,
|
||||
initial_device_display_name: Option<String>,
|
||||
client_ip: Option<String>,
|
||||
) -> Result<()> {
|
||||
@@ -62,12 +38,8 @@ pub async fn create_device(
|
||||
|
||||
increment(&self.db.userid_devicelistversion, user_id.as_bytes());
|
||||
self.db.userdeviceid_metadata.put(key, Json(device));
|
||||
|
||||
if let Some(token) = token {
|
||||
self.set_token(user_id, device_id, token).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
self.set_token(user_id, device_id, token, token_max_age)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Removes a device from a user.
|
||||
@@ -125,12 +97,31 @@ pub async fn get_token(&self, user_id: &UserId, device_id: &DeviceId) -> Result<
|
||||
self.db.userdeviceid_token.qry(&key).await.deserialized()
|
||||
}
|
||||
|
||||
/// Generate a unique access token that doesn't collide with existing tokens
|
||||
pub async fn generate_unique_token(&self) -> String {
|
||||
loop {
|
||||
let token = utils::random_string(32);
|
||||
|
||||
// Check for collision with existing appservice and user tokens
|
||||
let (appservice, usr) = tokio::join!(
|
||||
self.services.appservice.find_from_token(&token),
|
||||
self.db.token_userdeviceid.get(&token)
|
||||
);
|
||||
if appservice.is_ok() || usr.is_ok() {
|
||||
continue;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces the access token of one device.
|
||||
pub async fn set_token(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
DeviceToken { token, max_age }: DeviceToken,
|
||||
token: &str,
|
||||
token_max_age: Option<Duration>,
|
||||
) -> Result<()> {
|
||||
let key = (user_id, device_id);
|
||||
if self.db.userdeviceid_metadata.qry(&key).await.is_err() {
|
||||
@@ -145,7 +136,7 @@ pub async fn set_token(
|
||||
if self
|
||||
.services
|
||||
.appservice
|
||||
.find_from_token(&token)
|
||||
.find_from_token(token)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
@@ -162,10 +153,10 @@ pub async fn set_token(
|
||||
}
|
||||
|
||||
// Assign token to user device combination
|
||||
self.db.userdeviceid_token.put_raw(key, &token);
|
||||
self.db.token_userdeviceid.raw_put(&token, key);
|
||||
self.db.userdeviceid_token.put_raw(key, token);
|
||||
self.db.token_userdeviceid.raw_put(token, key);
|
||||
|
||||
if let Some(max_age) = max_age {
|
||||
if let Some(max_age) = token_max_age {
|
||||
let expires = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.expect("system time should not be before the epoch")
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
utils::{self},
|
||||
};
|
||||
use database::Map;
|
||||
pub use device::DeviceToken;
|
||||
pub use profile::ProfileFieldChange;
|
||||
use ruma::{UserId, api::error::ErrorKind, encryption::CrossSigningKey, serde::Raw};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -155,7 +155,7 @@ async fn get_account_deeplink(
|
||||
));
|
||||
};
|
||||
|
||||
format!("device/{device_id}/remove")
|
||||
format!("device/{device_id}/delete")
|
||||
},
|
||||
| AccountManagementAction::DeviceView => {
|
||||
let Some(device_id) = query.device_id else {
|
||||
|
||||
Reference in New Issue
Block a user