mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-07-17 16:32:28 +00:00
Compare commits
97
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba0ec6ba70 | ||
|
|
1ac05838dd | ||
|
|
7ecd2aa5e2 | ||
|
|
69a3145983 | ||
|
|
73b7553b1e | ||
|
|
232ec3f620 | ||
|
|
e52bbeaf94 | ||
|
|
ad1c194fb0 | ||
|
|
5e2dcd8e79 | ||
|
|
24c6474bd1 | ||
|
|
642d05048b | ||
|
|
446ae93ad7 | ||
|
|
9ca0774b2d | ||
|
|
ce494eb0c0 | ||
|
|
1f5e178c3f | ||
|
|
ddd050d402 | ||
|
|
6ad3e679bc | ||
|
|
65812bb246 | ||
|
|
9f3a7994c7 | ||
|
|
baef3289fe | ||
|
|
9a94b93ddd | ||
|
|
0ac0bd93ae | ||
|
|
fe9f718c6e | ||
|
|
e333f5a8e5 | ||
|
|
b8c64a68d4 | ||
|
|
163f049757 | ||
|
|
f48125c37a | ||
|
|
4001b99261 | ||
|
|
b6e5a106ff | ||
|
|
e1eea45fd6 | ||
|
|
7cb53f41d6 | ||
|
|
fbf81fcdeb | ||
|
|
87030a3a22 | ||
|
|
4f509fa113 | ||
|
|
aa7ed885c0 | ||
|
|
d5e6e617d1 | ||
|
|
1f7680ad5f | ||
|
|
0c37a542d4 | ||
|
|
efd07da0d7 | ||
|
|
f0590e882a | ||
|
|
7225bf25ae | ||
|
|
e52bb5db69 | ||
|
|
995726923b | ||
|
|
556141b404 | ||
|
|
6858e1b893 | ||
|
|
07e241f616 | ||
|
|
cfbe590f1c | ||
|
|
d9c2f04d73 | ||
|
|
ada0b3184b | ||
|
|
1d6ad485f6 | ||
|
|
9d2ce72f73 | ||
|
|
13bcb5af13 | ||
|
|
5868510df1 | ||
|
|
c34d1fe76e | ||
|
|
838764b0ac | ||
|
|
b51a34c1c1 | ||
|
|
2fbb779d12 | ||
|
|
e16a62e1ed | ||
|
|
64a08262ca | ||
|
|
70a3905761 | ||
|
|
cf9179e8f6 | ||
|
|
6b899b7f80 | ||
|
|
ad57fa06fc | ||
|
|
ec9efbada5 | ||
|
|
318d2b205a | ||
|
|
99a5f2a9fb | ||
|
|
ace0d52074 | ||
|
|
0b1e24a9ad | ||
|
|
f55207d5c7 | ||
|
|
3f2fae4ca7 | ||
|
|
081f865705 | ||
|
|
0d6608a2f8 | ||
|
|
abf96140b2 | ||
|
|
35fb56aafd | ||
|
|
03bde0e5fa | ||
|
|
5e306f160a | ||
|
|
fb2eedd5fd | ||
|
|
a76c5c95aa | ||
|
|
7f83ec3883 | ||
|
|
9b14ab4c0b | ||
|
|
b915692ae1 | ||
|
|
bed80964fd | ||
|
|
bc7e3185ef | ||
|
|
7082914d7d | ||
|
|
8fb14f666f | ||
|
|
8b6a176901 | ||
|
|
2e1015846d | ||
|
|
5d2e840859 | ||
|
|
2fabf18451 | ||
|
|
63a26141f8 | ||
|
|
cc1f667c2b | ||
|
|
89dcda4c3c | ||
|
|
62730e861a | ||
|
|
68d018bd37 | ||
|
|
959370169a | ||
|
|
f71f6e922c | ||
|
|
ce185aff13 |
@@ -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@50414676f9f5d50a65992c6dd2ed02641263226c # v2
|
||||
uses: https://github.com/taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853 # 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@718ea10b132b3b2eba29c1007bb80653f286566b # v3
|
||||
uses: https://github.com/softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # 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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
uses: https://github.com/actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 📦 Setup Node.js
|
||||
uses: https://github.com/actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
uses: https://github.com/actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ jobs:
|
||||
CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }}
|
||||
TARGET_CPU=
|
||||
RUST_PROFILE=release
|
||||
CARGO_FEATURES=default
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.prepare.outputs.metadata_labels }}
|
||||
annotations: ${{ steps.prepare.outputs.metadata_annotations }}
|
||||
@@ -161,6 +162,7 @@ jobs:
|
||||
CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }}
|
||||
TARGET_CPU=${{ matrix.target_cpu }}
|
||||
RUST_PROFILE=release-max-perf
|
||||
CARGO_FEATURES=default,release_max_log_level
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.prepare.outputs.metadata_labels }}
|
||||
annotations: ${{ steps.prepare.outputs.metadata_annotations }}
|
||||
@@ -216,7 +218,7 @@ jobs:
|
||||
path: binaries
|
||||
merge-multiple: true
|
||||
- name: Create Release and Upload
|
||||
uses: https://github.com/softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3
|
||||
uses: https://github.com/softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3
|
||||
with:
|
||||
draft: true
|
||||
files: binaries/*
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
name: Renovate
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/renovatebot/renovate:43.252.1@sha256:121eb04ef758537019fb58f587aa53c99e5fda4e703993ce2ca01bd4b3926bd4
|
||||
image: ghcr.io/renovatebot/renovate:43.260.2@sha256:60d830108d9ff6408d11a55d5f110ee0976bed78a6b82b08211e3ddb72f72952
|
||||
options: --tmpfs /tmp:exec
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -1,3 +1,93 @@
|
||||
# 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
|
||||
|
||||
- Added enforcement for new federated invite checks and corrected a bunch of related spec compliance issues along the way. Contributed by @nex. (#1952)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Fixed existing accounts failing to link when logging in with OIDC if `prompt_for_localpart` was `false`. (#1942)
|
||||
- Authentication is no longer required on the `/_matrix/client/v3/account/3pid/email/requestToken` endpoint. (#1953)
|
||||
- Fixed newly created rooms failing to sync properly in clients using legacy sync.
|
||||
- Stopped appservice users from being erroneously marked as deactivated during a 26.6 database migration.
|
||||
- Whitespace will now automatically be trimmed from the start and end of the `global.oauth.oidc.client_secret_file`.
|
||||
|
||||
|
||||
# Continuwuity 26.6.0 (2026-07-10)
|
||||
|
||||
## Features
|
||||
|
||||
- Added support for linking an external identity provider with OIDC. Contributed by @ginger. (#765)
|
||||
- Updated [MSC4284: Policy Servers](https://github.com/matrix-org/matrix-spec-proposals/pull/4284) implementation to support the newly stabilised proposal. Contributed by @nex. (#1487)
|
||||
- Added config option for default room ACLs. Contributed by @eve. (#1691)
|
||||
- Added support for fallback encryption keys. (#1710)
|
||||
- Add `!admin users reject-all-invites` to clean invite spam (#1741)
|
||||
- Implemented event rejection, which should resolve and prevent future netsplits of the kinds observed
|
||||
within some Continuwuity rooms.
|
||||
Also resolved several bugs related to both soft-failing events, and event backfilling, which should
|
||||
improve state resolution stability.
|
||||
The `!admin debug get-pdu` command was updated to disambiguate event acceptance status, and
|
||||
`!admin debug show-auth-chain` was added to visually display event auth chains, which may assist
|
||||
developers in debugging strangely complex events.
|
||||
|
||||
Contributed by @nex. (#1747)
|
||||
- Added full support for [MSC4168: Update `m.space.*` state on room upgrade](https://github.com/matrix-org/matrix-spec-proposals/pull/4168). Contributed by @nex. (#1807)
|
||||
- Improved the performance and reliability of fetching missing events, improving network partition recovery. Contributed
|
||||
by @nex. (#1818)
|
||||
- Added static builds using Nix, allowing for Continuwuity on musl. During this, we also introduced a `max-perf-haswell` package, separating it from `max-perf`, so you may want to swap to this if you are on NixOS. Contributed by @Henry-Hiles (QuadRadical). (#1853)
|
||||
- Added support for MSC4380 invite blocking, which has become part of the Matrix specification in v1.18. Contributed by @nex. (#1875)
|
||||
- Added `!admin debug get-state-at` command (#1877)
|
||||
- Added a configuration option to allow choosing a client IP source that is not the TCP connecting IP. Contributed by @nex. (#1931)
|
||||
- Added support for MSC4466, which allows clients to customize how changes to a user's global profile are propagated. Contributed by @ginger.
|
||||
- Added support for Matrix 1.16's `state_after` feature, allowing clients which understand it to sync room state changes more reliably. Contributed by @ginger.
|
||||
- Added support for authenticating clients using the new OAuth 2.0 login API. Contributed by @ginger.
|
||||
- Appservice device management as outlined in MSC4190 (part of Matrix 1.17) is now fully supported. Contributed by @ginger.
|
||||
- Users may now be forbidden from deactivating their own accounts with the new `allow_deactivation` config option. Contributed by @ginger.
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Adjusted legacy sync logic to allow the `roomsynctoken_shortstatehash` database column to be dropped, massively reducing database sizes, especially for old deployments. Contributed by @ginger. (#917)
|
||||
- Fixed a bug that caused the server to drop events during processing if several events for the same room were sent in a singular transaction. Contributed by @nex. (#1711)
|
||||
- fix `!admin query account-data account-data-get` not returning the content (#1742)
|
||||
- Fixed an issue where Continuwuity would only advertise support for the unstable endpoint for Mutual Rooms (MSC2666), despite only supporting the stable endpoint. Contributed by @Henry-Hiles (QuadRadical) (#1752)
|
||||
- Fixed admin commands being ignored when they had leading whitespace before admin commands. Contributed by @kitvonsnookerz. (#1804)
|
||||
- Fixed several bugs in the `POST /_matrix/client/v3/rooms/{roomId}/upgrade` endpoint. Contributed by @nex. (#1807)
|
||||
- Devices which set their presence as "offline" will no longer be considered for presence updates. Contributed by @timedout.
|
||||
- Improved invite and join reliability in clients using legacy sync. Contributed by @ginger
|
||||
- The invite recipient's membership event is now included in invite stripped state, which should fix flaky invite display in some clients. Contributed by @ginger
|
||||
|
||||
## Improved Documentation
|
||||
|
||||
- Add performance tuning documentation. Contributed by @stratself. (#1498)
|
||||
- Explain accessing Continuwuity's server console when deployed via Docker. (#1671)
|
||||
- Clarified in the config that `max_request_size` affects federated media as well. (#1706)
|
||||
- Added example configuration using caddy-docker-proxy in the livekit setup section of the docs. Contributed by @Cease (#1762)
|
||||
- Updated deployment docs to account for new RPM package availability across more distros. Contributed by @julian45. (#1912)
|
||||
|
||||
## Deprecations and Removals
|
||||
|
||||
- Removed support for LDAP. (#1701)
|
||||
- Removed support for guest user registration, a little-used and deprecated approach to room previews.
|
||||
- Removed the `/_conduwuit/` versions of the `local_user_count` and `version` routes. These routes are still accessible under the `/_continuwuity` prefix.
|
||||
- Support for server-side blurhashing (part of MSC2448) has been removed.
|
||||
- The deprecated `well_known.rtc_focus_server_urls` config option has been removed. MatrixRTC foci should be configured using the `matrix_rtc.foci` config option.
|
||||
|
||||
## Misc
|
||||
|
||||
- #1505, #1829, #1927, #1933, #1934
|
||||
- Switched from Continuwuity's fork of Ruma back to upstream Ruma. Contributed by @ginger.
|
||||
- The version of Debian that the Docker-based build process uses has been upgraded from Bookworm to Trixie, meaning that standalone binaries now have a minimum glibc of 2.41, and can no longer be used on distro versions from before 2025-01-30
|
||||
|
||||
|
||||
# Continuwuity 0.5.8 (2026-04-24)
|
||||
|
||||
## Features
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ ### Pre-commit Checks
|
||||
|
||||
|
||||
```bash
|
||||
# Install prek using cargo-binstall
|
||||
# Install prek using cargo-binstall https://github.com/cargo-bins/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](/development/code_style.md)
|
||||
2. Your code follows the [code style guide](docs/development/code_style.mdx)
|
||||
3. Your commit messages follow the conventional commits format
|
||||
4. Tests are added for new functionality
|
||||
5. Documentation is updated if needed
|
||||
|
||||
Generated
+150
-247
File diff suppressed because it is too large
Load Diff
+4
-10
@@ -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.0-rc.1"
|
||||
version = "26.6.2"
|
||||
|
||||
[workspace.metadata.crane]
|
||||
name = "conduwuit"
|
||||
@@ -141,12 +141,6 @@ features = [
|
||||
version = "0.23.25"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.aws-lc-sys]
|
||||
version = "0.41.0"
|
||||
|
||||
[workspace.dependencies.aws-lc-rs]
|
||||
version = "1.17.0"
|
||||
|
||||
[workspace.dependencies.reqwest]
|
||||
version = "0.13.2"
|
||||
default-features = false
|
||||
@@ -350,7 +344,7 @@ version = "1.1.1"
|
||||
[workspace.dependencies.ruma]
|
||||
# version = "0.14.1"
|
||||
git = "https://github.com/ruma/ruma.git"
|
||||
rev = "fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
rev = "9b6a2e7323649af926e4b5363d87239ee4247f4a"
|
||||
features = [
|
||||
"appservice-api-c",
|
||||
"client-api",
|
||||
@@ -502,7 +496,7 @@ version = "0.4.9"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.termimad]
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.checked_ops]
|
||||
@@ -570,7 +564,7 @@ features = ["std"]
|
||||
version = "0.3.0"
|
||||
|
||||
[workspace.dependencies.resolvematrix]
|
||||
version = "1.0.0"
|
||||
version = "1.2.0"
|
||||
|
||||
[workspace.dependencies.serde_urlencoded]
|
||||
version = "0.7.1"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Appservice device management as outlined in MSC4190 (part of Matrix 1.17) is now fully supported. Contributed by @ginger.
|
||||
@@ -1 +0,0 @@
|
||||
Improved invite and join reliability in clients using legacy sync. Contributed by @ginger
|
||||
@@ -0,0 +1 @@
|
||||
Appservices are now properly able to create devices for E2EE.
|
||||
@@ -1 +0,0 @@
|
||||
Removed the `/_conduwuit/` versions of the `local_user_count` and `version` routes. These routes are still accessible under the `/_continuwuity` prefix.
|
||||
@@ -1 +0,0 @@
|
||||
The invite recipient's membership event is now included in invite stripped state, which should fix flaky invite display in some clients. Contributed by @ginger
|
||||
@@ -1 +0,0 @@
|
||||
Switched from Continuwuity's fork of Ruma back to upstream Ruma. Contributed by @ginger.
|
||||
@@ -1 +0,0 @@
|
||||
Users may now be forbidden from deactivating their own accounts with the new `allow_deactivation` config option. Contributed by @ginger.
|
||||
@@ -1 +0,0 @@
|
||||
Added support for Matrix 1.16's `state_after` feature, allowing clients which understand it to sync room state changes more reliably. Contributed by @ginger.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed newly created rooms failing to sync properly in clients using legacy sync.
|
||||
@@ -1 +0,0 @@
|
||||
Added support for authenticating clients using the new OAuth 2.0 login API. Contributed by @ginger.
|
||||
@@ -1 +0,0 @@
|
||||
Added support for MSC4466, which allows clients to customize how changes to a user's global profile are propagated. Contributed by @ginger.
|
||||
@@ -1 +0,0 @@
|
||||
Devices which set their presence as "offline" will no longer be considered for presence updates. Contributed by @timedout.
|
||||
@@ -1 +0,0 @@
|
||||
Removed support for guest user registration, a little-used and deprecated approach to room previews.
|
||||
@@ -1 +0,0 @@
|
||||
The deprecated `well_known.rtc_focus_server_urls` config option has been removed. MatrixRTC foci should be configured using the `matrix_rtc.foci` config option.
|
||||
@@ -0,0 +1 @@
|
||||
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 @@
|
||||
The version of Debian that the Docker-based build process uses has been upgraded from Bookworm to Trixie, meaning that standalone binaries now have a minimum glibc of 2.41, and can no longer be used on distro versions from before 2025-01-30
|
||||
@@ -1 +0,0 @@
|
||||
Support for server-side blurhashing (part of MSC2448) has been removed.
|
||||
@@ -1 +0,0 @@
|
||||
Updated [MSC4284: Policy Servers](https://github.com/matrix-org/matrix-spec-proposals/pull/4284) implementation to support the newly stabilised proposal. Contributed by @nex.
|
||||
@@ -1 +0,0 @@
|
||||
Add performance tuning documentation. Contributed by @stratself.
|
||||
@@ -1 +0,0 @@
|
||||
Rewrite the resolver service to use [resolvematrix](https://forgejo.ellis.link/continuwuation/resolvematrix) for server resolution. Rewrite by @s1lv3r, crate by @Jade
|
||||
@@ -1 +0,0 @@
|
||||
Explain accessing Continuwuity's server console when deployed via Docker.
|
||||
@@ -1 +0,0 @@
|
||||
Added config option for default room ACLs. Contributed by @eve.
|
||||
@@ -1 +0,0 @@
|
||||
Removed support for LDAP.
|
||||
@@ -1 +0,0 @@
|
||||
Clarified in the config that `max_request_size` affects federated media as well.
|
||||
@@ -1 +0,0 @@
|
||||
Added support for fallback encryption keys.
|
||||
@@ -1 +0,0 @@
|
||||
Fixed a bug that caused the server to drop events during processing if several events for the same room were sent in a singular transaction. Contributed by @nex.
|
||||
@@ -1 +0,0 @@
|
||||
Add `!admin users reject-all-invites` to clean invite spam
|
||||
@@ -1 +0,0 @@
|
||||
fix `!admin query account-data account-data-get` not returning the content
|
||||
@@ -1,9 +0,0 @@
|
||||
Implemented event rejection, which should resolve and prevent future netsplits of the kinds observed
|
||||
within some Continuwuity rooms.
|
||||
Also resolved several bugs related to both soft-failing events, and event backfilling, which should
|
||||
improve state resolution stability.
|
||||
The `!admin debug get-pdu` command was updated to disambiguate event acceptance status, and
|
||||
`!admin debug show-auth-chain` was added to visually display event auth chains, which may assist
|
||||
developers in debugging strangely complex events.
|
||||
|
||||
Contributed by @nex.
|
||||
@@ -1 +0,0 @@
|
||||
Fixed an issue where Continuwuity would only advertise support for the unstable endpoint for Mutual Rooms (MSC2666), despite only supporting the stable endpoint. Contributed by @Henry-Hiles (QuadRadical)
|
||||
@@ -1 +0,0 @@
|
||||
Added example configuration using caddy-docker-proxy in the livekit setup section of the docs. Contributed by @Cease
|
||||
@@ -1 +0,0 @@
|
||||
Fixed admin commands being ignored when they had leading whitespace before admin commands. Contributed by @kitvonsnookerz.
|
||||
@@ -1 +0,0 @@
|
||||
Fixed several bugs in the `POST /_matrix/client/v3/rooms/{roomId}/upgrade` endpoint. Contributed by @nex.
|
||||
@@ -1 +0,0 @@
|
||||
Added full support for [MSC4168: Update `m.space.*` state on room upgrade](https://github.com/matrix-org/matrix-spec-proposals/pull/4168). Contributed by @nex.
|
||||
@@ -1,2 +0,0 @@
|
||||
Improved the performance and reliability of fetching missing events, improving network partition recovery. Contributed
|
||||
by @nex.
|
||||
@@ -1 +0,0 @@
|
||||
Remove support for MSC4373, as the MSC is now closed. Contributed by @vel.
|
||||
@@ -1 +0,0 @@
|
||||
Added static builds using Nix, allowing for Continuwuity on musl. During this, we also introduced a `max-perf-haswell` package, separating it from `max-perf`, so you may want to swap to this if you are on NixOS. Contributed by @Henry-Hiles (QuadRadical).
|
||||
@@ -1 +0,0 @@
|
||||
Added support for MSC4380 invite blocking, which has become part of the Matrix specification in v1.18. Contributed by @nex.
|
||||
@@ -1 +0,0 @@
|
||||
Added `!admin debug get-state-at` command
|
||||
@@ -1 +0,0 @@
|
||||
Updated deployment docs to account for new RPM package availability across more distros. Contributed by @julian45.
|
||||
@@ -1 +0,0 @@
|
||||
Move resolver service into client service, and remove deprecated and unused code. Contributed by @s1lv3r.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed the deeplink redirect for deleting devices. Contributed by @koen
|
||||
@@ -0,0 +1 @@
|
||||
Fix status code for oauth registration. Contributed by @n00byking
|
||||
@@ -0,0 +1 @@
|
||||
Exempt m.room.create from auth_events check. Contributed by @eleboucher
|
||||
@@ -1 +0,0 @@
|
||||
Added support for linking an external identity provider with OIDC. Contributed by @ginger.
|
||||
@@ -1 +0,0 @@
|
||||
Adjusted legacy sync logic to no longer use the `roomsynctoken_shortstatehash` database column. Once this change has been confirmed to be stable and reliable, a future update will remove it entirely, significantly decreasing database sizes. Contributed by @ginger.
|
||||
@@ -290,6 +290,27 @@
|
||||
#
|
||||
#ip_lookup_strategy = 5
|
||||
|
||||
# The source to use for discovering the real connecting client IP.
|
||||
#
|
||||
# Takes any of the following options:
|
||||
#
|
||||
# "cf_connecting_ip" - `Cf-Connecting-Ip` header
|
||||
# "cloudfront_viewer_address" - `CloudFront-Viewer-Address` header
|
||||
# "fly_client_ip" - `Fly-Client-IP` header
|
||||
# "x_forwarded_for" - rightmost value of the `X-Forwarded-For` header
|
||||
# "true_client_ip" - `True-Client-Ip` header
|
||||
# "x_envoy_external_address" - `X-Envoy-External-Address` header
|
||||
# "x_real_ip" - `X-Real-Ip` header
|
||||
#
|
||||
# Only set this if you are certain only your reverse proxy
|
||||
# will send the expected header. There is no "is the connecting IP allowed
|
||||
# to set this header" check; if the header selected is present, it is
|
||||
# used.
|
||||
#
|
||||
# Defaults to the IP address actually making the connection.
|
||||
#
|
||||
#request_ip_source = false
|
||||
|
||||
# Max request size for file uploads in bytes. Defaults to 20MB.
|
||||
# Also limits incoming federated media.
|
||||
#
|
||||
|
||||
+3
-1
@@ -171,6 +171,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/usr/local/cargo/git/db \
|
||||
--mount=type=cache,target=/app/target,id=continuwuity-cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-${RUST_PROFILE} \
|
||||
bash <<'EOF'
|
||||
set -euo pipefail
|
||||
set -o allexport
|
||||
set -o xtrace
|
||||
. /etc/environment
|
||||
@@ -191,7 +192,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
jq -r ".target_directory"))
|
||||
mkdir /out/sbin
|
||||
PACKAGE=conduwuit
|
||||
xx-cargo build --locked --profile ${RUST_PROFILE} \
|
||||
CARGO_LOG=debug xx-cargo build --locked --profile ${RUST_PROFILE} \
|
||||
--no-default-features --features ${CARGO_FEATURES} \
|
||||
-p $PACKAGE;
|
||||
BINARIES=($(cargo metadata --no-deps --format-version 1 | \
|
||||
@@ -207,6 +208,7 @@ EOF
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/usr/local/cargo/git/db \
|
||||
bash <<'EOF'
|
||||
set -euo pipefail
|
||||
set -o xtrace
|
||||
mkdir /out/sbom
|
||||
typeset -A PACKAGES
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"id": 16,
|
||||
"mention_room": true,
|
||||
"date": "2026-06-20",
|
||||
"message": "[v0.5.10](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.10) has been released. It is a security release, so we suggest you update as soon as possible. Don't forget to also join [our announcements room](https://matrix.to/#/!jIdNjSM5X-V5JVx2h2kAhUZIIQ08GyzPL55NFZAH1vM/%24K1ISNKIqfNiZzsNVCaTt2E7ZtNeP6Dsy6sbz9l3rO0A?via=ellis.link&via=gingershaped.computer&via=matrix.org)."
|
||||
"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."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -27,3 +27,7 @@ ## `!admin appservices show-appservice-config`
|
||||
## `!admin appservices list-registered`
|
||||
|
||||
List all the currently registered appservices
|
||||
|
||||
## `!admin appservices ensure-puppets-active`
|
||||
|
||||
Ensure no appservice puppets are marked as deactivated. This is a debug command to fix issues caused by a faulty database migration in Continuwuity 26.6.0
|
||||
|
||||
@@ -146,6 +146,10 @@ ## `!admin debug send-test-email`
|
||||
|
||||
Send a test email to the invoking admin's email address
|
||||
|
||||
## `!admin debug rooms-by-extremity-count`
|
||||
|
||||
Lists room IDs by forward extremity count in descending order
|
||||
|
||||
## `!admin debug tester`
|
||||
|
||||
Developer test stubs
|
||||
|
||||
@@ -104,13 +104,9 @@ ## `!admin query resolver`
|
||||
|
||||
resolver service
|
||||
|
||||
### `!admin query resolver destinations-cache`
|
||||
### `!admin query resolver cache`
|
||||
|
||||
Query the destinations cache
|
||||
|
||||
### `!admin query resolver overrides-cache`
|
||||
|
||||
Query the overrides cache
|
||||
Query the destinations or overrides cache, depending on the value of the `overrides` flag (default false)
|
||||
|
||||
### `!admin query resolver flush-cache`
|
||||
|
||||
|
||||
Generated
+21
-21
@@ -3,11 +3,11 @@
|
||||
"advisory-db": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1781566179,
|
||||
"narHash": "sha256-Tqv8I586fYzWpEW/Smq/JqESFa3DVVzVWsnAMtvhy/I=",
|
||||
"lastModified": 1783840254,
|
||||
"narHash": "sha256-XjyvZk0f3YiinVHmkGOotmLBAzvK+LwEJYj2QqJ5pn8=",
|
||||
"owner": "rustsec",
|
||||
"repo": "advisory-db",
|
||||
"rev": "74e084413d979d52d2f93b1d93b1ab7b9ee648f5",
|
||||
"rev": "6e3286f4efa8c142fb33e5ea4342c8db6693cf34",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -18,11 +18,11 @@
|
||||
},
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1780532242,
|
||||
"narHash": "sha256-D+BsdpxmtUwtqGoY0IXPhHgTlmqgcZKCEo1oMyn7ep0=",
|
||||
"lastModified": 1783203018,
|
||||
"narHash": "sha256-G6R9IT/xwFuu+CYBWDUAok6AdC4ERC4ZfPPFtEpxnZE=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "59a82a1222dd3b2080b5cc52a1a2e8d5f1b77f37",
|
||||
"rev": "80db5bdc391be8a1794f6d8a2d56e3a84ebcede2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -39,11 +39,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781527054,
|
||||
"narHash": "sha256-1fX9ev2Fh5QoKQ41G9dYutjo5j/jywu6tZse5Eb1Ck4=",
|
||||
"lastModified": 1783844668,
|
||||
"narHash": "sha256-3MOpw4y3reoErRLvFBDz0t9aG6FWXUj7leKvUns5eQA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "8c2e51dffefc040a21975da7abf6f252c8c9b783",
|
||||
"rev": "4e49ef62eedaa5c149d62b63b3f53844e1cc45d7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -74,11 +74,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778716662,
|
||||
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
||||
"lastModified": 1782949081,
|
||||
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
||||
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -89,11 +89,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1781074563,
|
||||
"narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=",
|
||||
"lastModified": 1783776592,
|
||||
"narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9ae611a455b90cf061d8f332b977e387bda8e1ca",
|
||||
"rev": "e7a3ca8092b61ff85b6a45bf863ea2b2d6a661b3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -105,11 +105,11 @@
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1777168982,
|
||||
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
|
||||
"lastModified": 1782614948,
|
||||
"narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
|
||||
"rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -132,11 +132,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1781453968,
|
||||
"narHash": "sha256-+V3nK4pCngbmgyVGXY6Kkrlevp4ocPkJJLf2aqwkDNA=",
|
||||
"lastModified": 1783779020,
|
||||
"narHash": "sha256-vpm418WZa9l1KUl6HRs+Ga+SIfE+D2/sV4olwTlilYc=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "cc272809a173c2c11d0e479d639c811c1eacf049",
|
||||
"rev": "5be5e89cf0145d73a85c805726821b4adfc3af48",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -43,6 +43,8 @@ let
|
||||
env = {
|
||||
CARGO_PROFILE = profile;
|
||||
RUSTFLAGS = rustflags;
|
||||
GIT_COMMIT_HASH = self.rev or self.dirtyRev or "";
|
||||
GIT_COMMIT_HASH_SHORT = self.shortRev or self.dirtyShortRev or "";
|
||||
}
|
||||
// (lib.optionalAttrs (rocksdb != null) {
|
||||
ROCKSDB_INCLUDE_DIR = "${rocksdb}/include";
|
||||
|
||||
@@ -36,14 +36,16 @@
|
||||
self'.packages.stable-toolchain
|
||||
);
|
||||
|
||||
# extra features via `cargoExtraArgs`
|
||||
cargoExtraArgs = "-F http3";
|
||||
|
||||
default = pkgs.callPackage ./continuwuity.nix {
|
||||
inherit self craneLib;
|
||||
|
||||
liburing = (if isStatic then pkgs.pkgsStatic else pkgs).liburing;
|
||||
rocksdb = if isStatic then null else self'.packages.rocksdb;
|
||||
|
||||
# extra features via `cargoExtraArgs`
|
||||
cargoExtraArgs = "-F http3";
|
||||
inherit cargoExtraArgs;
|
||||
# extra RUSTFLAGS via `rustflags`
|
||||
# the stuff below is required for http3
|
||||
rustflags = "--cfg reqwest_unstable";
|
||||
@@ -55,6 +57,7 @@
|
||||
max-perf = default.override {
|
||||
# compiles slower but with more thorough optimizations
|
||||
profile = "release-max-perf";
|
||||
cargoExtraArgs = "${cargoExtraArgs} -F release_max_log_level";
|
||||
};
|
||||
|
||||
max-perf-haswell = max-perf.override {
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
target:
|
||||
target.fromToolchainName {
|
||||
name = (lib.importTOML "${inputs.self}/rust-toolchain.toml").toolchain.channel;
|
||||
sha256 = "sha256-h+t2xTBz5yt2YIO+1VMIIGlCU7gyp2LYOFvaV1nwOXU=";
|
||||
sha256 = "sha256-OATSZm98Es5kIFuqaba+UvkQtFsVgJEBMmS+t6od5/U=";
|
||||
};
|
||||
in
|
||||
{
|
||||
|
||||
Generated
+416
-55
@@ -12,7 +12,7 @@
|
||||
"@rspress/core": "^2.0.0",
|
||||
"@rspress/plugin-client-redirects": "^2.0.0",
|
||||
"@rspress/plugin-sitemap": "^2.0.0",
|
||||
"typescript": "^6.0.0"
|
||||
"typescript": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
@@ -510,14 +510,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/core": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.2.0.tgz",
|
||||
"integrity": "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.3.1.tgz",
|
||||
"integrity": "sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/primitive": "4.2.0",
|
||||
"@shikijs/types": "4.2.0",
|
||||
"@shikijs/primitive": "4.3.1",
|
||||
"@shikijs/types": "4.3.1",
|
||||
"@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.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.2.0.tgz",
|
||||
"integrity": "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.3.1.tgz",
|
||||
"integrity": "sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.2.0",
|
||||
"@shikijs/types": "4.3.1",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"oniguruma-to-es": "^4.3.6"
|
||||
},
|
||||
@@ -542,13 +542,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/engine-oniguruma": {
|
||||
"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==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.3.1.tgz",
|
||||
"integrity": "sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.2.0",
|
||||
"@shikijs/types": "4.3.1",
|
||||
"@shikijs/vscode-textmate": "^10.0.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -556,26 +556,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/langs": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.2.0.tgz",
|
||||
"integrity": "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.2.0"
|
||||
"@shikijs/types": "4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/primitive": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.2.0.tgz",
|
||||
"integrity": "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.3.1.tgz",
|
||||
"integrity": "sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.2.0",
|
||||
"@shikijs/types": "4.3.1",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4"
|
||||
},
|
||||
@@ -584,16 +584,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/rehype": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.2.0.tgz",
|
||||
"integrity": "sha512-ST3EWye/dwF1gWskczJNBnwFtDzEQ9ceytXZtyc/GfwR5V0qJrkoSGZO55O3SAKDDsXkTDcsfwd9pVe7ROlAHg==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.3.1.tgz",
|
||||
"integrity": "sha512-oshrlfUF3VPUJfnp5K1lLwsS/SRBKrIxONpdWebSKZXdBE3UsZnxgqpvRUA8UsofS7vmjFOCAHIT71ECbmOxTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.2.0",
|
||||
"@shikijs/types": "4.3.1",
|
||||
"@types/hast": "^3.0.4",
|
||||
"hast-util-to-string": "^3.0.1",
|
||||
"shiki": "4.2.0",
|
||||
"shiki": "4.3.1",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.1.0"
|
||||
},
|
||||
@@ -602,22 +602,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/themes": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.2.0.tgz",
|
||||
"integrity": "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.3.1.tgz",
|
||||
"integrity": "sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.2.0"
|
||||
"@shikijs/types": "4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/types": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz",
|
||||
"integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.3.1.tgz",
|
||||
"integrity": "sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -684,9 +684,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
||||
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz",
|
||||
"integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -735,10 +735,350 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript/typescript-aix-ppc64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
|
||||
"integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-darwin-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-darwin-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-freebsd-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-freebsd-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-arm": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
|
||||
"integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-loong64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
|
||||
"integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-mips64el": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
|
||||
"integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-ppc64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
|
||||
"integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-riscv64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
|
||||
"integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-s390x": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
|
||||
"integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-netbsd-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-netbsd-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-openbsd-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-openbsd-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-sunos-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-win32-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-win32-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz",
|
||||
"integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -3249,18 +3589,18 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shiki": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.2.0.tgz",
|
||||
"integrity": "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.3.1.tgz",
|
||||
"integrity": "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@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/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/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4"
|
||||
},
|
||||
@@ -3361,17 +3701,38 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
|
||||
"integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
"tsc": "bin/tsc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
"node": ">=16.20.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@typescript/typescript-aix-ppc64": "7.0.2",
|
||||
"@typescript/typescript-darwin-arm64": "7.0.2",
|
||||
"@typescript/typescript-darwin-x64": "7.0.2",
|
||||
"@typescript/typescript-freebsd-arm64": "7.0.2",
|
||||
"@typescript/typescript-freebsd-x64": "7.0.2",
|
||||
"@typescript/typescript-linux-arm": "7.0.2",
|
||||
"@typescript/typescript-linux-arm64": "7.0.2",
|
||||
"@typescript/typescript-linux-loong64": "7.0.2",
|
||||
"@typescript/typescript-linux-mips64el": "7.0.2",
|
||||
"@typescript/typescript-linux-ppc64": "7.0.2",
|
||||
"@typescript/typescript-linux-riscv64": "7.0.2",
|
||||
"@typescript/typescript-linux-s390x": "7.0.2",
|
||||
"@typescript/typescript-linux-x64": "7.0.2",
|
||||
"@typescript/typescript-netbsd-arm64": "7.0.2",
|
||||
"@typescript/typescript-netbsd-x64": "7.0.2",
|
||||
"@typescript/typescript-openbsd-arm64": "7.0.2",
|
||||
"@typescript/typescript-openbsd-x64": "7.0.2",
|
||||
"@typescript/typescript-sunos-x64": "7.0.2",
|
||||
"@typescript/typescript-win32-arm64": "7.0.2",
|
||||
"@typescript/typescript-win32-x64": "7.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/unhead": {
|
||||
|
||||
+1
-1
@@ -25,6 +25,6 @@
|
||||
"@rspress/core": "^2.0.0",
|
||||
"@rspress/plugin-client-redirects": "^2.0.0",
|
||||
"@rspress/plugin-sitemap": "^2.0.0",
|
||||
"typescript": "^6.0.0"
|
||||
"typescript": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
[toolchain]
|
||||
profile = "minimal"
|
||||
channel = "1.96.1"
|
||||
channel = "1.97.0"
|
||||
components = [
|
||||
# For rust-analyzer
|
||||
"rust-src",
|
||||
|
||||
@@ -73,4 +73,29 @@ pub(super) async fn list_registered(&self) -> Result {
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) async fn ensure_puppets_active(&self) -> Result {
|
||||
self.services
|
||||
.users
|
||||
.stream_local_users()
|
||||
.filter_map(async |user_id| {
|
||||
if self.services.appservice.is_user_id(&user_id).await {
|
||||
Some(user_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.for_each(async |user_id| {
|
||||
self.services
|
||||
.users
|
||||
.convert_to_shadow_account(&user_id)
|
||||
.await
|
||||
.expect("should be able to convert to shadow");
|
||||
})
|
||||
.await;
|
||||
|
||||
write!(self, "All appservice puppets have been marked as active.").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,4 +37,10 @@ pub enum AppserviceCommand {
|
||||
/// List all the currently registered appservices
|
||||
#[clap(alias("list"))]
|
||||
ListRegistered,
|
||||
|
||||
/// Ensure no appservice puppets are marked as deactivated.
|
||||
/// This is a debug command to fix issues caused by a faulty database
|
||||
/// migration in Continuwuity 26.6.0.
|
||||
#[clap(hide = true)]
|
||||
EnsurePuppetsActive,
|
||||
}
|
||||
|
||||
@@ -486,7 +486,7 @@ pub(super) async fn get_remote_pdu(
|
||||
.services
|
||||
.rooms
|
||||
.event_handler
|
||||
.parse_incoming_pdu(&response.pdu)
|
||||
.parse_incoming_pdu(&response.pdu, None)
|
||||
.boxed()
|
||||
.await;
|
||||
|
||||
@@ -832,7 +832,7 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
.services
|
||||
.rooms
|
||||
.event_handler
|
||||
.parse_incoming_pdu(&pdu)
|
||||
.parse_incoming_pdu(&pdu, Some(&room_version_rules))
|
||||
.await
|
||||
{
|
||||
| Ok(t) => t,
|
||||
@@ -1014,10 +1014,10 @@ pub(super) async fn resolve_true_destination(
|
||||
let resolver: &MatrixResolver = if no_cache {
|
||||
&MatrixResolverBuilder::new()
|
||||
.dangerous_tls_accept_invalid_certs(self.services.server.config.allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure)
|
||||
.http_client(self.services.client.default.clone())
|
||||
.http_client(self.services.client.dns.clone())
|
||||
.build()?
|
||||
} else {
|
||||
&self.services.client.resolver
|
||||
&self.services.client.matrix_resolver
|
||||
};
|
||||
|
||||
let actual = resolver.resolve_server(server_name.as_str()).await?;
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(super) async fn fetch_support_well_known(&self, server_name: OwnedServerName
|
||||
let response = self
|
||||
.services
|
||||
.client
|
||||
.default
|
||||
.external_resource
|
||||
.get(format!("https://{server_name}/.well-known/matrix/support"))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@@ -47,7 +47,7 @@ async fn cache(
|
||||
writeln!(self, "| Server Name | Destination | SNI | Override | Step | Expires |").await?;
|
||||
writeln!(self, "| ----------- | ----------- | --- | -------- | ---- | ------- |").await?;
|
||||
|
||||
let entries = self.services.client.resolver.get_all_cache_entries();
|
||||
let entries = self.services.client.matrix_resolver.get_all_cache_entries();
|
||||
|
||||
for (host, entry) in entries {
|
||||
if let Some(ors) = overrides
|
||||
@@ -81,13 +81,13 @@ async fn cache(
|
||||
|
||||
async fn flush_cache(&self, name: Option<OwnedServerName>, all: bool) -> Result {
|
||||
if all {
|
||||
self.services.client.resolver.clear_cache();
|
||||
self.services.client.dns.clear_cache();
|
||||
self.services.client.matrix_resolver.clear_cache();
|
||||
self.services.client.dns_resolver.clear_cache();
|
||||
writeln!(self, "Resolver and DNS caches cleared!").await
|
||||
} else if let Some(name) = name {
|
||||
self.services
|
||||
.client
|
||||
.resolver
|
||||
.matrix_resolver
|
||||
.remove_cache_entry(name.as_str());
|
||||
self.write_str(&format!("Cleared {name} from resolver caches!"))
|
||||
.await
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
};
|
||||
use service::{mailer::messages, uiaa::UiaaInitiator, users::HashedPassword};
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
use super::DEVICE_ID_LENGTH;
|
||||
use crate::{Ruma, router::ClientIdentity};
|
||||
|
||||
pub(crate) mod register;
|
||||
|
||||
@@ -20,9 +20,12 @@
|
||||
assign,
|
||||
};
|
||||
use serde_json::value::RawValue;
|
||||
use service::{mailer::messages, users::HashedPassword};
|
||||
use service::{
|
||||
mailer::messages,
|
||||
users::{DeviceToken, HashedPassword},
|
||||
};
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
use super::DEVICE_ID_LENGTH;
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `POST /_matrix/client/v3/register`
|
||||
@@ -119,7 +122,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 = utils::random_string(TOKEN_LENGTH);
|
||||
let new_token = DeviceToken::new_random();
|
||||
|
||||
// Create device for this account
|
||||
services
|
||||
@@ -127,8 +130,7 @@ pub(crate) async fn register_route(
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
&new_token,
|
||||
None,
|
||||
Some(new_token.clone()),
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
@@ -142,7 +144,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,
|
||||
access_token: token.map(DeviceToken::into_token),
|
||||
device_id: device,
|
||||
refresh_token: None,
|
||||
expires_in: None,
|
||||
|
||||
@@ -57,13 +57,11 @@ pub(crate) async fn request_3pid_management_token_via_email_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<request_3pid_management_token_via_email::v3::Request>,
|
||||
) -> Result<request_3pid_management_token_via_email::v3::Response> {
|
||||
// Authentication for this endpoint is technically optional,
|
||||
// but we require the user to be logged in
|
||||
let sender_user = body
|
||||
.identity
|
||||
.as_ref()
|
||||
.map(ClientIdentity::expect_sender_user)
|
||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))??;
|
||||
.transpose()?;
|
||||
|
||||
if !services.threepid.email_requirement().may_change() {
|
||||
return Err!(Request(Forbidden("You may not change your email address.")));
|
||||
@@ -88,7 +86,7 @@ pub(crate) async fn request_3pid_management_token_via_email_route(
|
||||
Mailbox::new(None, email),
|
||||
|verification_link| messages::ChangeEmail {
|
||||
server_name: services.config.server_name.as_str(),
|
||||
user_id: Some(sender_user),
|
||||
user_id: sender_user,
|
||||
verification_link,
|
||||
},
|
||||
&body.client_secret,
|
||||
|
||||
@@ -89,14 +89,7 @@ pub(crate) async fn update_device_route(
|
||||
|
||||
services
|
||||
.users
|
||||
.create_device(
|
||||
sender_user,
|
||||
&device_id,
|
||||
&appservice.registration.as_token,
|
||||
None,
|
||||
None,
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.create_device(sender_user, &device_id, None, None, Some(client.to_string()))
|
||||
.await?;
|
||||
|
||||
return Ok(update_device::v3::Response::new());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use conduwuit::{
|
||||
Err, Result, debug_error, err, info,
|
||||
matrix::{event::gen_event_id_canonical_json, pdu::PartialPdu},
|
||||
warn,
|
||||
trace, warn,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use ruma::{
|
||||
@@ -166,7 +166,7 @@ pub(crate) async fn invite_helper(
|
||||
let invite_room_state = services
|
||||
.rooms
|
||||
.state
|
||||
.summary_stripped(&pdu, room_id, recipient_user)
|
||||
.summary_stripped(&pdu, room_id, recipient_user, true)
|
||||
.await;
|
||||
|
||||
drop(state_lock);
|
||||
@@ -193,6 +193,7 @@ pub(crate) async fn invite_helper(
|
||||
.await
|
||||
.ok();
|
||||
|
||||
trace!(?request, "Sending invite");
|
||||
let response = services
|
||||
.sending
|
||||
.send_federation_request(recipient_user.server_name(), request)
|
||||
@@ -221,7 +222,7 @@ pub(crate) async fn invite_helper(
|
||||
let pdu_id = services
|
||||
.rooms
|
||||
.event_handler
|
||||
.handle_incoming_pdu(recipient_user.server_name(), room_id, &event_id, value, true)
|
||||
.handle_incoming_pdu(recipient_user.server_name(), room_id, &event_id, value, false)
|
||||
.boxed()
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
|
||||
@@ -24,5 +24,5 @@ pub(crate) async fn register_client_route(
|
||||
.await
|
||||
.map_err(|err| (StatusCode::BAD_REQUEST, Json(err)).into_response())?;
|
||||
|
||||
Ok(Json(RegisteredClient { client_id, metadata }).into_response())
|
||||
Ok((StatusCode::CREATED, Json(RegisteredClient { client_id, metadata })).into_response())
|
||||
}
|
||||
|
||||
@@ -29,8 +29,9 @@
|
||||
},
|
||||
assign,
|
||||
};
|
||||
use service::users::DeviceToken;
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
use super::DEVICE_ID_LENGTH;
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `GET /_matrix/client/v3/login`
|
||||
@@ -196,8 +197,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 (ensuring no collisions)
|
||||
let token = services.users.generate_unique_token().await;
|
||||
// Generate a new token for the device
|
||||
let token = DeviceToken::new_random();
|
||||
|
||||
// Determine if device_id was provided and exists in the db for this user
|
||||
let device_exists = if body.device_id.is_some() {
|
||||
@@ -213,7 +214,7 @@ pub(crate) async fn login_route(
|
||||
if device_exists {
|
||||
services
|
||||
.users
|
||||
.set_token(&user_id, &device_id, &token, None)
|
||||
.set_token(&user_id, &device_id, token.clone())
|
||||
.await?;
|
||||
} else {
|
||||
services
|
||||
@@ -221,8 +222,7 @@ pub(crate) async fn login_route(
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
&token,
|
||||
None,
|
||||
Some(token.clone()),
|
||||
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, device_id), {
|
||||
Ok(assign!(login::v3::Response::new(user_id, token.into_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 = utils::random_string(TOKEN_LENGTH);
|
||||
let login_token = DeviceToken::new_random().into_token();
|
||||
let expires_in = services.users.create_login_token(sender_user, &login_token);
|
||||
|
||||
Ok(get_login_token::v1::Response::new(
|
||||
|
||||
@@ -381,16 +381,21 @@ async fn fetch_shortstatehashes(
|
||||
| Some((_, pdu_after_last_sync_end)) => {
|
||||
trace!(?pdu_after_last_sync_end.event_id, "pdu at last sync end");
|
||||
|
||||
Some(
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.pdu_shortstatehash(&pdu_after_last_sync_end.event_id)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
err!("Last sync end PDU has no shortstatehash: {err}")
|
||||
}),
|
||||
)
|
||||
if pdu_after_last_sync_end.kind == RoomCreate {
|
||||
// Room create events have no previous state
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.pdu_shortstatehash(&pdu_after_last_sync_end.event_id)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
err!("Last sync end PDU has no shortstatehash: {err}")
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
| None => {
|
||||
// No events have been sent since the last sync, or we just joined this room
|
||||
|
||||
@@ -15,10 +15,9 @@
|
||||
#[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
|
||||
|
||||
+22
-18
@@ -219,25 +219,29 @@ 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.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
|
||||
.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."
|
||||
)));
|
||||
}
|
||||
|
||||
Some(device_id.to_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some(device_id.to_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(ClientIdentity::Appservice {
|
||||
sender_user,
|
||||
|
||||
+294
-110
@@ -1,23 +1,29 @@
|
||||
use std::collections::{HashMap, hash_map::Entry};
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::ClientIp;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use conduwuit::{
|
||||
Err, Error, PduEvent, Result, err, error,
|
||||
matrix::{Event, event::gen_event_id},
|
||||
utils::{self, hash::sha256},
|
||||
Err, Error, EventTypeExt, PduEvent, Result, debug, err, error,
|
||||
matrix::{Event, StateKey},
|
||||
result::FlatOk,
|
||||
state_res, trace,
|
||||
utils::hash::sha256,
|
||||
warn,
|
||||
};
|
||||
use ruma::{
|
||||
CanonicalJsonValue, UserId,
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedUserId, ServerName,
|
||||
UserId,
|
||||
api::{
|
||||
error::{ErrorKind, IncompatibleRoomVersionErrorData},
|
||||
federation::membership::{RawStrippedState, create_invite},
|
||||
},
|
||||
events::room::member::{MembershipState, RoomMemberEventContent},
|
||||
serde::JsonObject,
|
||||
events::{StateEventType, room::member::MembershipState},
|
||||
room_version_rules::RoomVersionRules,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::Ruma;
|
||||
use crate::{Ruma, server::utils::validate_any_membership_event};
|
||||
|
||||
/// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}`
|
||||
///
|
||||
@@ -28,30 +34,16 @@ pub(crate) async fn create_invite_route(
|
||||
ClientIp(client): ClientIp,
|
||||
body: Ruma<create_invite::v2::Request>,
|
||||
) -> Result<create_invite::v2::Response> {
|
||||
// ACL check origin
|
||||
services
|
||||
.rooms
|
||||
.event_handler
|
||||
.acl_check(&body.identity, &body.room_id)
|
||||
.await?;
|
||||
|
||||
if !services.server.supported_room_version(&body.room_version) {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData::new(
|
||||
body.room_version.clone(),
|
||||
)),
|
||||
"Server does not support this room version.",
|
||||
"This server does not support that room version",
|
||||
));
|
||||
}
|
||||
|
||||
let room_version_rules = body.room_version.rules().unwrap();
|
||||
|
||||
if let Some(server) = body.room_id.server_name() {
|
||||
if services.moderation.is_remote_server_forbidden(server) {
|
||||
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
||||
}
|
||||
}
|
||||
|
||||
if services
|
||||
.moderation
|
||||
.is_remote_server_forbidden(&body.identity)
|
||||
@@ -61,90 +53,61 @@ pub(crate) async fn create_invite_route(
|
||||
body.identity, body.room_id
|
||||
);
|
||||
|
||||
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
||||
return Err!(Request(Forbidden("Federation denied with {}", body.identity)));
|
||||
}
|
||||
|
||||
let mut signed_event = utils::to_canonical_object(&body.event)
|
||||
.map_err(|_| err!(Request(InvalidParam("Invite event is invalid."))))?;
|
||||
|
||||
// Ensure this is a membership event
|
||||
if signed_event
|
||||
.get("type")
|
||||
.expect("event must have a type")
|
||||
.as_str()
|
||||
.expect("type must be a string")
|
||||
!= "m.room.member"
|
||||
{
|
||||
return Err!(Request(BadJson(
|
||||
"Not allowed to send non-membership event to invite endpoint."
|
||||
)));
|
||||
}
|
||||
|
||||
let content: RoomMemberEventContent = serde_json::from_value(
|
||||
signed_event
|
||||
.get("content")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing content property"))))?
|
||||
.clone()
|
||||
.into(),
|
||||
// First, validate the invite room state, so we can compare with the create
|
||||
// event.
|
||||
debug!(
|
||||
event_id=%body.event_id,
|
||||
room_id=%body.room_id,
|
||||
room_version=?body.room_version,
|
||||
via=?body.via,
|
||||
"Validating invite room state for invite request"
|
||||
);
|
||||
let (create_event_id, state) = validate_invite_state(
|
||||
&services,
|
||||
&body.invite_room_state,
|
||||
&room_version_rules,
|
||||
body.room_id.clone(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("Event content is empty or invalid: {e}")))))?;
|
||||
.await?;
|
||||
let create_event_json = state
|
||||
.get(&StateEventType::RoomCreate.with_state_key(""))
|
||||
.expect("must have create event in invite state by this point");
|
||||
|
||||
// Ensure this is an invite membership event
|
||||
if content.membership != MembershipState::Invite {
|
||||
return Err!(Request(BadJson(
|
||||
"Not allowed to send a non-invite membership event to invite endpoint."
|
||||
)));
|
||||
}
|
||||
|
||||
// Ensure the sending user isn't a lying bozo
|
||||
let sender_user = signed_event
|
||||
// We can now perform the banned remote server check with the create event.
|
||||
// N.B. this checks the sender field, which is technically incorrect for rooms
|
||||
// v10 and below. This usually isn't the case though so sue me
|
||||
let creator = create_event_json
|
||||
.get("sender")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(UserId::parse)
|
||||
.and_then(Result::ok)
|
||||
.ok_or_else(|| err!(Request(InvalidParam("Invalid sender property"))))?;
|
||||
|
||||
if sender_user.server_name() != body.identity {
|
||||
return Err!(Request(Forbidden("Sender's server does not match the origin server.",)));
|
||||
}
|
||||
|
||||
// Ensure the target user belongs to this server
|
||||
let recipient_user = signed_event
|
||||
.get("state_key")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(UserId::parse)
|
||||
.and_then(Result::ok)
|
||||
.ok_or_else(|| err!(Request(InvalidParam("Invalid state_key property"))))?;
|
||||
|
||||
if !services
|
||||
.globals
|
||||
.server_is_ours(recipient_user.server_name())
|
||||
.flat_ok()
|
||||
.expect("must have valid sender in create event");
|
||||
if services
|
||||
.moderation
|
||||
.is_remote_server_forbidden(creator.server_name())
|
||||
{
|
||||
return Err!(Request(InvalidParam("User does not belong to this homeserver.")));
|
||||
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
// Make sure we're not ACL'ed from their room.
|
||||
services
|
||||
.rooms
|
||||
.event_handler
|
||||
.acl_check(recipient_user.server_name(), &body.room_id)
|
||||
.await?;
|
||||
|
||||
services
|
||||
.server_keys
|
||||
.hash_and_sign_event(&mut signed_event, &room_version_rules)
|
||||
.map_err(|e| err!(Request(InvalidParam("Failed to sign event: {e}"))))?;
|
||||
|
||||
// Generate event id
|
||||
let event_id = gen_event_id(&signed_event, &room_version_rules)?;
|
||||
|
||||
// Add event_id back
|
||||
signed_event.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.to_string()));
|
||||
// And then we can validate the member event itself
|
||||
let (mut signed_event, sender_user, recipient_user) = validate_invite_membership_event(
|
||||
&services,
|
||||
&body.event,
|
||||
&room_version_rules,
|
||||
&body.identity,
|
||||
create_event_id.clone(),
|
||||
body.room_id.clone(),
|
||||
body.event_id.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if services.rooms.metadata.is_banned(&body.room_id).await
|
||||
&& !services.users.is_admin(&recipient_user).await
|
||||
{
|
||||
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
||||
return Err!(Request(Forbidden("That room is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
if services.config.block_non_admin_invites && !services.users.is_admin(&recipient_user).await
|
||||
@@ -161,30 +124,57 @@ pub(crate) async fn create_invite_route(
|
||||
return Err!(Request(Forbidden("Invite rejected by antispam service.")));
|
||||
}
|
||||
|
||||
// If we're already in the room, ensure that neither the origin nor ourselves
|
||||
// are ACL'd.
|
||||
let resident = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||
.await;
|
||||
if resident {
|
||||
services
|
||||
.rooms
|
||||
.event_handler
|
||||
.acl_check(&body.identity, &body.room_id)
|
||||
.await?;
|
||||
services
|
||||
.rooms
|
||||
.event_handler
|
||||
.acl_check(recipient_user.server_name(), &body.room_id)
|
||||
.await
|
||||
.map_err(|_| err!(Request(Forbidden("This server is ACL'd from that room"))))?;
|
||||
}
|
||||
|
||||
services
|
||||
.server_keys
|
||||
.hash_and_sign_event(&mut signed_event, &room_version_rules)
|
||||
.map_err(|e| err!(Request(InvalidParam("Failed to sign event: {e}"))))?;
|
||||
|
||||
// Add event_id back
|
||||
signed_event
|
||||
.insert("event_id".to_owned(), CanonicalJsonValue::String(body.event_id.to_string()));
|
||||
|
||||
let mut invite_state = body.invite_room_state.clone();
|
||||
|
||||
let mut event: JsonObject = serde_json::from_str(body.event.get())
|
||||
.map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?;
|
||||
|
||||
event.insert("event_id".to_owned(), "$placeholder".into());
|
||||
|
||||
let pdu: PduEvent = serde_json::from_value(event.into())
|
||||
.map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?;
|
||||
|
||||
invite_state.push(RawStrippedState::Pdu(
|
||||
serde_json::value::to_raw_value(&pdu).expect("PDU was just created, it must be valid"),
|
||||
));
|
||||
let pdu = PduEvent::from_id_val(&body.event_id, signed_event.clone())
|
||||
.expect("must be able to create PDU object");
|
||||
invite_state.push(RawStrippedState::Pdu(serde_json::value::to_raw_value(&signed_event)?));
|
||||
|
||||
// If we are active in the room, the remote server will notify us about the
|
||||
// join/invite through /send. If we are not in the room, we need to manually
|
||||
// record the invited state for client /sync through update_membership(), and
|
||||
// send the invite PDU to the relevant appservices.
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||
.await
|
||||
{
|
||||
if !resident {
|
||||
// We will start by recording the room's create event as an outlier.
|
||||
// This will allow us to recognise it later in case the sender revokes the
|
||||
// invite over federation later. We could store more state from the invite
|
||||
// request, but we will get that during send_join anyway.
|
||||
// This is safe to just add directly as an outlier as we already auth checked it
|
||||
// during validation.
|
||||
services
|
||||
.rooms
|
||||
.outlier
|
||||
.add_pdu_outlier(&create_event_id, create_event_json);
|
||||
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
@@ -240,3 +230,197 @@ pub(crate) async fn create_invite_route(
|
||||
.await,
|
||||
))
|
||||
}
|
||||
|
||||
/// Validates the *membership event* in the invite request, per the steps listed
|
||||
/// under the invite endpoint's [spec].
|
||||
///
|
||||
/// Returns the validated JSON body, sender user ID, and recipient user ID.
|
||||
///
|
||||
/// Since this function performs a PDU format check, the create event must be
|
||||
/// known ahead of time. This implies validating the invite state before the
|
||||
/// invite event itself.
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/v1.19/server-server-api/#put_matrixfederationv2inviteroomideventid
|
||||
async fn validate_invite_membership_event(
|
||||
services: &crate::State,
|
||||
body: &serde_json::value::RawValue,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
origin: &ServerName,
|
||||
create_event_id: OwnedEventId,
|
||||
room_id: OwnedRoomId,
|
||||
event_id: OwnedEventId,
|
||||
) -> Result<(CanonicalJsonObject, OwnedUserId, OwnedUserId)> {
|
||||
trace!(?body, "Invite membership event");
|
||||
let (pdu, target_membership, sender_user, recipient_user) = validate_any_membership_event(
|
||||
services,
|
||||
body,
|
||||
room_version_rules,
|
||||
create_event_id,
|
||||
room_id,
|
||||
event_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Ensure the sender belongs to the remote that is sending the invite
|
||||
if sender_user.server_name() != origin {
|
||||
return Err!(Request(Forbidden("Sender belongs to a different server")));
|
||||
}
|
||||
|
||||
// Ensure the target user belongs to this server
|
||||
if !services
|
||||
.globals
|
||||
.server_is_ours(recipient_user.server_name())
|
||||
{
|
||||
return Err!(Request(InvalidParam("Recipient does not belong to this homeserver")));
|
||||
}
|
||||
|
||||
if target_membership != MembershipState::Invite {
|
||||
return Err!(Request(BadJson("Invalid membership (expected `invite`)")));
|
||||
}
|
||||
|
||||
Ok((pdu, sender_user, recipient_user))
|
||||
}
|
||||
|
||||
/// Validates the *invite state* of an invite request, per the steps listed
|
||||
/// under the endpoint's [spec].
|
||||
///
|
||||
/// Returns the create event's event ID, and the partial state map.
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/v1.19/server-server-api/#put_matrixfederationv2inviteroomideventid
|
||||
async fn validate_invite_state(
|
||||
services: &crate::State,
|
||||
invite_state: &[RawStrippedState],
|
||||
room_version_rules: &RoomVersionRules,
|
||||
room_id: OwnedRoomId,
|
||||
) -> Result<(OwnedEventId, HashMap<(StateEventType, StateKey), CanonicalJsonObject>)> {
|
||||
trace!(?invite_state, "Raw invite state");
|
||||
let mut invite_state_map: HashMap<(StateEventType, StateKey), _> =
|
||||
HashMap::with_capacity(invite_state.len());
|
||||
let mut create_event_id: Option<OwnedEventId> = None;
|
||||
|
||||
for (idx, invite_state_event) in invite_state.iter().cloned().enumerate() {
|
||||
trace!(%idx, ?invite_state_event, "Invite state event");
|
||||
// Stripped state hasn't been sent over federation since v1.16.
|
||||
let RawStrippedState::Pdu(raw_pdu) = invite_state_event else {
|
||||
debug!(%idx, "Invite state event is not a PDU");
|
||||
return Err!(Request(InvalidParam(
|
||||
"PDU in invite state (index {idx}) violates the room event format"
|
||||
)));
|
||||
};
|
||||
let (state_event_room_id, state_event_id, state_event_json) = services
|
||||
.rooms
|
||||
.event_handler
|
||||
.parse_incoming_pdu(&raw_pdu, Some(room_version_rules))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam(debug_warn!("Invalid PDU in invite state: {e}"))))
|
||||
})?;
|
||||
|
||||
if state_event_room_id != room_id {
|
||||
return Err!(Request(InvalidParam(debug_warn!(
|
||||
%state_event_room_id,
|
||||
%room_id,
|
||||
"PDU in invite state ({state_event_id}) belongs to the wrong room"
|
||||
))));
|
||||
}
|
||||
|
||||
services
|
||||
.server_keys
|
||||
.verify_event(&state_event_json, room_version_rules)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam("Signature verification failed on invite event: {e}")))
|
||||
})?;
|
||||
|
||||
let Some(state_key) = state_event_json.get("state_key").and_then(|k| k.as_str()) else {
|
||||
return Err!(Request(InvalidParam(debug_info!(
|
||||
"PDU in invite state ({state_event_id}) is not a state event"
|
||||
))));
|
||||
};
|
||||
let Some(event_type) = state_event_json.get("type").and_then(|k| k.as_str()) else {
|
||||
return Err!(Request(InvalidParam(debug_warn!(
|
||||
"PDU in invite state ({state_event_id}) is not an event?"
|
||||
))));
|
||||
};
|
||||
|
||||
let key = StateEventType::from(event_type).with_state_key(state_key);
|
||||
match invite_state_map.entry(key) {
|
||||
| Entry::Occupied(entry) =>
|
||||
return Err!(Request(InvalidParam(
|
||||
"Duplicate state events in invite state for state key: {:?}",
|
||||
entry.key(),
|
||||
))),
|
||||
| Entry::Vacant(entry) => {
|
||||
if entry.key().0 == StateEventType::RoomCreate {
|
||||
// Ensure this is a legal create event.
|
||||
let pdu_event =
|
||||
PduEvent::from_id_val(&state_event_id, state_event_json.clone())
|
||||
.expect("must be able to create pdu event from event json");
|
||||
debug!("Validating discovered create event in invite room state");
|
||||
validate_invite_create_event(&pdu_event, room_version_rules).await?;
|
||||
create_event_id = Some(state_event_id);
|
||||
}
|
||||
entry.insert(state_event_json);
|
||||
},
|
||||
}
|
||||
}
|
||||
let Some(create_event_id) = create_event_id else {
|
||||
return Err!(Request(InvalidParam(debug_warn!(
|
||||
parsed_state=?invite_state_map,
|
||||
"Invite state does not contain the m.room.create event"
|
||||
))));
|
||||
};
|
||||
invite_state_map.iter().try_for_each(|(key, event_json)| {
|
||||
service::rooms::event_handler::Service::pdu_format_check_1(
|
||||
event_json,
|
||||
room_version_rules,
|
||||
&create_event_id,
|
||||
)
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam(
|
||||
"PDU in invite state for {key:?} violates the room event format: {e}"
|
||||
)))
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok((create_event_id, invite_state_map))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MFederate {
|
||||
#[serde(rename = "m.federate")]
|
||||
mfederate: Option<bool>,
|
||||
}
|
||||
|
||||
/// Validates that a create event is suitable for the invite, namely:
|
||||
///
|
||||
/// 1. It passes auth checks (aka is valid)
|
||||
/// 2. The room is federated (there's no point persisting unfederated rooms)
|
||||
async fn validate_invite_create_event(
|
||||
pdu: &PduEvent,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
) -> Result {
|
||||
if !state_res::auth_check(
|
||||
room_version_rules,
|
||||
pdu,
|
||||
None,
|
||||
|_, _| async {
|
||||
unreachable!("No state should be fetched when processing a lone create event");
|
||||
},
|
||||
pdu,
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return Err!(Request(InvalidParam("m.room.create event fails auth check")));
|
||||
}
|
||||
|
||||
let can_federate = pdu.get_content::<MFederate>()?.mfederate;
|
||||
if !can_federate.unwrap_or(true) {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Cannot receive invites to a room with m.federate=false"
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -75,14 +75,6 @@ pub(crate) async fn create_join_event_template_route(
|
||||
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
if let Some(server) = body.room_id.server_name() {
|
||||
if services.moderation.is_remote_server_forbidden(server) {
|
||||
return Err!(Request(Forbidden(warn!(
|
||||
"Room ID server name {server} is banned on this homeserver."
|
||||
))));
|
||||
}
|
||||
}
|
||||
|
||||
let room_version = services.rooms.state.get_room_version(&body.room_id).await?;
|
||||
let room_version_rules = room_version.rules().unwrap();
|
||||
if !body.ver.contains(&room_version) {
|
||||
|
||||
@@ -58,12 +58,6 @@ pub(crate) async fn create_knock_event_template_route(
|
||||
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
if let Some(server) = body.room_id.server_name() {
|
||||
if services.moderation.is_remote_server_forbidden(server) {
|
||||
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
||||
}
|
||||
}
|
||||
|
||||
let room_version = services.rooms.state.get_room_version(&body.room_id).await?;
|
||||
let room_version_rules = room_version.rules().unwrap();
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ pub(crate) async fn create_leave_event_template_route(
|
||||
{
|
||||
info!(
|
||||
origin = body.identity.as_str(),
|
||||
room_id = %body.room_id,
|
||||
"Refusing to serve make_leave for room we aren't participating in"
|
||||
);
|
||||
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||
|
||||
+11
-19
@@ -150,7 +150,7 @@ async fn process_inbound_transaction(
|
||||
.pdus
|
||||
.iter()
|
||||
.stream()
|
||||
.broad_then(|pdu| services.rooms.event_handler.parse_incoming_pdu(pdu))
|
||||
.broad_then(|pdu| services.rooms.event_handler.parse_incoming_pdu(pdu, None))
|
||||
.inspect_err(|e| warn!("Could not parse incoming PDU: {e}"))
|
||||
.ready_filter_map(Result::ok);
|
||||
|
||||
@@ -241,7 +241,7 @@ async fn handle(
|
||||
pdus: impl Stream<Item = Pdu> + Send,
|
||||
edus: impl Stream<Item = Edu> + Send,
|
||||
) -> std::result::Result<ResolvedMap, TransactionError> {
|
||||
// group pdus by room
|
||||
// Group PDUs by room for parallel processing
|
||||
let pdus = pdus
|
||||
.collect()
|
||||
.map(|mut pdus: Vec<_>| {
|
||||
@@ -252,7 +252,7 @@ async fn handle(
|
||||
})
|
||||
.await;
|
||||
|
||||
// we can evaluate rooms concurrently
|
||||
// Evaluate rooms in parallel
|
||||
let results: ResolvedMap = pdus
|
||||
.into_iter()
|
||||
.try_stream()
|
||||
@@ -266,7 +266,7 @@ async fn handle(
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
// evaluate edus after pdus, at least for now.
|
||||
// Evaluate EDUs after PDUs in case some of the PDUs then forbid some EDUs.
|
||||
edus.for_each_concurrent(automatic_width(), |edu| handle_edu(services, client, origin, edu))
|
||||
.boxed()
|
||||
.await;
|
||||
@@ -296,20 +296,12 @@ async fn handle_room(
|
||||
// Try to sort PDUs by their dependencies, but fall back to arbitrary order on
|
||||
// failure (e.g., cycles). This is best-effort; proper ordering is the sender's
|
||||
// responsibility.
|
||||
let sorted_event_ids = if pdu_map.len() >= 2 {
|
||||
let refmap = pdu_map
|
||||
.iter()
|
||||
.map(|(event_id, obj)| (event_id.clone(), obj))
|
||||
.collect();
|
||||
build_local_dag(&refmap, DagBuilderTree::PrevEvents)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
debug_warn!("Failed to build local DAG for room {room_id}: {e}");
|
||||
pdu_map.keys().cloned().collect()
|
||||
})
|
||||
} else {
|
||||
pdu_map.keys().cloned().collect()
|
||||
};
|
||||
let sorted_event_ids = build_local_dag(&pdu_map, DagBuilderTree::PrevEvents)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
debug_warn!("Failed to build local DAG for room {room_id}: {e}");
|
||||
pdu_map.keys().cloned().collect()
|
||||
});
|
||||
let mut results = Vec::with_capacity(sorted_event_ids.len());
|
||||
for event_id in sorted_event_ids {
|
||||
let value = pdu_map
|
||||
@@ -322,7 +314,7 @@ async fn handle_room(
|
||||
let result = services
|
||||
.rooms
|
||||
.event_handler
|
||||
.handle_incoming_pdu(origin, room_id, &event_id, value.clone(), true)
|
||||
.handle_incoming_pdu(origin, room_id, &event_id, value, false)
|
||||
.boxed()
|
||||
.await
|
||||
.map(|_| ());
|
||||
|
||||
+67
-115
@@ -2,34 +2,36 @@
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Event, Result, at, debug, err, info,
|
||||
matrix::event::gen_event_id_canonical_json,
|
||||
trace,
|
||||
Err, Event, Result, at, debug, err, info, trace,
|
||||
utils::stream::{BroadbandExt, IterStream, TryBroadbandExt},
|
||||
warn,
|
||||
};
|
||||
use conduwuit_service::Services;
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use ruma::{
|
||||
CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, ServerName,
|
||||
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, RoomId, ServerName, UserId,
|
||||
api::federation::membership::create_join_event,
|
||||
assign,
|
||||
events::{
|
||||
StateEventType, TimelineEventType,
|
||||
TimelineEventType,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
room_version_rules::RoomVersionRules,
|
||||
};
|
||||
use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
|
||||
use serde_json::value::to_raw_value;
|
||||
|
||||
use crate::Ruma;
|
||||
use crate::{Ruma, server::utils::validate_any_membership_event};
|
||||
|
||||
/// helper method for /send_join v1 and v2
|
||||
/// Creates a join membership event for the target, returning a computed
|
||||
/// response with room state, auth chains, etc.
|
||||
#[tracing::instrument(skip(services, pdu, omit_members), fields(room_id = room_id.as_str(), origin = origin.as_str()), level = "info")]
|
||||
async fn create_join_event(
|
||||
services: &Services,
|
||||
origin: &ServerName,
|
||||
room_id: &RoomId,
|
||||
pdu: &RawJsonValue,
|
||||
event_id: &EventId,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
mut pdu: CanonicalJsonObject,
|
||||
omit_members: bool,
|
||||
) -> Result<create_join_event::v2::RoomState> {
|
||||
if !services.rooms.metadata.exists(room_id).await {
|
||||
@@ -43,6 +45,7 @@ async fn create_join_event(
|
||||
{
|
||||
info!(
|
||||
origin = origin.as_str(),
|
||||
room_id = %room_id,
|
||||
"Refusing to serve send_join for room we aren't participating in"
|
||||
);
|
||||
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||
@@ -64,94 +67,25 @@ async fn create_join_event(
|
||||
.await
|
||||
.map_err(|e| err!(Request(NotFound(error!("Room has no state: {e}")))))?;
|
||||
|
||||
// We do not add the event_id field to the pdu here because of signature and
|
||||
// hashes checks
|
||||
trace!("Getting room version");
|
||||
let room_version = services.rooms.state.get_room_version(room_id).await?;
|
||||
let room_version_rules = room_version.rules().unwrap();
|
||||
|
||||
trace!("Generating event ID and converting to canonical json");
|
||||
let Ok((event_id, mut value)) = gen_event_id_canonical_json(pdu, &room_version_rules) else {
|
||||
// Event could not be converted to canonical json
|
||||
return Err!(Request(BadJson("Could not convert event to canonical json.")));
|
||||
};
|
||||
|
||||
let event_room_id: OwnedRoomId = serde_json::from_value(
|
||||
value
|
||||
.get("room_id")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing room_id property."))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("room_id field is not a valid room ID: {e}")))))?;
|
||||
|
||||
if event_room_id != room_id {
|
||||
return Err!(Request(BadJson("Event room_id does not match request path room ID.")));
|
||||
}
|
||||
|
||||
let event_type: StateEventType = serde_json::from_value(
|
||||
value
|
||||
.get("type")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing type property."))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("Event has invalid state event type: {e}")))))?;
|
||||
|
||||
if event_type != StateEventType::RoomMember {
|
||||
return Err!(Request(BadJson(
|
||||
"Not allowed to send non-membership state event to join endpoint."
|
||||
)));
|
||||
}
|
||||
let sender = pdu
|
||||
.get("sender")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(UserId::parse)
|
||||
.and_then(Result::ok)
|
||||
.expect("sender was already validated");
|
||||
|
||||
let content: RoomMemberEventContent = serde_json::from_value(
|
||||
value
|
||||
.get("content")
|
||||
pdu.get("content")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing content property"))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("Event content is empty or invalid: {e}")))))?;
|
||||
|
||||
if content.membership != MembershipState::Join {
|
||||
return Err!(Request(BadJson(
|
||||
"Not allowed to send a non-join membership event to join endpoint."
|
||||
)));
|
||||
}
|
||||
|
||||
let sender: OwnedUserId = serde_json::from_value(
|
||||
value
|
||||
.get("sender")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing sender property."))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("sender property is not a valid user ID: {e}")))))?;
|
||||
|
||||
// check if origin server is trying to send for another server
|
||||
if sender.server_name() != origin {
|
||||
return Err!(Request(Forbidden("Not allowed to join on behalf of another server.")));
|
||||
}
|
||||
|
||||
let state_key: OwnedUserId = serde_json::from_value(
|
||||
value
|
||||
.get("state_key")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing state_key property."))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("State key is not a valid user ID: {e}")))))?;
|
||||
|
||||
if state_key != sender {
|
||||
return Err!(Request(BadJson("State key does not match sender user.")));
|
||||
}
|
||||
|
||||
if let Some(authorising_user) = content.join_authorized_via_users_server {
|
||||
use ruma::RoomVersionId::*;
|
||||
|
||||
if matches!(room_version, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
|
||||
if !room_version_rules.authorization.restricted_join_rule {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Room version {room_version} does not support restricted rooms but \
|
||||
"Room version does not support restricted rooms but \
|
||||
join_authorised_via_users_server ({authorising_user}) was found in the event."
|
||||
)));
|
||||
}
|
||||
@@ -174,8 +108,7 @@ async fn create_join_event(
|
||||
they cannot authorise your join."
|
||||
)));
|
||||
}
|
||||
|
||||
if !super::user_can_perform_restricted_join(services, &state_key, room_id).await? {
|
||||
if !super::user_can_perform_restricted_join(services, &sender, room_id).await? {
|
||||
return Err!(Request(UnableToAuthorizeJoin(
|
||||
"Joining user did not pass restricted room's rules."
|
||||
)));
|
||||
@@ -183,7 +116,7 @@ async fn create_join_event(
|
||||
|
||||
services
|
||||
.server_keys
|
||||
.hash_and_sign_event(&mut value, &room_version_rules)
|
||||
.hash_and_sign_event(&mut pdu, room_version_rules)
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam(warn!("Failed to sign send_join event: {e}"))))
|
||||
})?;
|
||||
@@ -200,7 +133,7 @@ async fn create_join_event(
|
||||
let pdu_id = services
|
||||
.rooms
|
||||
.event_handler
|
||||
.handle_incoming_pdu(sender.server_name(), room_id, &event_id, value.clone(), true)
|
||||
.handle_incoming_pdu(sender.server_name(), room_id, event_id, pdu.clone(), false)
|
||||
.boxed()
|
||||
.await?
|
||||
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
|
||||
@@ -220,14 +153,12 @@ async fn create_join_event(
|
||||
.iter()
|
||||
.try_stream()
|
||||
.broad_filter_map(|event_id| async move {
|
||||
if omit_members {
|
||||
if let Ok(e) = event_id.as_ref() {
|
||||
let pdu = services.rooms.timeline.get_pdu(e).await;
|
||||
if pdu.is_ok_and(|p| *p.kind() == TimelineEventType::RoomMember) {
|
||||
trace!("omitting member event {e:?} from returned state");
|
||||
// skip members
|
||||
return None;
|
||||
}
|
||||
if omit_members && let Ok(e) = event_id.as_ref() {
|
||||
let pdu = services.rooms.timeline.get_pdu(e).await;
|
||||
if pdu.is_ok_and(|p| *p.kind() == TimelineEventType::RoomMember) {
|
||||
trace!("omitting member event {e:?} from returned state");
|
||||
// skip members
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(event_id)
|
||||
@@ -289,7 +220,7 @@ async fn create_join_event(
|
||||
Ok(assign!(create_join_event::v2::RoomState::new(), {
|
||||
auth_chain,
|
||||
state,
|
||||
event: to_raw_value(&CanonicalJsonValue::Object(value)).ok(),
|
||||
event: to_raw_value(&CanonicalJsonValue::Object(pdu)).ok(),
|
||||
members_omitted: omit_members,
|
||||
servers_in_room,
|
||||
}))
|
||||
@@ -309,24 +240,45 @@ pub(crate) async fn create_join_event_v2_route(
|
||||
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
if let Some(server) = body.room_id.server_name() {
|
||||
if services.moderation.is_remote_server_forbidden(server) {
|
||||
warn!(
|
||||
"Server {} tried joining room ID {} through us which has a server name that is \
|
||||
globally forbidden. Rejecting.",
|
||||
body.identity, &body.room_id,
|
||||
);
|
||||
return Err!(Request(Forbidden(warn!(
|
||||
"Room ID server name {server} is banned on this homeserver."
|
||||
))));
|
||||
}
|
||||
let room_version = services.rooms.state.get_room_version(&body.room_id).await?;
|
||||
let create_event = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_room_create_event(&body.room_id)
|
||||
.await;
|
||||
let room_version_rules = room_version.rules().unwrap();
|
||||
|
||||
let (value, membership, sender, target) = validate_any_membership_event(
|
||||
&services,
|
||||
&body.pdu,
|
||||
&room_version_rules,
|
||||
create_event.event_id.clone(),
|
||||
body.room_id.clone(),
|
||||
body.event_id.clone(),
|
||||
)
|
||||
.await?;
|
||||
if membership != MembershipState::Join {
|
||||
return Err!(Request(InvalidParam("Invalid membership (expected `join`)")));
|
||||
}
|
||||
if sender.server_name() != body.identity {
|
||||
return Err!(Request(InvalidParam("Sender belongs to a different server")));
|
||||
}
|
||||
if sender != target {
|
||||
return Err!(Request(InvalidParam("Sender does not match state key")));
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
let room_state =
|
||||
create_join_event(&services, &body.identity, &body.room_id, &body.pdu, body.omit_members)
|
||||
.boxed()
|
||||
.await?;
|
||||
let room_state = create_join_event(
|
||||
&services,
|
||||
&body.identity,
|
||||
&body.room_id,
|
||||
&body.event_id,
|
||||
&room_version_rules,
|
||||
value,
|
||||
body.omit_members,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
info!(
|
||||
"Finished sending a join for {} in {} in {:?}",
|
||||
body.identity,
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Result, err, info,
|
||||
matrix::{event::gen_event_id_canonical_json, pdu::PduEvent},
|
||||
warn,
|
||||
};
|
||||
use conduwuit::{Err, Event, Result, debug_info, err, matrix::pdu::PduEvent, warn};
|
||||
use futures::FutureExt;
|
||||
use ruma::{
|
||||
OwnedUserId,
|
||||
api::federation::membership::create_knock_event,
|
||||
events::{
|
||||
StateEventType,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
serde::JsonObject,
|
||||
api::federation::membership::create_knock_event, events::room::member::MembershipState,
|
||||
};
|
||||
|
||||
use crate::Ruma;
|
||||
use crate::{Ruma, server::utils::validate_any_membership_event};
|
||||
|
||||
/// # `PUT /_matrix/federation/v1/send_knock/{roomId}/{eventId}`
|
||||
///
|
||||
@@ -33,18 +23,7 @@ pub(crate) async fn create_knock_event_v1_route(
|
||||
forbidden. Rejecting.",
|
||||
body.identity, &body.room_id,
|
||||
);
|
||||
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
if let Some(server) = body.room_id.server_name() {
|
||||
if services.moderation.is_remote_server_forbidden(server) {
|
||||
warn!(
|
||||
"Server {} tried knocking room ID {} which has a server name that is globally \
|
||||
forbidden. Rejecting.",
|
||||
body.identity, &body.room_id,
|
||||
);
|
||||
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
||||
}
|
||||
return Err!(Request(Forbidden("Federation denied with {}", body.identity)));
|
||||
}
|
||||
|
||||
if !services.rooms.metadata.exists(&body.room_id).await {
|
||||
@@ -57,8 +36,9 @@ pub(crate) async fn create_knock_event_v1_route(
|
||||
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||
.await
|
||||
{
|
||||
info!(
|
||||
debug_info!(
|
||||
origin = body.identity.as_str(),
|
||||
room_id = %body.room_id,
|
||||
"Refusing to serve send_knock for room we aren't participating in"
|
||||
);
|
||||
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||
@@ -72,88 +52,40 @@ pub(crate) async fn create_knock_event_v1_route(
|
||||
.await?;
|
||||
|
||||
let room_version = services.rooms.state.get_room_version(&body.room_id).await?;
|
||||
let create_event = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_room_create_event(&body.room_id)
|
||||
.await;
|
||||
let room_version_rules = room_version.rules().unwrap();
|
||||
|
||||
if !room_version_rules.authorization.knocking {
|
||||
return Err!(Request(Forbidden("Room version does not support knocking.")));
|
||||
}
|
||||
|
||||
let Ok((event_id, value)) = gen_event_id_canonical_json(&body.pdu, &room_version_rules)
|
||||
else {
|
||||
// Event could not be converted to canonical json
|
||||
return Err!(Request(InvalidParam("Could not convert event to canonical json.")));
|
||||
};
|
||||
|
||||
let event_type: StateEventType = serde_json::from_value(
|
||||
value
|
||||
.get("type")
|
||||
.ok_or_else(|| err!(Request(InvalidParam("Event has no event type."))))?
|
||||
.clone()
|
||||
.into(),
|
||||
let (mut event, target_membership, sender, target) = validate_any_membership_event(
|
||||
&services,
|
||||
&body.pdu,
|
||||
&room_version_rules,
|
||||
create_event.event_id().to_owned(),
|
||||
body.room_id.clone(),
|
||||
body.event_id.clone(),
|
||||
)
|
||||
.map_err(|e| err!(Request(InvalidParam("Event has invalid event type: {e}"))))?;
|
||||
.await?;
|
||||
|
||||
if event_type != StateEventType::RoomMember {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Not allowed to send non-membership state event to knock endpoint.",
|
||||
)));
|
||||
if target_membership != MembershipState::Knock {
|
||||
return Err!(Request(InvalidParam("Invalid membership (expected `knock`)")));
|
||||
}
|
||||
|
||||
let content: RoomMemberEventContent = serde_json::from_value(
|
||||
value
|
||||
.get("content")
|
||||
.ok_or_else(|| err!(Request(InvalidParam("Membership event has no content"))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(InvalidParam("Event has invalid membership content: {e}"))))?;
|
||||
|
||||
if content.membership != MembershipState::Knock {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Not allowed to send a non-knock membership event to knock endpoint."
|
||||
)));
|
||||
}
|
||||
|
||||
// ACL check sender server name
|
||||
let sender: OwnedUserId = serde_json::from_value(
|
||||
value
|
||||
.get("sender")
|
||||
.ok_or_else(|| err!(Request(InvalidParam("Event has no sender user ID."))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson("Event sender is not a valid user ID: {e}"))))?;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.event_handler
|
||||
.acl_check(sender.server_name(), &body.room_id)
|
||||
.await?;
|
||||
|
||||
// check if origin server is trying to send for another server
|
||||
if sender.server_name() != body.identity {
|
||||
return Err!(Request(BadJson("Not allowed to knock on behalf of another server/user.")));
|
||||
return Err!(Request(InvalidParam("Sender belongs to a different server")));
|
||||
}
|
||||
if sender != target {
|
||||
return Err!(Request(InvalidParam("Sender does not match state key")));
|
||||
}
|
||||
|
||||
let state_key: OwnedUserId = serde_json::from_value(
|
||||
value
|
||||
.get("state_key")
|
||||
.ok_or_else(|| err!(Request(InvalidParam("Event does not have a state_key"))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson("Event does not have a valid state_key: {e}"))))?;
|
||||
event.insert("event_id".to_owned(), body.event_id.as_str().into());
|
||||
|
||||
if state_key != sender {
|
||||
return Err!(Request(InvalidParam("state_key does not match sender user of event.")));
|
||||
}
|
||||
|
||||
let mut event: JsonObject = serde_json::from_str(body.pdu.get())
|
||||
.map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
|
||||
|
||||
event.insert("event_id".to_owned(), "$placeholder".into());
|
||||
|
||||
let pdu: PduEvent = serde_json::from_value(event.into())
|
||||
let pdu = PduEvent::from_id_val(&body.event_id, event.clone())
|
||||
.map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
|
||||
|
||||
let mutex_lock = services
|
||||
@@ -166,7 +98,7 @@ pub(crate) async fn create_knock_event_v1_route(
|
||||
let pdu_id = services
|
||||
.rooms
|
||||
.event_handler
|
||||
.handle_incoming_pdu(sender.server_name(), &body.room_id, &event_id, value.clone(), true)
|
||||
.handle_incoming_pdu(sender.server_name(), &body.room_id, &body.event_id, event, false)
|
||||
.boxed()
|
||||
.await?
|
||||
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
|
||||
@@ -181,7 +113,7 @@ pub(crate) async fn create_knock_event_v1_route(
|
||||
let knock_room_state = services
|
||||
.rooms
|
||||
.state
|
||||
.summary_stripped(&pdu, &body.room_id, &sender)
|
||||
.summary_stripped(&pdu, &body.room_id, &sender, true)
|
||||
.await;
|
||||
|
||||
Ok(create_knock_event::v1::Response::new(knock_room_state))
|
||||
|
||||
+31
-104
@@ -1,18 +1,11 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result, err, info, matrix::event::gen_event_id_canonical_json};
|
||||
use conduwuit_service::Services;
|
||||
use conduwuit::{Err, Result, debug_info, err};
|
||||
use futures::FutureExt;
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedUserId, RoomId, ServerName,
|
||||
api::federation::membership::create_leave_event,
|
||||
events::{
|
||||
StateEventType,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
api::federation::membership::create_leave_event, events::room::member::MembershipState,
|
||||
};
|
||||
use serde_json::value::RawValue as RawJsonValue;
|
||||
|
||||
use crate::Ruma;
|
||||
use crate::{Ruma, server::utils::validate_any_membership_event};
|
||||
|
||||
/// # `PUT /_matrix/federation/v2/send_leave/{roomId}/{eventId}`
|
||||
///
|
||||
@@ -21,17 +14,8 @@ pub(crate) async fn create_leave_event_v2_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<create_leave_event::v2::Request>,
|
||||
) -> Result<create_leave_event::v2::Response> {
|
||||
create_leave_event(&services, &body.identity, &body.room_id, &body.pdu).await?;
|
||||
|
||||
Ok(create_leave_event::v2::Response::new())
|
||||
}
|
||||
|
||||
async fn create_leave_event(
|
||||
services: &Services,
|
||||
origin: &ServerName,
|
||||
room_id: &RoomId,
|
||||
pdu: &RawJsonValue,
|
||||
) -> Result {
|
||||
let room_id = body.room_id.as_ref();
|
||||
let origin = &body.identity;
|
||||
if !services.rooms.metadata.exists(room_id).await {
|
||||
return Err!(Request(NotFound("Room is unknown to this server.")));
|
||||
}
|
||||
@@ -42,9 +26,10 @@ async fn create_leave_event(
|
||||
.server_in_room(services.globals.server_name(), room_id)
|
||||
.await
|
||||
{
|
||||
info!(
|
||||
debug_info!(
|
||||
origin = origin.as_str(),
|
||||
"Refusing to serve backfill for room we aren't participating in"
|
||||
room_id = %room_id,
|
||||
"Refusing to send_leave for room we aren't participating in"
|
||||
);
|
||||
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||
}
|
||||
@@ -56,91 +41,31 @@ async fn create_leave_event(
|
||||
.acl_check(origin, room_id)
|
||||
.await?;
|
||||
|
||||
// We do not add the event_id field to the pdu here because of signature and
|
||||
// hashes checks
|
||||
let room_version = services.rooms.state.get_room_version(room_id).await?;
|
||||
let create_event = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_room_create_event(room_id)
|
||||
.await;
|
||||
let room_version_rules = room_version.rules().unwrap();
|
||||
|
||||
let Ok((event_id, value)) = gen_event_id_canonical_json(pdu, &room_version_rules) else {
|
||||
// Event could not be converted to canonical json
|
||||
return Err!(Request(BadJson("Could not convert event to canonical json.")));
|
||||
};
|
||||
|
||||
let event_room_id: OwnedRoomId = serde_json::from_value(
|
||||
serde_json::to_value(
|
||||
value
|
||||
.get("room_id")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing room_id property."))))?,
|
||||
)
|
||||
.expect("CanonicalJson is valid json value"),
|
||||
let (value, membership, sender, target) = validate_any_membership_event(
|
||||
&services,
|
||||
&body.pdu,
|
||||
&room_version_rules,
|
||||
create_event.event_id.clone(),
|
||||
body.room_id.clone(),
|
||||
body.event_id.clone(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("room_id field is not a valid room ID: {e}")))))?;
|
||||
|
||||
if event_room_id != room_id {
|
||||
return Err!(Request(BadJson("Event room_id does not match request path room ID.")));
|
||||
.await?;
|
||||
if membership != MembershipState::Leave {
|
||||
return Err!(Request(InvalidParam("Invalid membership (expected `leave`)")));
|
||||
}
|
||||
|
||||
let content: RoomMemberEventContent = serde_json::from_value(
|
||||
value
|
||||
.get("content")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing content property."))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("Event content is empty or invalid: {e}")))))?;
|
||||
|
||||
if content.membership != MembershipState::Leave {
|
||||
return Err!(Request(BadJson(
|
||||
"Not allowed to send a non-leave membership event to leave endpoint."
|
||||
)));
|
||||
if sender.server_name() != body.identity {
|
||||
return Err!(Request(InvalidParam("Sender belongs to a different server")));
|
||||
}
|
||||
|
||||
let event_type: StateEventType = serde_json::from_value(
|
||||
value
|
||||
.get("type")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing type property."))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("Event has invalid state event type: {e}")))))?;
|
||||
|
||||
if event_type != StateEventType::RoomMember {
|
||||
return Err!(Request(BadJson(
|
||||
"Not allowed to send non-membership state event to leave endpoint."
|
||||
)));
|
||||
}
|
||||
|
||||
// ACL check sender server name
|
||||
let sender: OwnedUserId = serde_json::from_value(
|
||||
value
|
||||
.get("sender")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing sender property."))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("sender property is not a valid user ID: {e}")))))?;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.event_handler
|
||||
.acl_check(sender.server_name(), room_id)
|
||||
.await?;
|
||||
|
||||
if sender.server_name() != origin {
|
||||
return Err!(Request(BadJson("Not allowed to leave on behalf of another server/user.")));
|
||||
}
|
||||
|
||||
let state_key: OwnedUserId = serde_json::from_value(
|
||||
value
|
||||
.get("state_key")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing state_key property."))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("State key is not a valid user ID: {e}")))))?;
|
||||
|
||||
if state_key != sender {
|
||||
return Err!(Request(BadJson("State key does not match sender user.")));
|
||||
if sender != target {
|
||||
return Err!(Request(InvalidParam("Sender does not match state key")));
|
||||
}
|
||||
|
||||
let mutex_lock = services
|
||||
@@ -153,7 +78,7 @@ async fn create_leave_event(
|
||||
let pdu_id = services
|
||||
.rooms
|
||||
.event_handler
|
||||
.handle_incoming_pdu(origin, room_id, &event_id, value, true)
|
||||
.handle_incoming_pdu(origin, room_id, &body.event_id, value, false)
|
||||
.boxed()
|
||||
.await?
|
||||
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
|
||||
@@ -164,5 +89,7 @@ async fn create_leave_event(
|
||||
.sending
|
||||
.send_pdu_room(room_id, &pdu_id)
|
||||
.boxed()
|
||||
.await
|
||||
.await?;
|
||||
|
||||
Ok(create_leave_event::v2::Response::new())
|
||||
}
|
||||
|
||||
+105
-2
@@ -1,7 +1,10 @@
|
||||
use conduwuit::{Err, Result, is_false};
|
||||
use conduwuit::{Err, Result, err, is_false};
|
||||
use conduwuit_service::Services;
|
||||
use futures::{FutureExt, future::OptionFuture, join};
|
||||
use ruma::{EventId, RoomId, ServerName};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, ServerName,
|
||||
UserId, events::room::member::MembershipState, room_version_rules::RoomVersionRules,
|
||||
};
|
||||
|
||||
pub(super) struct AccessCheck<'a> {
|
||||
pub(super) services: &'a Services,
|
||||
@@ -63,3 +66,103 @@ pub(super) async fn assert(&self) -> Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs validation on a membership event that should be run on any event a
|
||||
/// remote is trying to send via us.
|
||||
///
|
||||
/// ## Checks performed
|
||||
///
|
||||
/// 1. PDU room ID matches request path room ID
|
||||
/// 2. PDU event ID matches request path event ID
|
||||
/// 3. Signature check
|
||||
/// 4. Event type check
|
||||
/// 5. `sender` field presence (and parsing)
|
||||
/// 6. `state_key` field presence (and parsing)
|
||||
/// 7. PDU room format check (PDU check 1)
|
||||
///
|
||||
/// ## Returns
|
||||
///
|
||||
/// A resulting tuple of (PDU JSON, target membership state, sender, recipient).
|
||||
pub(crate) async fn validate_any_membership_event(
|
||||
services: &crate::State,
|
||||
body: &serde_json::value::RawValue,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
create_event_id: OwnedEventId,
|
||||
expected_room_id: OwnedRoomId,
|
||||
expected_event_id: OwnedEventId,
|
||||
) -> Result<(CanonicalJsonObject, MembershipState, OwnedUserId, OwnedUserId)> {
|
||||
let (template_room_id, template_event_id, pdu) = services
|
||||
.rooms
|
||||
.event_handler
|
||||
.parse_incoming_pdu(body, Some(room_version_rules))
|
||||
.await
|
||||
.map_err(|e| err!(Request(BadJson("Invalid membership PDU: {e}"))))?;
|
||||
|
||||
if template_room_id != expected_room_id {
|
||||
return Err!(Request(InvalidParam("Membership event does not belong to requested room")));
|
||||
}
|
||||
if template_event_id != expected_event_id {
|
||||
return Err!(Request(InvalidParam(debug_warn!(
|
||||
%template_event_id,
|
||||
%expected_event_id,
|
||||
"Membership event ID does not match provided event ID"
|
||||
))));
|
||||
}
|
||||
|
||||
services
|
||||
.server_keys
|
||||
.verify_event(&pdu, room_version_rules)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam("Signature verification failed on membership event: {e}")))
|
||||
})?;
|
||||
|
||||
// Ensure this is a membership event
|
||||
if pdu
|
||||
.get("type")
|
||||
.expect("event must have a type")
|
||||
.as_str()
|
||||
.expect("type must be a string")
|
||||
!= "m.room.member"
|
||||
{
|
||||
return Err!(Request(BadJson(
|
||||
"Not allowed to send non-membership event to this endpoint"
|
||||
)));
|
||||
}
|
||||
let membership = pdu
|
||||
.get("content")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing content property"))))?
|
||||
.as_object()
|
||||
.ok_or_else(|| err!(Request(BadJson("Event content is not an object"))))?
|
||||
.get("membership")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing membership property"))))?
|
||||
.as_str()
|
||||
.ok_or_else(|| err!(Request(BadJson("Event is not a string"))))?
|
||||
.to_owned();
|
||||
|
||||
let sender_user = pdu
|
||||
.get("sender")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(UserId::parse)
|
||||
.and_then(Result::ok)
|
||||
.ok_or_else(|| err!(Request(InvalidParam("Invalid sender property"))))?;
|
||||
let recipient_user = pdu
|
||||
.get("state_key")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(UserId::parse)
|
||||
.and_then(Result::ok)
|
||||
.ok_or_else(|| err!(Request(InvalidParam("Invalid state_key property"))))?;
|
||||
|
||||
// Do a quick format check. The spec doesn't suggest this, but it's probably
|
||||
// a good idea nonetheless.
|
||||
service::rooms::event_handler::Service::pdu_format_check_1(
|
||||
&pdu,
|
||||
room_version_rules,
|
||||
&create_event_id,
|
||||
)
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam("Membership event violates the room event format: {e}")))
|
||||
})?;
|
||||
|
||||
Ok((pdu, membership.into(), sender_user, recipient_user))
|
||||
}
|
||||
|
||||
+23
-1
@@ -370,6 +370,27 @@ pub struct Config {
|
||||
#[serde(default = "default_ip_lookup_strategy")]
|
||||
pub ip_lookup_strategy: u8,
|
||||
|
||||
/// The source to use for discovering the real connecting client IP.
|
||||
///
|
||||
/// Takes any of the following options:
|
||||
///
|
||||
/// "cf_connecting_ip" - `Cf-Connecting-Ip` header
|
||||
/// "cloudfront_viewer_address" - `CloudFront-Viewer-Address` header
|
||||
/// "fly_client_ip" - `Fly-Client-IP` header
|
||||
/// "x_forwarded_for" - rightmost value of the `X-Forwarded-For` header
|
||||
/// "true_client_ip" - `True-Client-Ip` header
|
||||
/// "x_envoy_external_address" - `X-Envoy-External-Address` header
|
||||
/// "x_real_ip" - `X-Real-Ip` header
|
||||
///
|
||||
/// Only set this if you are certain only your reverse proxy
|
||||
/// will send the expected header. There is no "is the connecting IP allowed
|
||||
/// to set this header" check; if the header selected is present, it is
|
||||
/// used.
|
||||
///
|
||||
/// Defaults to the IP address actually making the connection.
|
||||
#[serde(default)]
|
||||
pub request_ip_source: Option<String>,
|
||||
|
||||
/// Max request size for file uploads in bytes. Defaults to 20MB.
|
||||
/// Also limits incoming federated media.
|
||||
///
|
||||
@@ -2421,6 +2442,7 @@ pub struct OauthConfig {
|
||||
/// legacy authentication will be unable to log in.
|
||||
///
|
||||
/// default: "hybrid"
|
||||
#[serde(default)]
|
||||
compatibility_mode: OAuthMode,
|
||||
|
||||
/// display: hidden
|
||||
@@ -2476,7 +2498,7 @@ pub struct OidcConfig {
|
||||
/// automatically fetch the identity provider's metadata from this URL.
|
||||
/// Generally you should set this to the base domain your identity provider
|
||||
/// runs on.
|
||||
pub discovery_url: Url,
|
||||
pub discovery_url: String,
|
||||
|
||||
/// The OAuth client ID for Continuwuity to use when communicating with the
|
||||
/// identity provider.
|
||||
|
||||
@@ -149,6 +149,14 @@ macro_rules! err_log {
|
||||
#[macro_export]
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! err_lev {
|
||||
(debug_info) => {
|
||||
if $crate::debug::logging() {
|
||||
$crate::tracing::Level::INFO
|
||||
} else {
|
||||
$crate::tracing::Level::DEBUG
|
||||
}
|
||||
};
|
||||
|
||||
(debug_warn) => {
|
||||
if $crate::debug::logging() {
|
||||
$crate::tracing::Level::WARN
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
},
|
||||
};
|
||||
|
||||
use conduwuit::{Err, Result, debug, info, warn};
|
||||
use conduwuit::{Err, Result, debug, err, info, warn};
|
||||
use rocksdb::{
|
||||
AsColumnFamilyRef, BoundColumnFamily, DBCommon, DBWithThreadMode, MultiThreaded,
|
||||
WaitForCompactOptions,
|
||||
@@ -127,6 +127,12 @@ pub fn current_sequence(&self) -> u64 {
|
||||
|
||||
sequence
|
||||
}
|
||||
|
||||
pub fn drop_column(&self, name: &str) -> Result {
|
||||
self.db
|
||||
.drop_cf(name)
|
||||
.map_err(|err| err!("Failed to drop {name}: {err}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Engine {
|
||||
|
||||
+1
-1
@@ -42,12 +42,12 @@ assets = [
|
||||
[features]
|
||||
default = [
|
||||
"standard",
|
||||
"release_max_log_level",
|
||||
"ring",
|
||||
"bindgen-runtime", # replace with bindgen-static on alpine
|
||||
]
|
||||
standard = [
|
||||
"brotli_compression",
|
||||
"direct_tls",
|
||||
"element_hacks",
|
||||
"gzip_compression",
|
||||
"io_uring",
|
||||
|
||||
+17
-1
@@ -48,6 +48,22 @@ pub(crate) fn build(services: &Arc<Services>) -> Result<(Router, Guard)> {
|
||||
))]
|
||||
let layers = layers.layer(compression_layer(server));
|
||||
|
||||
let client_ip_layer = match services
|
||||
.config
|
||||
.request_ip_source
|
||||
.as_ref()
|
||||
.map(AsRef::as_ref)
|
||||
{
|
||||
| Some("cf_connecting_ip") => ClientIpSource::CfConnectingIp,
|
||||
| Some("cloudfront_viewer_address") => ClientIpSource::CloudFrontViewerAddress,
|
||||
| Some("fly_client_ip") => ClientIpSource::FlyClientIp,
|
||||
| Some("x_forwarded_for") => ClientIpSource::RightmostXForwardedFor,
|
||||
| Some("true_client_ip") => ClientIpSource::TrueClientIp,
|
||||
| Some("x_envoy_external_address") => ClientIpSource::XEnvoyExternalAddress,
|
||||
| Some("x_real_ip") => ClientIpSource::XRealIp,
|
||||
| None | Some(_) => ClientIpSource::ConnectInfo,
|
||||
};
|
||||
|
||||
let services_ = services.clone();
|
||||
let layers = layers
|
||||
.layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION]))
|
||||
@@ -59,7 +75,7 @@ pub(crate) fn build(services: &Arc<Services>) -> Result<(Router, Guard)> {
|
||||
.on_response(DefaultOnResponse::new().level(Level::DEBUG)),
|
||||
)
|
||||
.layer(axum::middleware::from_fn_with_state(Arc::clone(services), request::handle))
|
||||
.layer(ClientIpSource::ConnectInfo.into_extension())
|
||||
.layer(client_ip_layer.into_extension())
|
||||
.layer(ResponseBodyTimeoutLayer::new(Duration::from_secs(
|
||||
server.config.client_response_timeout,
|
||||
)))
|
||||
|
||||
@@ -219,7 +219,7 @@ fn configure_output_err(mut output: MadSkin) -> MadSkin {
|
||||
use termimad::{Alignment, CompoundStyle, LineStyle, crossterm::style::Color};
|
||||
|
||||
let code_style = CompoundStyle::with_fgbg(Color::AnsiValue(196), Color::AnsiValue(234));
|
||||
output.inline_code = code_style.clone();
|
||||
output.inline_code = code_style;
|
||||
output.code_block = LineStyle {
|
||||
left_margin: 0,
|
||||
right_margin: 0,
|
||||
@@ -234,7 +234,7 @@ fn configure_output(mut output: MadSkin) -> MadSkin {
|
||||
use termimad::{Alignment, CompoundStyle, LineStyle, crossterm::style::Color};
|
||||
|
||||
let code_style = CompoundStyle::with_fgbg(Color::AnsiValue(40), Color::AnsiValue(234));
|
||||
output.inline_code = code_style.clone();
|
||||
output.inline_code = code_style;
|
||||
output.code_block = LineStyle {
|
||||
left_margin: 0,
|
||||
right_margin: 0,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use conduwuit::{Err, Result, debug, debug_info, error, info};
|
||||
use std::io::IsTerminal;
|
||||
|
||||
use conduwuit::{Err, Result, debug, debug_info, error, info, warn};
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
use tokio::time::{Duration, sleep};
|
||||
|
||||
@@ -7,10 +9,16 @@
|
||||
pub(super) const SIGNAL: &str = "SIGUSR2";
|
||||
|
||||
impl super::Service {
|
||||
/// Possibly spawn the terminal console at startup if configured.
|
||||
/// Possibly spawn the terminal console at startup if configured and a TTY
|
||||
/// is available.
|
||||
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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user