Compare commits

..
Author SHA1 Message Date
Renovate Bot ba0ec6ba70 chore(deps): update https://github.com/actions/setup-node action to v7 2026-07-15 05:05:16 +00:00
Erwan Leboucher 1ac05838dd docs: Add back binstall with a link to the deps 2026-07-14 19:44:28 +02:00
Erwan Leboucher 7ecd2aa5e2 fix(pdu): Exempt m.room.create from auth_events check 2026-07-14 19:27:46 +02:00
Renovate BotandEllis Git 69a3145983 chore(deps): update rust-zerover-patch-updates 2026-07-14 13:10:32 +00:00
N00byKing 73b7553b1e chore: Add changelog entry for #1984 2026-07-14 13:40:28 +02:00
N00byKing 232ec3f620 fix: Return 201 instead of 200 on oauth registration
See RFC 7591 at 3.2.1: "The server responds with an HTTP 201 Created status code [...]"

Fixes a failure in matrix-dart-sdk
2026-07-14 11:00:34 +02:00
N00byKing e52bbeaf94 fix: Correct link for code style guide in CONTRIBUTING.md 2026-07-14 08:59:08 +00:00
Renovate Bot ad1c194fb0 chore(deps): update github-actions-digest 2026-07-14 05:01:52 +00:00
GingerandEllis Git 5e2dcd8e79 chore: News fragment 2026-07-13 20:38:10 +00:00
GingerandEllis Git 24c6474bd1 fix: Allow unstable and stable device id query together 2026-07-13 20:38:10 +00:00
gingerandEllis Git 642d05048b chore: announce 2026-07-13 18:54:04 +00:00
Koen OostveenandEllis Git 446ae93ad7 fix(web): Change incorrect deeplink for deleting a device 2026-07-13 13:45:46 +00:00
Renovate BotandEllis Git 9ca0774b2d chore(deps): lock file maintenance 2026-07-13 13:44:47 +00:00
Ginger ce494eb0c0 fix: Properly sync newly created rooms 2026-07-13 08:50:53 -04:00
Ginger 1f5e178c3f fix: Properly handle appservice device creation 2026-07-13 08:50:48 -04:00
Renovate Bot ddd050d402 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v43.260.2 2026-07-13 05:03:24 +00:00
Ginger 6ad3e679bc chore: Release 2026-07-12 17:34:16 -04:00
Ginger 65812bb246 chore: Update changelog 2026-07-12 17:33:52 -04:00
theS1LV3R 9f3a7994c7 chore: Changelog 2026-07-12 20:22:19 +02:00
theS1LV3R baef3289fe fix(service/admin): Prevent console from being spawned when no TTY is available 2026-07-12 20:22:18 +02:00
Ginger 9a94b93ddd fix: Fix new database migrations running on every server startup 2026-07-12 12:31:12 -04:00
Gingerandtimedout 0ac0bd93ae fix: Default deserialize for compatibility_mode 2026-07-12 16:28:27 +01:00
Gingerandtimedout fe9f718c6e fix: Stop using the federation DNS resolver for non-federation requests 2026-07-12 16:11:00 +01:00
nexandEllis Git e333f5a8e5 chore: announce 2026-07-12 04:14:23 +00:00
Ginger b8c64a68d4 chore: Release 2026-07-12 00:09:26 -04:00
Ginger 163f049757 chore: Update changelog 2026-07-12 00:07:20 -04:00
Ginger f48125c37a fix: Remove dependency on aws-lc-rs 2026-07-11 22:48:30 -04:00
timedoutandEllis Git 4001b99261 style: Make send_join's docstring more useful 2026-07-12 02:11:50 +00:00
timedoutandEllis Git b6e5a106ff fix: Don't unhelpfully panic when encountering an unrecognised room version 2026-07-12 02:11:50 +00:00
timedoutandEllis Git e1eea45fd6 style: Fix EOL 2026-07-12 02:11:50 +00:00
timedoutandEllis Git 7cb53f41d6 chore: Add newsfrag 2026-07-12 02:11:50 +00:00
timedoutandEllis Git fbf81fcdeb fix: Opportunistically re-use room format rules when parsing incoming PDUs 2026-07-12 02:11:50 +00:00
timedoutandEllis Git 87030a3a22 fix: Ensure PDU returned by create_hash_and_sign_event is itself signed 2026-07-12 02:11:50 +00:00
timedoutandEllis Git 4f509fa113 fix: Convert room summary to federation format when sending invites 2026-07-12 02:11:50 +00:00
timedoutandEllis Git aa7ed885c0 fix: Check correct field name when determining event type 2026-07-12 02:11:50 +00:00
timedoutandEllis Git d5e6e617d1 fix: Remove redundant banned room server checks
These don't make sense here anyway
2026-07-12 02:11:50 +00:00
timedoutandEllis Git 1f7680ad5f feat: Give send_join the invite treatment 2026-07-12 02:11:50 +00:00
timedoutandEllis Git 0c37a542d4 feat: Give send_leave the invite treatment 2026-07-12 02:11:50 +00:00
timedoutandEllis Git efd07da0d7 feat: Give send_knock the invite treatment 2026-07-12 02:11:50 +00:00
timedoutandEllis Git f0590e882a fix: Only run ACL checks when we know we have live state
Prevents a potential bug where we might inadvertently reject valid invites because we have a stale state cache that blocks the sender or even ourselves

Also fixes the banned remote server room check by using the create event instead of room ID
2026-07-12 02:11:50 +00:00
timedoutandEllis Git 7225bf25ae feat: Persist create events received during the invite process 2026-07-12 02:11:50 +00:00
timedoutandEllis Git e52bb5db69 style: Move invite validation into own functions 2026-07-12 02:11:50 +00:00
timedoutandEllis Git 995726923b feat: Enforce new federation invite checks
These were introduced in spec v1.16 however we didn't implement them until now for compatibility.
2026-07-12 02:11:50 +00:00
GingerandEllis Git 556141b404 fix: Trim whitespace from OIDC client secret file 2026-07-12 02:06:48 +00:00
Henry-HilesandEllis Git 6858e1b893 fix: fallback rev to empty string
Fixes flake-compat support
2026-07-11 20:13:58 +00:00
GingerandEllis Git 07e241f616 chore: News fragment 2026-07-11 19:40:25 +00:00
GingerandEllis Git cfbe590f1c fix: Do not require an access token on 3pid token request route 2026-07-11 19:40:25 +00:00
GingerandEllis Git d9c2f04d73 fix: Correctly handle preferred_username claim 2026-07-11 19:21:33 +00:00
GingerandEllis Git ada0b3184b fix: Properly sync newly created rooms 2026-07-11 19:21:19 +00:00
Renovate BotandEllis Git 1d6ad485f6 chore(deps): update ruma digest to 9b6a2e7 2026-07-11 16:29:54 +00:00
timedout 9d2ce72f73 style: Remove redundant length check for dag build 2026-07-11 17:27:42 +01:00
timedout 13bcb5af13 style: Accept event id reference in pdu_format_check_1 2026-07-11 17:27:42 +01:00
timedout 5868510df1 fix: Remove redundant "correct room ID" filter 2026-07-11 17:27:42 +01:00
timedout c34d1fe76e feat: Move PDU validation out of parsing and into check 1 2026-07-11 17:27:42 +01:00
timedout 838764b0ac style: Remove needless async def for parse_incoming_pdu_with_known_room 2026-07-11 17:27:42 +01:00
timedout b51a34c1c1 fix: Address review feedback 2026-07-11 17:27:42 +01:00
timedout 2fbb779d12 fix: Invert boolean returned by policy server check 2026-07-11 17:27:42 +01:00
timedout e16a62e1ed feat: Add more debug logging to PDU handle funcs
Tracking down weird soft-fails
2026-07-11 17:27:42 +01:00
timedout 64a08262ca perf: Change signature of build_local_dag to remove refmap hacks 2026-07-11 17:27:42 +01:00
timedout 70a3905761 style: Remove duplicate pdu check 1 call in handle_incoming_pdu 2026-07-11 17:27:42 +01:00
timedout cf9179e8f6 fix: Perform checks 1-3 on auth chain PDUs 2026-07-11 17:27:42 +01:00
timedout 6b899b7f80 refactor: Move PDU checks 1, 2, and 3 into pdu_checks.rs 2026-07-11 17:27:42 +01:00
timedout ad57fa06fc style: Unify and document state_before_incoming 2026-07-11 17:27:42 +01:00
timedout ec9efbada5 fix: Don't reject auth events when they're innocent in auth chain response 2026-07-11 17:27:42 +01:00
timedout 318d2b205a style: Move PDU check functions into their own module 2026-07-11 17:27:42 +01:00
timedout 99a5f2a9fb refactor: Reduce complexity of incoming PDU handling
* Ignores any events pushed without a room ID
* Removes needless clones for PDU size checking
* Removes incorrect ACL check in incoming handler
* Fast-path handling already handled outlier events
* Remove redundant same-room checks, replace with useful ones
* Combine event rejection and persistence in a single function
* Additional safety assertions in upgrade task
* Split upgrade task into multiple subroutines for reduced cognitive complexity
* Only mutate current state and forward extremities in tandem
* Improve code documentation to better explain the logic flow
2026-07-11 17:27:42 +01:00
Renovate BotandEllis Git ace0d52074 chore(deps): Update rust crate termimad to 0.35.0 2026-07-11 15:35:53 +00:00
GingerandEllis Git 0b1e24a9ad fix: Add admin command to ensure all appservice puppets are active 2026-07-11 14:55:43 +00:00
GingerandEllis Git f55207d5c7 fix: Stop appservice users from being wrongly deactivated during migration 2026-07-11 14:55:43 +00:00
Renovate Bot 3f2fae4ca7 chore(deps): update dependency typescript to v7 2026-07-11 05:04:52 +00:00
theS1LV3RandEllis Git 081f865705 chore: Use CARGO_LOG=debug for cargo build 2026-07-11 01:31:07 +00:00
theS1LV3RandEllis Git 0d6608a2f8 fix: Ensure docker builds stop on failed compilation 2026-07-11 01:31:07 +00:00
renovateandEllis Git abf96140b2 chore(Nix): Updated flake hashes 2026-07-11 01:30:41 +00:00
Renovate BotandEllis Git 35fb56aafd chore(deps): update rust to v1.97.0 2026-07-11 01:30:41 +00:00
Henry-HilesandEllis Git 03bde0e5fa feat: add commit info to Nix build 2026-07-10 19:04:01 +00:00
Jade Ellis 5e306f160a chore: Admin room announcement 2026-07-10 19:04:03 +01:00
Ginger fb2eedd5fd chore: Update changelog again 2026-07-10 13:09:49 -04:00
Ginger a76c5c95aa chore: Release 2026-07-10 13:01:48 -04:00
Ginger 7f83ec3883 chore: Update changelog 2026-07-10 12:59:50 -04:00
GingerandEllis Git 9b14ab4c0b fix: Parse OIDC discovery URL more leniently 2026-07-10 16:52:04 +00:00
timedout b915692ae1 fix: Remove broken federated invite revoke handling
It turns out it never worked in the first place :D
2026-07-10 16:32:38 +01:00
theS1LV3RandEllis Git bed80964fd chore: Update changelog 2026-07-10 13:44:59 +00:00
theS1LV3RandEllis Git bc7e3185ef chore(deps): Update to resolvematrix 1.1.0 2026-07-10 13:44:59 +00:00
theS1LV3RandEllis Git 7082914d7d chore: Update resolvematrix dependency rev hash 2026-07-10 13:44:59 +00:00
theS1LV3RandEllis Git 8fb14f666f chore: Add changelog 2026-07-10 13:44:59 +00:00
theS1LV3RandEllis Git 8b6a176901 fix: Use correct DNS caching for SNI to function properly
This removes the old (broken) DNS caching method for reqwest, and replaces
it with resolvematrix' `MatrixDnsResolver`, as it correctly implements
caching and overrides for correct SNI behavior.
2026-07-10 13:44:59 +00:00
Henry-Hiles 2e1015846d fix: Add missing whitespace between feature flags in nix build command 2026-07-09 22:16:28 -04:00
Jade Ellis 5d2e840859 feat: Enable direct_tls and more logging by default
Disables additional logging on maxperf builds
2026-07-09 22:44:26 +01:00
Ginger 2fabf18451 style: Fix comment 2026-07-09 17:36:56 -04:00
Ginger 63a26141f8 fix: Make depth cap a bit more thorough 2026-07-09 17:33:13 -04:00
Ginger cc1f667c2b fix: Cap max_depth to 50 in hierarchy endpoint 2026-07-09 17:22:36 -04:00
Ginger 89dcda4c3c feat: Kill roomsynctoken_shortstatehash dead 2026-07-09 16:43:10 -04:00
timedoutandEllis Git 62730e861a style: Use string literals instead of u8s 2026-07-09 19:40:11 +00:00
timedoutandEllis Git 68d018bd37 style: Use string literals instead of u8s 2026-07-09 19:40:11 +00:00
timedoutandEllis Git 959370169a chore: Rename changelog file 2026-07-09 19:40:11 +00:00
timedoutandEllis Git f71f6e922c feat: Allow configuring the desired client IP source 2026-07-09 19:40:11 +00:00
theS1LV3RandEllis Git ce185aff13 docs: Update docs to match new resolver query command 2026-07-09 19:21:59 +00:00
134 changed files with 2615 additions and 1963 deletions
+1 -1
View File
@@ -71,7 +71,7 @@ runs:
- name: Install timelord-cli and git-warp-time - name: Install timelord-cli and git-warp-time
if: steps.check-binaries.outputs.need-install == 'true' 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: with:
tool: git-warp-time,timelord-cli@3.0.1 tool: git-warp-time,timelord-cli@3.0.1
+1 -1
View File
@@ -65,7 +65,7 @@ jobs:
path: binaries path: binaries
merge-multiple: true merge-multiple: true
- name: Create Release and Upload - 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: with:
draft: true draft: true
files: binaries/* files: binaries/*
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
if: steps.runner-env.outputs.node_major == '' || steps.runner-env.outputs.node_major < '20' 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: with:
node-version: 22 node-version: 22
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
steps: steps:
- name: 📦 Setup Node.js - name: 📦 Setup Node.js
uses: https://github.com/actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 uses: https://github.com/actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with: with:
node-version: "22" node-version: "22"
+3 -1
View File
@@ -74,6 +74,7 @@ jobs:
CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }} CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }}
TARGET_CPU= TARGET_CPU=
RUST_PROFILE=release RUST_PROFILE=release
CARGO_FEATURES=default
platforms: ${{ matrix.platform }} platforms: ${{ matrix.platform }}
labels: ${{ steps.prepare.outputs.metadata_labels }} labels: ${{ steps.prepare.outputs.metadata_labels }}
annotations: ${{ steps.prepare.outputs.metadata_annotations }} annotations: ${{ steps.prepare.outputs.metadata_annotations }}
@@ -161,6 +162,7 @@ jobs:
CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }} CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }}
TARGET_CPU=${{ matrix.target_cpu }} TARGET_CPU=${{ matrix.target_cpu }}
RUST_PROFILE=release-max-perf RUST_PROFILE=release-max-perf
CARGO_FEATURES=default,release_max_log_level
platforms: ${{ matrix.platform }} platforms: ${{ matrix.platform }}
labels: ${{ steps.prepare.outputs.metadata_labels }} labels: ${{ steps.prepare.outputs.metadata_labels }}
annotations: ${{ steps.prepare.outputs.metadata_annotations }} annotations: ${{ steps.prepare.outputs.metadata_annotations }}
@@ -216,7 +218,7 @@ jobs:
path: binaries path: binaries
merge-multiple: true merge-multiple: true
- name: Create Release and Upload - 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: with:
draft: true draft: true
files: binaries/* files: binaries/*
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
name: Renovate name: Renovate
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: 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 options: --tmpfs /tmp:exec
steps: steps:
- name: Checkout - name: Checkout
+90
View File
@@ -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) # Continuwuity 0.5.8 (2026-04-24)
## Features ## Features
+2 -2
View File
@@ -26,7 +26,7 @@ ### Pre-commit Checks
```bash ```bash
# Install prek using cargo-binstall # Install prek using cargo-binstall https://github.com/cargo-bins/cargo-binstall
cargo binstall prek cargo binstall prek
# Install git hooks to run checks automatically # Install git hooks to run checks automatically
@@ -155,7 +155,7 @@ ### Creating pull requests
Before submitting a pull request, please ensure: Before submitting a pull request, please ensure:
1. Your code passes all CI checks (formatting, linting, typo detection, etc.) 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 3. Your commit messages follow the conventional commits format
4. Tests are added for new functionality 4. Tests are added for new functionality
5. Documentation is updated if needed 5. Documentation is updated if needed
Generated
+150 -247
View File
File diff suppressed because it is too large Load Diff
+4 -10
View File
@@ -12,7 +12,7 @@ license = "Apache-2.0"
# See also `rust-toolchain.toml` # See also `rust-toolchain.toml`
readme = "README.md" readme = "README.md"
repository = "https://forgejo.ellis.link/continuwuation/continuwuity" repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
version = "26.6.0-rc.1" version = "26.6.2"
[workspace.metadata.crane] [workspace.metadata.crane]
name = "conduwuit" name = "conduwuit"
@@ -141,12 +141,6 @@ features = [
version = "0.23.25" version = "0.23.25"
default-features = false default-features = false
[workspace.dependencies.aws-lc-sys]
version = "0.41.0"
[workspace.dependencies.aws-lc-rs]
version = "1.17.0"
[workspace.dependencies.reqwest] [workspace.dependencies.reqwest]
version = "0.13.2" version = "0.13.2"
default-features = false default-features = false
@@ -350,7 +344,7 @@ version = "1.1.1"
[workspace.dependencies.ruma] [workspace.dependencies.ruma]
# version = "0.14.1" # version = "0.14.1"
git = "https://github.com/ruma/ruma.git" git = "https://github.com/ruma/ruma.git"
rev = "fca1dbc060f730c1aaf84c9c2fb1e8a587455be8" rev = "9b6a2e7323649af926e4b5363d87239ee4247f4a"
features = [ features = [
"appservice-api-c", "appservice-api-c",
"client-api", "client-api",
@@ -502,7 +496,7 @@ version = "0.4.9"
default-features = false default-features = false
[workspace.dependencies.termimad] [workspace.dependencies.termimad]
version = "0.34.0" version = "0.35.0"
default-features = false default-features = false
[workspace.dependencies.checked_ops] [workspace.dependencies.checked_ops]
@@ -570,7 +564,7 @@ features = ["std"]
version = "0.3.0" version = "0.3.0"
[workspace.dependencies.resolvematrix] [workspace.dependencies.resolvematrix]
version = "1.0.0" version = "1.2.0"
[workspace.dependencies.serde_urlencoded] [workspace.dependencies.serde_urlencoded]
version = "0.7.1" version = "0.7.1"
-1
View File
@@ -1 +0,0 @@
Appservice device management as outlined in MSC4190 (part of Matrix 1.17) is now fully supported. Contributed by @ginger.
-1
View File
@@ -1 +0,0 @@
Improved invite and join reliability in clients using legacy sync. Contributed by @ginger
+1
View File
@@ -0,0 +1 @@
Appservices are now properly able to create devices for E2EE.
-1
View File
@@ -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
View File
@@ -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
View File
@@ -1 +0,0 @@
Switched from Continuwuity's fork of Ruma back to upstream Ruma. Contributed by @ginger.
-1
View File
@@ -1 +0,0 @@
Users may now be forbidden from deactivating their own accounts with the new `allow_deactivation` config option. Contributed by @ginger.
-1
View File
@@ -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.
+1
View File
@@ -0,0 +1 @@
Fixed newly created rooms failing to sync properly in clients using legacy sync.
-1
View File
@@ -1 +0,0 @@
Added support for authenticating clients using the new OAuth 2.0 login API. Contributed by @ginger.
-1
View File
@@ -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
View File
@@ -1 +0,0 @@
Devices which set their presence as "offline" will no longer be considered for presence updates. Contributed by @timedout.
-1
View File
@@ -1 +0,0 @@
Removed support for guest user registration, a little-used and deprecated approach to room previews.
-1
View File
@@ -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.
+1
View File
@@ -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
View File
@@ -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
View File
@@ -1 +0,0 @@
Support for server-side blurhashing (part of MSC2448) has been removed.
-1
View File
@@ -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
View File
@@ -1 +0,0 @@
Add performance tuning documentation. Contributed by @stratself.
-1
View File
@@ -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
View File
@@ -1 +0,0 @@
Explain accessing Continuwuity's server console when deployed via Docker.
-1
View File
@@ -1 +0,0 @@
Added config option for default room ACLs. Contributed by @eve.
-1
View File
@@ -1 +0,0 @@
Removed support for LDAP.
-1
View File
@@ -1 +0,0 @@
Clarified in the config that `max_request_size` affects federated media as well.
-1
View File
@@ -1 +0,0 @@
Added support for fallback encryption keys.
-1
View File
@@ -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
View File
@@ -1 +0,0 @@
Add `!admin users reject-all-invites` to clean invite spam
-1
View File
@@ -1 +0,0 @@
fix `!admin query account-data account-data-get` not returning the content
-9
View File
@@ -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
View File
@@ -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
View File
@@ -1 +0,0 @@
Added example configuration using caddy-docker-proxy in the livekit setup section of the docs. Contributed by @Cease
-1
View File
@@ -1 +0,0 @@
Fixed admin commands being ignored when they had leading whitespace before admin commands. Contributed by @kitvonsnookerz.
-1
View File
@@ -1 +0,0 @@
Fixed several bugs in the `POST /_matrix/client/v3/rooms/{roomId}/upgrade` endpoint. Contributed by @nex.
-1
View File
@@ -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.
-2
View File
@@ -1,2 +0,0 @@
Improved the performance and reliability of fetching missing events, improving network partition recovery. Contributed
by @nex.
-1
View File
@@ -1 +0,0 @@
Remove support for MSC4373, as the MSC is now closed. Contributed by @vel.
-1
View File
@@ -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
View File
@@ -1 +0,0 @@
Added support for MSC4380 invite blocking, which has become part of the Matrix specification in v1.18. Contributed by @nex.
-1
View File
@@ -1 +0,0 @@
Added `!admin debug get-state-at` command
-1
View File
@@ -1 +0,0 @@
Updated deployment docs to account for new RPM package availability across more distros. Contributed by @julian45.
-1
View File
@@ -1 +0,0 @@
Move resolver service into client service, and remove deprecated and unused code. Contributed by @s1lv3r.
+1
View File
@@ -0,0 +1 @@
Fixed the deeplink redirect for deleting devices. Contributed by @koen
+1
View File
@@ -0,0 +1 @@
Fix status code for oauth registration. Contributed by @n00byking
+1
View File
@@ -0,0 +1 @@
Exempt m.room.create from auth_events check. Contributed by @eleboucher
-1
View File
@@ -1 +0,0 @@
Added support for linking an external identity provider with OIDC. Contributed by @ginger.
-1
View File
@@ -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.
+21
View File
@@ -290,6 +290,27 @@
# #
#ip_lookup_strategy = 5 #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. # Max request size for file uploads in bytes. Defaults to 20MB.
# Also limits incoming federated media. # Also limits incoming federated media.
# #
+3 -1
View File
@@ -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=/usr/local/cargo/git/db \
--mount=type=cache,target=/app/target,id=continuwuity-cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-${RUST_PROFILE} \ --mount=type=cache,target=/app/target,id=continuwuity-cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-${RUST_PROFILE} \
bash <<'EOF' bash <<'EOF'
set -euo pipefail
set -o allexport set -o allexport
set -o xtrace set -o xtrace
. /etc/environment . /etc/environment
@@ -191,7 +192,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
jq -r ".target_directory")) jq -r ".target_directory"))
mkdir /out/sbin mkdir /out/sbin
PACKAGE=conduwuit 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} \ --no-default-features --features ${CARGO_FEATURES} \
-p $PACKAGE; -p $PACKAGE;
BINARIES=($(cargo metadata --no-deps --format-version 1 | \ BINARIES=($(cargo metadata --no-deps --format-version 1 | \
@@ -207,6 +208,7 @@ EOF
RUN --mount=type=cache,target=/usr/local/cargo/registry \ RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git/db \ --mount=type=cache,target=/usr/local/cargo/git/db \
bash <<'EOF' bash <<'EOF'
set -euo pipefail
set -o xtrace set -o xtrace
mkdir /out/sbom mkdir /out/sbom
typeset -A PACKAGES typeset -A PACKAGES
@@ -6,10 +6,10 @@
"message": "Welcome to Continuwuity! Important announcements about the project will appear here." "message": "Welcome to Continuwuity! Important announcements about the project will appear here."
}, },
{ {
"id": 14, "id": 16,
"mention_room": true, "mention_room": true,
"date": "2026-06-20", "date": "2026-07-13",
"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)." "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."
} }
] ]
} }
+4
View File
@@ -27,3 +27,7 @@ ## `!admin appservices show-appservice-config`
## `!admin appservices list-registered` ## `!admin appservices list-registered`
List all the currently registered appservices 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
+4
View File
@@ -146,6 +146,10 @@ ## `!admin debug send-test-email`
Send a test email to the invoking admin's email address 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` ## `!admin debug tester`
Developer test stubs Developer test stubs
+2 -6
View File
@@ -104,13 +104,9 @@ ## `!admin query resolver`
resolver service resolver service
### `!admin query resolver destinations-cache` ### `!admin query resolver cache`
Query the destinations cache Query the destinations or overrides cache, depending on the value of the `overrides` flag (default false)
### `!admin query resolver overrides-cache`
Query the overrides cache
### `!admin query resolver flush-cache` ### `!admin query resolver flush-cache`
Generated
+21 -21
View File
@@ -3,11 +3,11 @@
"advisory-db": { "advisory-db": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1781566179, "lastModified": 1783840254,
"narHash": "sha256-Tqv8I586fYzWpEW/Smq/JqESFa3DVVzVWsnAMtvhy/I=", "narHash": "sha256-XjyvZk0f3YiinVHmkGOotmLBAzvK+LwEJYj2QqJ5pn8=",
"owner": "rustsec", "owner": "rustsec",
"repo": "advisory-db", "repo": "advisory-db",
"rev": "74e084413d979d52d2f93b1d93b1ab7b9ee648f5", "rev": "6e3286f4efa8c142fb33e5ea4342c8db6693cf34",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -18,11 +18,11 @@
}, },
"crane": { "crane": {
"locked": { "locked": {
"lastModified": 1780532242, "lastModified": 1783203018,
"narHash": "sha256-D+BsdpxmtUwtqGoY0IXPhHgTlmqgcZKCEo1oMyn7ep0=", "narHash": "sha256-G6R9IT/xwFuu+CYBWDUAok6AdC4ERC4ZfPPFtEpxnZE=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "59a82a1222dd3b2080b5cc52a1a2e8d5f1b77f37", "rev": "80db5bdc391be8a1794f6d8a2d56e3a84ebcede2",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -39,11 +39,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1781527054, "lastModified": 1783844668,
"narHash": "sha256-1fX9ev2Fh5QoKQ41G9dYutjo5j/jywu6tZse5Eb1Ck4=", "narHash": "sha256-3MOpw4y3reoErRLvFBDz0t9aG6FWXUj7leKvUns5eQA=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "8c2e51dffefc040a21975da7abf6f252c8c9b783", "rev": "4e49ef62eedaa5c149d62b63b3f53844e1cc45d7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -74,11 +74,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1778716662, "lastModified": 1782949081,
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=", "narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb", "rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -89,11 +89,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1781074563, "lastModified": 1783776592,
"narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=", "narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "9ae611a455b90cf061d8f332b977e387bda8e1ca", "rev": "e7a3ca8092b61ff85b6a45bf863ea2b2d6a661b3",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -105,11 +105,11 @@
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1777168982, "lastModified": 1782614948,
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=", "narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixpkgs.lib", "repo": "nixpkgs.lib",
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14", "rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -132,11 +132,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1781453968, "lastModified": 1783779020,
"narHash": "sha256-+V3nK4pCngbmgyVGXY6Kkrlevp4ocPkJJLf2aqwkDNA=", "narHash": "sha256-vpm418WZa9l1KUl6HRs+Ga+SIfE+D2/sV4olwTlilYc=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "cc272809a173c2c11d0e479d639c811c1eacf049", "rev": "5be5e89cf0145d73a85c805726821b4adfc3af48",
"type": "github" "type": "github"
}, },
"original": { "original": {
+2
View File
@@ -43,6 +43,8 @@ let
env = { env = {
CARGO_PROFILE = profile; CARGO_PROFILE = profile;
RUSTFLAGS = rustflags; 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) { // (lib.optionalAttrs (rocksdb != null) {
ROCKSDB_INCLUDE_DIR = "${rocksdb}/include"; ROCKSDB_INCLUDE_DIR = "${rocksdb}/include";
+5 -2
View File
@@ -36,14 +36,16 @@
self'.packages.stable-toolchain self'.packages.stable-toolchain
); );
# extra features via `cargoExtraArgs`
cargoExtraArgs = "-F http3";
default = pkgs.callPackage ./continuwuity.nix { default = pkgs.callPackage ./continuwuity.nix {
inherit self craneLib; inherit self craneLib;
liburing = (if isStatic then pkgs.pkgsStatic else pkgs).liburing; liburing = (if isStatic then pkgs.pkgsStatic else pkgs).liburing;
rocksdb = if isStatic then null else self'.packages.rocksdb; rocksdb = if isStatic then null else self'.packages.rocksdb;
# extra features via `cargoExtraArgs` inherit cargoExtraArgs;
cargoExtraArgs = "-F http3";
# extra RUSTFLAGS via `rustflags` # extra RUSTFLAGS via `rustflags`
# the stuff below is required for http3 # the stuff below is required for http3
rustflags = "--cfg reqwest_unstable"; rustflags = "--cfg reqwest_unstable";
@@ -55,6 +57,7 @@
max-perf = default.override { max-perf = default.override {
# compiles slower but with more thorough optimizations # compiles slower but with more thorough optimizations
profile = "release-max-perf"; profile = "release-max-perf";
cargoExtraArgs = "${cargoExtraArgs} -F release_max_log_level";
}; };
max-perf-haswell = max-perf.override { max-perf-haswell = max-perf.override {
+1 -1
View File
@@ -12,7 +12,7 @@
target: target:
target.fromToolchainName { target.fromToolchainName {
name = (lib.importTOML "${inputs.self}/rust-toolchain.toml").toolchain.channel; name = (lib.importTOML "${inputs.self}/rust-toolchain.toml").toolchain.channel;
sha256 = "sha256-h+t2xTBz5yt2YIO+1VMIIGlCU7gyp2LYOFvaV1nwOXU="; sha256 = "sha256-OATSZm98Es5kIFuqaba+UvkQtFsVgJEBMmS+t6od5/U=";
}; };
in in
{ {
+416 -55
View File
@@ -12,7 +12,7 @@
"@rspress/core": "^2.0.0", "@rspress/core": "^2.0.0",
"@rspress/plugin-client-redirects": "^2.0.0", "@rspress/plugin-client-redirects": "^2.0.0",
"@rspress/plugin-sitemap": "^2.0.0", "@rspress/plugin-sitemap": "^2.0.0",
"typescript": "^6.0.0" "typescript": "^7.0.0"
} }
}, },
"node_modules/@emnapi/core": { "node_modules/@emnapi/core": {
@@ -510,14 +510,14 @@
} }
}, },
"node_modules/@shikijs/core": { "node_modules/@shikijs/core": {
"version": "4.2.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.2.0.tgz", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.3.1.tgz",
"integrity": "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ==", "integrity": "sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/primitive": "4.2.0", "@shikijs/primitive": "4.3.1",
"@shikijs/types": "4.2.0", "@shikijs/types": "4.3.1",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4", "@types/hast": "^3.0.4",
"hast-util-to-html": "^9.0.5" "hast-util-to-html": "^9.0.5"
@@ -527,13 +527,13 @@
} }
}, },
"node_modules/@shikijs/engine-javascript": { "node_modules/@shikijs/engine-javascript": {
"version": "4.2.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.2.0.tgz", "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.3.1.tgz",
"integrity": "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og==", "integrity": "sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.2.0", "@shikijs/types": "4.3.1",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"oniguruma-to-es": "^4.3.6" "oniguruma-to-es": "^4.3.6"
}, },
@@ -542,13 +542,13 @@
} }
}, },
"node_modules/@shikijs/engine-oniguruma": { "node_modules/@shikijs/engine-oniguruma": {
"version": "4.2.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.2.0.tgz", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.3.1.tgz",
"integrity": "sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g==", "integrity": "sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.2.0", "@shikijs/types": "4.3.1",
"@shikijs/vscode-textmate": "^10.0.2" "@shikijs/vscode-textmate": "^10.0.2"
}, },
"engines": { "engines": {
@@ -556,26 +556,26 @@
} }
}, },
"node_modules/@shikijs/langs": { "node_modules/@shikijs/langs": {
"version": "4.2.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.2.0.tgz", "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.3.1.tgz",
"integrity": "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ==", "integrity": "sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.2.0" "@shikijs/types": "4.3.1"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/@shikijs/primitive": { "node_modules/@shikijs/primitive": {
"version": "4.2.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.2.0.tgz", "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.3.1.tgz",
"integrity": "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA==", "integrity": "sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.2.0", "@shikijs/types": "4.3.1",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4" "@types/hast": "^3.0.4"
}, },
@@ -584,16 +584,16 @@
} }
}, },
"node_modules/@shikijs/rehype": { "node_modules/@shikijs/rehype": {
"version": "4.2.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.2.0.tgz", "resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.3.1.tgz",
"integrity": "sha512-ST3EWye/dwF1gWskczJNBnwFtDzEQ9ceytXZtyc/GfwR5V0qJrkoSGZO55O3SAKDDsXkTDcsfwd9pVe7ROlAHg==", "integrity": "sha512-oshrlfUF3VPUJfnp5K1lLwsS/SRBKrIxONpdWebSKZXdBE3UsZnxgqpvRUA8UsofS7vmjFOCAHIT71ECbmOxTw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.2.0", "@shikijs/types": "4.3.1",
"@types/hast": "^3.0.4", "@types/hast": "^3.0.4",
"hast-util-to-string": "^3.0.1", "hast-util-to-string": "^3.0.1",
"shiki": "4.2.0", "shiki": "4.3.1",
"unified": "^11.0.5", "unified": "^11.0.5",
"unist-util-visit": "^5.1.0" "unist-util-visit": "^5.1.0"
}, },
@@ -602,22 +602,22 @@
} }
}, },
"node_modules/@shikijs/themes": { "node_modules/@shikijs/themes": {
"version": "4.2.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.2.0.tgz", "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.3.1.tgz",
"integrity": "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w==", "integrity": "sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.2.0" "@shikijs/types": "4.3.1"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/@shikijs/types": { "node_modules/@shikijs/types": {
"version": "4.2.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz", "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.3.1.tgz",
"integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==", "integrity": "sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -684,9 +684,9 @@
} }
}, },
"node_modules/@types/hast": { "node_modules/@types/hast": {
"version": "3.0.4", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz",
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -735,10 +735,350 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/@ungap/structured-clone": {
"version": "1.3.1", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz",
"integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@@ -3249,18 +3589,18 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/shiki": { "node_modules/shiki": {
"version": "4.2.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.2.0.tgz", "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.3.1.tgz",
"integrity": "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ==", "integrity": "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/core": "4.2.0", "@shikijs/core": "4.3.1",
"@shikijs/engine-javascript": "4.2.0", "@shikijs/engine-javascript": "4.3.1",
"@shikijs/engine-oniguruma": "4.2.0", "@shikijs/engine-oniguruma": "4.3.1",
"@shikijs/langs": "4.2.0", "@shikijs/langs": "4.3.1",
"@shikijs/themes": "4.2.0", "@shikijs/themes": "4.3.1",
"@shikijs/types": "4.2.0", "@shikijs/types": "4.3.1",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4" "@types/hast": "^3.0.4"
}, },
@@ -3361,17 +3701,38 @@
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "6.0.3", "version": "7.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc"
"tsserver": "bin/tsserver"
}, },
"engines": { "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": { "node_modules/unhead": {
+1 -1
View File
@@ -25,6 +25,6 @@
"@rspress/core": "^2.0.0", "@rspress/core": "^2.0.0",
"@rspress/plugin-client-redirects": "^2.0.0", "@rspress/plugin-client-redirects": "^2.0.0",
"@rspress/plugin-sitemap": "^2.0.0", "@rspress/plugin-sitemap": "^2.0.0",
"typescript": "^6.0.0" "typescript": "^7.0.0"
} }
} }
+1 -1
View File
@@ -10,7 +10,7 @@
[toolchain] [toolchain]
profile = "minimal" profile = "minimal"
channel = "1.96.1" channel = "1.97.0"
components = [ components = [
# For rust-analyzer # For rust-analyzer
"rust-src", "rust-src",
+25
View File
@@ -73,4 +73,29 @@ pub(super) async fn list_registered(&self) -> Result {
}) })
.await .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(())
}
} }
+6
View File
@@ -37,4 +37,10 @@ pub enum AppserviceCommand {
/// List all the currently registered appservices /// List all the currently registered appservices
#[clap(alias("list"))] #[clap(alias("list"))]
ListRegistered, 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,
} }
+4 -4
View File
@@ -486,7 +486,7 @@ pub(super) async fn get_remote_pdu(
.services .services
.rooms .rooms
.event_handler .event_handler
.parse_incoming_pdu(&response.pdu) .parse_incoming_pdu(&response.pdu, None)
.boxed() .boxed()
.await; .await;
@@ -832,7 +832,7 @@ pub(super) async fn force_set_room_state_from_server(
.services .services
.rooms .rooms
.event_handler .event_handler
.parse_incoming_pdu(&pdu) .parse_incoming_pdu(&pdu, Some(&room_version_rules))
.await .await
{ {
| Ok(t) => t, | Ok(t) => t,
@@ -1014,10 +1014,10 @@ pub(super) async fn resolve_true_destination(
let resolver: &MatrixResolver = if no_cache { let resolver: &MatrixResolver = if no_cache {
&MatrixResolverBuilder::new() &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) .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()? .build()?
} else { } else {
&self.services.client.resolver &self.services.client.matrix_resolver
}; };
let actual = resolver.resolve_server(server_name.as_str()).await?; let actual = resolver.resolve_server(server_name.as_str()).await?;
+1 -1
View File
@@ -54,7 +54,7 @@ pub(super) async fn fetch_support_well_known(&self, server_name: OwnedServerName
let response = self let response = self
.services .services
.client .client
.default .external_resource
.get(format!("https://{server_name}/.well-known/matrix/support")) .get(format!("https://{server_name}/.well-known/matrix/support"))
.send() .send()
.await?; .await?;
+4 -4
View File
@@ -47,7 +47,7 @@ async fn cache(
writeln!(self, "| Server Name | Destination | SNI | Override | Step | Expires |").await?; writeln!(self, "| Server Name | Destination | SNI | Override | Step | Expires |").await?;
writeln!(self, "| ----------- | ----------- | --- | -------- | ---- | ------- |").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 { for (host, entry) in entries {
if let Some(ors) = overrides if let Some(ors) = overrides
@@ -81,13 +81,13 @@ async fn cache(
async fn flush_cache(&self, name: Option<OwnedServerName>, all: bool) -> Result { async fn flush_cache(&self, name: Option<OwnedServerName>, all: bool) -> Result {
if all { if all {
self.services.client.resolver.clear_cache(); self.services.client.matrix_resolver.clear_cache();
self.services.client.dns.clear_cache(); self.services.client.dns_resolver.clear_cache();
writeln!(self, "Resolver and DNS caches cleared!").await writeln!(self, "Resolver and DNS caches cleared!").await
} else if let Some(name) = name { } else if let Some(name) = name {
self.services self.services
.client .client
.resolver .matrix_resolver
.remove_cache_entry(name.as_str()); .remove_cache_entry(name.as_str());
self.write_str(&format!("Cleared {name} from resolver caches!")) self.write_str(&format!("Cleared {name} from resolver caches!"))
.await .await
+1 -1
View File
@@ -26,7 +26,7 @@
}; };
use service::{mailer::messages, uiaa::UiaaInitiator, users::HashedPassword}; 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}; use crate::{Ruma, router::ClientIdentity};
pub(crate) mod register; pub(crate) mod register;
+8 -6
View File
@@ -20,9 +20,12 @@
assign, assign,
}; };
use serde_json::value::RawValue; 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; use crate::Ruma;
/// # `POST /_matrix/client/v3/register` /// # `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()); .unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
// Generate new token for the device // 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 // Create device for this account
services services
@@ -127,8 +130,7 @@ pub(crate) async fn register_route(
.create_device( .create_device(
&user_id, &user_id,
&device_id, &device_id,
&new_token, Some(new_token.clone()),
None,
body.initial_device_display_name.clone(), body.initial_device_display_name.clone(),
Some(client.to_string()), 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"); debug_info!(%user_id, ?device, "New account created via legacy registration");
Ok(assign!(register::v3::Response::new(user_id), { Ok(assign!(register::v3::Response::new(user_id), {
access_token: token, access_token: token.map(DeviceToken::into_token),
device_id: device, device_id: device,
refresh_token: None, refresh_token: None,
expires_in: None, expires_in: None,
+2 -4
View File
@@ -57,13 +57,11 @@ pub(crate) async fn request_3pid_management_token_via_email_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<request_3pid_management_token_via_email::v3::Request>, body: Ruma<request_3pid_management_token_via_email::v3::Request>,
) -> Result<request_3pid_management_token_via_email::v3::Response> { ) -> 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 let sender_user = body
.identity .identity
.as_ref() .as_ref()
.map(ClientIdentity::expect_sender_user) .map(ClientIdentity::expect_sender_user)
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))??; .transpose()?;
if !services.threepid.email_requirement().may_change() { if !services.threepid.email_requirement().may_change() {
return Err!(Request(Forbidden("You may not change your email address."))); 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), Mailbox::new(None, email),
|verification_link| messages::ChangeEmail { |verification_link| messages::ChangeEmail {
server_name: services.config.server_name.as_str(), server_name: services.config.server_name.as_str(),
user_id: Some(sender_user), user_id: sender_user,
verification_link, verification_link,
}, },
&body.client_secret, &body.client_secret,
+1 -8
View File
@@ -89,14 +89,7 @@ pub(crate) async fn update_device_route(
services services
.users .users
.create_device( .create_device(sender_user, &device_id, None, None, Some(client.to_string()))
sender_user,
&device_id,
&appservice.registration.as_token,
None,
None,
Some(client.to_string()),
)
.await?; .await?;
return Ok(update_device::v3::Response::new()); return Ok(update_device::v3::Response::new());
+4 -3
View File
@@ -3,7 +3,7 @@
use conduwuit::{ use conduwuit::{
Err, Result, debug_error, err, info, Err, Result, debug_error, err, info,
matrix::{event::gen_event_id_canonical_json, pdu::PartialPdu}, matrix::{event::gen_event_id_canonical_json, pdu::PartialPdu},
warn, trace, warn,
}; };
use futures::FutureExt; use futures::FutureExt;
use ruma::{ use ruma::{
@@ -166,7 +166,7 @@ pub(crate) async fn invite_helper(
let invite_room_state = services let invite_room_state = services
.rooms .rooms
.state .state
.summary_stripped(&pdu, room_id, recipient_user) .summary_stripped(&pdu, room_id, recipient_user, true)
.await; .await;
drop(state_lock); drop(state_lock);
@@ -193,6 +193,7 @@ pub(crate) async fn invite_helper(
.await .await
.ok(); .ok();
trace!(?request, "Sending invite");
let response = services let response = services
.sending .sending
.send_federation_request(recipient_user.server_name(), request) .send_federation_request(recipient_user.server_name(), request)
@@ -221,7 +222,7 @@ pub(crate) async fn invite_helper(
let pdu_id = services let pdu_id = services
.rooms .rooms
.event_handler .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() .boxed()
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
+1 -1
View File
@@ -24,5 +24,5 @@ pub(crate) async fn register_client_route(
.await .await
.map_err(|err| (StatusCode::BAD_REQUEST, Json(err)).into_response())?; .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())
} }
+8 -8
View File
@@ -29,8 +29,9 @@
}, },
assign, assign,
}; };
use service::users::DeviceToken;
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH}; use super::DEVICE_ID_LENGTH;
use crate::Ruma; use crate::Ruma;
/// # `GET /_matrix/client/v3/login` /// # `GET /_matrix/client/v3/login`
@@ -196,8 +197,8 @@ pub(crate) async fn login_route(
.clone() .clone()
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into()); .unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
// Generate a new token for the device (ensuring no collisions) // Generate a new token for the device
let token = services.users.generate_unique_token().await; let token = DeviceToken::new_random();
// Determine if device_id was provided and exists in the db for this user // Determine if device_id was provided and exists in the db for this user
let device_exists = if body.device_id.is_some() { let device_exists = if body.device_id.is_some() {
@@ -213,7 +214,7 @@ pub(crate) async fn login_route(
if device_exists { if device_exists {
services services
.users .users
.set_token(&user_id, &device_id, &token, None) .set_token(&user_id, &device_id, token.clone())
.await?; .await?;
} else { } else {
services services
@@ -221,8 +222,7 @@ pub(crate) async fn login_route(
.create_device( .create_device(
&user_id, &user_id,
&device_id, &device_id,
&token, Some(token.clone()),
None,
body.initial_device_display_name.clone(), body.initial_device_display_name.clone(),
Some(client.to_string()), Some(client.to_string()),
) )
@@ -241,7 +241,7 @@ pub(crate) async fn login_route(
info!("{user_id} logged in"); info!("{user_id} logged in");
#[allow(deprecated)] #[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, well_known: client_discovery_info,
expires_in: None, expires_in: None,
home_server: Some(services.config.server_name.clone()), 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) .authenticate_password(&body.auth, sender_user, body.identity.sender_device(), None)
.await?; .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); let expires_in = services.users.create_login_token(sender_user, &login_token);
Ok(get_login_token::v1::Response::new( Ok(get_login_token::v1::Response::new(
+5
View File
@@ -381,6 +381,10 @@ async fn fetch_shortstatehashes(
| Some((_, pdu_after_last_sync_end)) => { | Some((_, pdu_after_last_sync_end)) => {
trace!(?pdu_after_last_sync_end.event_id, "pdu at last sync end"); trace!(?pdu_after_last_sync_end.event_id, "pdu at last sync end");
if pdu_after_last_sync_end.kind == RoomCreate {
// Room create events have no previous state
None
} else {
Some( Some(
services services
.rooms .rooms
@@ -391,6 +395,7 @@ async fn fetch_shortstatehashes(
err!("Last sync end PDU has no shortstatehash: {err}") err!("Last sync end PDU has no shortstatehash: {err}")
}), }),
) )
}
}, },
| None => { | None => {
// No events have been sent since the last sync, or we just joined this room // No events have been sent since the last sync, or we just joined this room
+2 -3
View File
@@ -15,10 +15,9 @@
#[derive(Deserialize)] #[derive(Deserialize)]
pub(crate) struct AuthQueryParams { pub(crate) struct AuthQueryParams {
pub(super) user_id: Option<String>, 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>, 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 /// Extractor for Ruma request structs
+8 -4
View File
@@ -219,8 +219,12 @@ async fn verify<B: AsRef<[u8]> + Sync>(
// MSC3202/MSC4190: Handle device_id masquerading for appservices. // MSC3202/MSC4190: Handle device_id masquerading for appservices.
// The device_id can be provided via `device_id` or // The device_id can be provided via `device_id` or
// `org.matrix.msc3202.device_id` query parameter. // `org.matrix.msc3202.device_id` query parameter.
let sender_device = let sender_device = if let Some(device_id) = query
if let Some(device_id) = query.device_id.as_deref().map(Into::into) { .device_id
.or(query.legacy_device_id)
.as_deref()
.map(Into::into)
{
// Verify the device exists for this user // Verify the device exists for this user
if services if services
.users .users
@@ -229,8 +233,8 @@ async fn verify<B: AsRef<[u8]> + Sync>(
.is_err() .is_err()
{ {
return Err!(Request(Forbidden( return Err!(Request(Forbidden(
"Device does not exist for user or appservice cannot masquerade as \ "Device does not exist for user or appservice cannot masquerade as this \
this device." device."
))); )));
} }
+292 -108
View File
@@ -1,23 +1,29 @@
use std::collections::{HashMap, hash_map::Entry};
use axum::extract::State; use axum::extract::State;
use axum_client_ip::ClientIp; use axum_client_ip::ClientIp;
use base64::{Engine as _, engine::general_purpose}; use base64::{Engine as _, engine::general_purpose};
use conduwuit::{ use conduwuit::{
Err, Error, PduEvent, Result, err, error, Err, Error, EventTypeExt, PduEvent, Result, debug, err, error,
matrix::{Event, event::gen_event_id}, matrix::{Event, StateKey},
utils::{self, hash::sha256}, result::FlatOk,
state_res, trace,
utils::hash::sha256,
warn, warn,
}; };
use ruma::{ use ruma::{
CanonicalJsonValue, UserId, CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedUserId, ServerName,
UserId,
api::{ api::{
error::{ErrorKind, IncompatibleRoomVersionErrorData}, error::{ErrorKind, IncompatibleRoomVersionErrorData},
federation::membership::{RawStrippedState, create_invite}, federation::membership::{RawStrippedState, create_invite},
}, },
events::room::member::{MembershipState, RoomMemberEventContent}, events::{StateEventType, room::member::MembershipState},
serde::JsonObject, 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}` /// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}`
/// ///
@@ -28,30 +34,16 @@ pub(crate) async fn create_invite_route(
ClientIp(client): ClientIp, ClientIp(client): ClientIp,
body: Ruma<create_invite::v2::Request>, body: Ruma<create_invite::v2::Request>,
) -> Result<create_invite::v2::Response> { ) -> 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) { if !services.server.supported_room_version(&body.room_version) {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData::new( ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData::new(
body.room_version.clone(), 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(); 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 if services
.moderation .moderation
.is_remote_server_forbidden(&body.identity) .is_remote_server_forbidden(&body.identity)
@@ -61,90 +53,61 @@ pub(crate) async fn create_invite_route(
body.identity, body.room_id 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) // First, validate the invite room state, so we can compare with the create
.map_err(|_| err!(Request(InvalidParam("Invite event is invalid."))))?; // event.
debug!(
// Ensure this is a membership event event_id=%body.event_id,
if signed_event room_id=%body.room_id,
.get("type") room_version=?body.room_version,
.expect("event must have a type") via=?body.via,
.as_str() "Validating invite room state for invite request"
.expect("type must be a string") );
!= "m.room.member" let (create_event_id, state) = validate_invite_state(
{ &services,
return Err!(Request(BadJson( &body.invite_room_state,
"Not allowed to send non-membership event to invite endpoint." &room_version_rules,
))); body.room_id.clone(),
}
let content: RoomMemberEventContent = serde_json::from_value(
signed_event
.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}")))))?; .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 // We can now perform the banned remote server check with the create event.
if content.membership != MembershipState::Invite { // N.B. this checks the sender field, which is technically incorrect for rooms
return Err!(Request(BadJson( // v10 and below. This usually isn't the case though so sue me
"Not allowed to send a non-invite membership event to invite endpoint." let creator = create_event_json
)));
}
// Ensure the sending user isn't a lying bozo
let sender_user = signed_event
.get("sender") .get("sender")
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
.map(UserId::parse) .map(UserId::parse)
.and_then(Result::ok) .flat_ok()
.ok_or_else(|| err!(Request(InvalidParam("Invalid sender property"))))?; .expect("must have valid sender in create event");
if services
if sender_user.server_name() != body.identity { .moderation
return Err!(Request(Forbidden("Sender's server does not match the origin server.",))); .is_remote_server_forbidden(creator.server_name())
}
// 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())
{ {
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. // And then we can validate the member event itself
services let (mut signed_event, sender_user, recipient_user) = validate_invite_membership_event(
.rooms &services,
.event_handler &body.event,
.acl_check(recipient_user.server_name(), &body.room_id) &room_version_rules,
&body.identity,
create_event_id.clone(),
body.room_id.clone(),
body.event_id.clone(),
)
.await?; .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()));
if services.rooms.metadata.is_banned(&body.room_id).await if services.rooms.metadata.is_banned(&body.room_id).await
&& !services.users.is_admin(&recipient_user).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 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."))); 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 invite_state = body.invite_room_state.clone();
let pdu = PduEvent::from_id_val(&body.event_id, signed_event.clone())
let mut event: JsonObject = serde_json::from_str(body.event.get()) .expect("must be able to create PDU object");
.map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?; invite_state.push(RawStrippedState::Pdu(serde_json::value::to_raw_value(&signed_event)?));
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"),
));
// If we are active in the room, the remote server will notify us about the // 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 // 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 // record the invited state for client /sync through update_membership(), and
// send the invite PDU to the relevant appservices. // send the invite PDU to the relevant appservices.
if !services 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 .rooms
.state_cache .outlier
.server_in_room(services.globals.server_name(), &body.room_id) .add_pdu_outlier(&create_event_id, create_event_json);
.await
{
services services
.rooms .rooms
.state_cache .state_cache
@@ -240,3 +230,197 @@ pub(crate) async fn create_invite_route(
.await, .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(())
}
-8
View File
@@ -75,14 +75,6 @@ pub(crate) async fn create_join_event_template_route(
return Err!(Request(Forbidden("Server is banned on this homeserver."))); 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 = services.rooms.state.get_room_version(&body.room_id).await?;
let room_version_rules = room_version.rules().unwrap(); let room_version_rules = room_version.rules().unwrap();
if !body.ver.contains(&room_version) { if !body.ver.contains(&room_version) {
-6
View File
@@ -58,12 +58,6 @@ pub(crate) async fn create_knock_event_template_route(
return Err!(Request(Forbidden("Server is banned on this homeserver."))); 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 = services.rooms.state.get_room_version(&body.room_id).await?;
let room_version_rules = room_version.rules().unwrap(); let room_version_rules = room_version.rules().unwrap();
+1
View File
@@ -27,6 +27,7 @@ pub(crate) async fn create_leave_event_template_route(
{ {
info!( info!(
origin = body.identity.as_str(), origin = body.identity.as_str(),
room_id = %body.room_id,
"Refusing to serve make_leave for room we aren't participating in" "Refusing to serve make_leave for room we aren't participating in"
); );
return Err!(Request(NotFound("This server is not participating in that room."))); return Err!(Request(NotFound("This server is not participating in that room.")));
+7 -15
View File
@@ -150,7 +150,7 @@ async fn process_inbound_transaction(
.pdus .pdus
.iter() .iter()
.stream() .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}")) .inspect_err(|e| warn!("Could not parse incoming PDU: {e}"))
.ready_filter_map(Result::ok); .ready_filter_map(Result::ok);
@@ -241,7 +241,7 @@ async fn handle(
pdus: impl Stream<Item = Pdu> + Send, pdus: impl Stream<Item = Pdu> + Send,
edus: impl Stream<Item = Edu> + Send, edus: impl Stream<Item = Edu> + Send,
) -> std::result::Result<ResolvedMap, TransactionError> { ) -> std::result::Result<ResolvedMap, TransactionError> {
// group pdus by room // Group PDUs by room for parallel processing
let pdus = pdus let pdus = pdus
.collect() .collect()
.map(|mut pdus: Vec<_>| { .map(|mut pdus: Vec<_>| {
@@ -252,7 +252,7 @@ async fn handle(
}) })
.await; .await;
// we can evaluate rooms concurrently // Evaluate rooms in parallel
let results: ResolvedMap = pdus let results: ResolvedMap = pdus
.into_iter() .into_iter()
.try_stream() .try_stream()
@@ -266,7 +266,7 @@ async fn handle(
.boxed() .boxed()
.await?; .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)) edus.for_each_concurrent(automatic_width(), |edu| handle_edu(services, client, origin, edu))
.boxed() .boxed()
.await; .await;
@@ -296,20 +296,12 @@ async fn handle_room(
// Try to sort PDUs by their dependencies, but fall back to arbitrary order on // 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 // failure (e.g., cycles). This is best-effort; proper ordering is the sender's
// responsibility. // responsibility.
let sorted_event_ids = if pdu_map.len() >= 2 { let sorted_event_ids = build_local_dag(&pdu_map, DagBuilderTree::PrevEvents)
let refmap = pdu_map
.iter()
.map(|(event_id, obj)| (event_id.clone(), obj))
.collect();
build_local_dag(&refmap, DagBuilderTree::PrevEvents)
.await .await
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
debug_warn!("Failed to build local DAG for room {room_id}: {e}"); debug_warn!("Failed to build local DAG for room {room_id}: {e}");
pdu_map.keys().cloned().collect() pdu_map.keys().cloned().collect()
}) });
} else {
pdu_map.keys().cloned().collect()
};
let mut results = Vec::with_capacity(sorted_event_ids.len()); let mut results = Vec::with_capacity(sorted_event_ids.len());
for event_id in sorted_event_ids { for event_id in sorted_event_ids {
let value = pdu_map let value = pdu_map
@@ -322,7 +314,7 @@ async fn handle_room(
let result = services let result = services
.rooms .rooms
.event_handler .event_handler
.handle_incoming_pdu(origin, room_id, &event_id, value.clone(), true) .handle_incoming_pdu(origin, room_id, &event_id, value, false)
.boxed() .boxed()
.await .await
.map(|_| ()); .map(|_| ());
+59 -107
View File
@@ -2,34 +2,36 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Event, Result, at, debug, err, info, Err, Event, Result, at, debug, err, info, trace,
matrix::event::gen_event_id_canonical_json,
trace,
utils::stream::{BroadbandExt, IterStream, TryBroadbandExt}, utils::stream::{BroadbandExt, IterStream, TryBroadbandExt},
warn, warn,
}; };
use conduwuit_service::Services; use conduwuit_service::Services;
use futures::{FutureExt, StreamExt, TryStreamExt}; use futures::{FutureExt, StreamExt, TryStreamExt};
use ruma::{ use ruma::{
CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, ServerName, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, RoomId, ServerName, UserId,
api::federation::membership::create_join_event, api::federation::membership::create_join_event,
assign, assign,
events::{ events::{
StateEventType, TimelineEventType, TimelineEventType,
room::member::{MembershipState, RoomMemberEventContent}, 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")] #[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( async fn create_join_event(
services: &Services, services: &Services,
origin: &ServerName, origin: &ServerName,
room_id: &RoomId, room_id: &RoomId,
pdu: &RawJsonValue, event_id: &EventId,
room_version_rules: &RoomVersionRules,
mut pdu: CanonicalJsonObject,
omit_members: bool, omit_members: bool,
) -> Result<create_join_event::v2::RoomState> { ) -> Result<create_join_event::v2::RoomState> {
if !services.rooms.metadata.exists(room_id).await { if !services.rooms.metadata.exists(room_id).await {
@@ -43,6 +45,7 @@ async fn create_join_event(
{ {
info!( info!(
origin = origin.as_str(), origin = origin.as_str(),
room_id = %room_id,
"Refusing to serve send_join for room we aren't participating in" "Refusing to serve send_join for room we aren't participating in"
); );
return Err!(Request(NotFound("This server is not participating in that room."))); return Err!(Request(NotFound("This server is not participating in that room.")));
@@ -64,94 +67,25 @@ async fn create_join_event(
.await .await
.map_err(|e| err!(Request(NotFound(error!("Room has no state: {e}")))))?; .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 let sender = pdu
// hashes checks .get("sender")
trace!("Getting room version"); .and_then(|v| v.as_str())
let room_version = services.rooms.state.get_room_version(room_id).await?; .map(UserId::parse)
let room_version_rules = room_version.rules().unwrap(); .and_then(Result::ok)
.expect("sender was already validated");
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 content: RoomMemberEventContent = serde_json::from_value( let content: RoomMemberEventContent = serde_json::from_value(
value pdu.get("content")
.get("content")
.ok_or_else(|| err!(Request(BadJson("Event missing content property"))))? .ok_or_else(|| err!(Request(BadJson("Event missing content property"))))?
.clone() .clone()
.into(), .into(),
) )
.map_err(|e| err!(Request(BadJson(warn!("Event content is empty or invalid: {e}")))))?; .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 { if let Some(authorising_user) = content.join_authorized_via_users_server {
use ruma::RoomVersionId::*; if !room_version_rules.authorization.restricted_join_rule {
if matches!(room_version, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
return Err!(Request(InvalidParam( 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." 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." they cannot authorise your join."
))); )));
} }
if !super::user_can_perform_restricted_join(services, &sender, room_id).await? {
if !super::user_can_perform_restricted_join(services, &state_key, room_id).await? {
return Err!(Request(UnableToAuthorizeJoin( return Err!(Request(UnableToAuthorizeJoin(
"Joining user did not pass restricted room's rules." "Joining user did not pass restricted room's rules."
))); )));
@@ -183,7 +116,7 @@ async fn create_join_event(
services services
.server_keys .server_keys
.hash_and_sign_event(&mut value, &room_version_rules) .hash_and_sign_event(&mut pdu, room_version_rules)
.map_err(|e| { .map_err(|e| {
err!(Request(InvalidParam(warn!("Failed to sign send_join event: {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 let pdu_id = services
.rooms .rooms
.event_handler .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() .boxed()
.await? .await?
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?; .ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
@@ -220,8 +153,7 @@ async fn create_join_event(
.iter() .iter()
.try_stream() .try_stream()
.broad_filter_map(|event_id| async move { .broad_filter_map(|event_id| async move {
if omit_members { if omit_members && let Ok(e) = event_id.as_ref() {
if let Ok(e) = event_id.as_ref() {
let pdu = services.rooms.timeline.get_pdu(e).await; let pdu = services.rooms.timeline.get_pdu(e).await;
if pdu.is_ok_and(|p| *p.kind() == TimelineEventType::RoomMember) { if pdu.is_ok_and(|p| *p.kind() == TimelineEventType::RoomMember) {
trace!("omitting member event {e:?} from returned state"); trace!("omitting member event {e:?} from returned state");
@@ -229,7 +161,6 @@ async fn create_join_event(
return None; return None;
} }
} }
}
Some(event_id) Some(event_id)
}) })
.broad_and_then(|event_id| services.rooms.timeline.get_pdu_json(event_id)) .broad_and_then(|event_id| services.rooms.timeline.get_pdu_json(event_id))
@@ -289,7 +220,7 @@ async fn create_join_event(
Ok(assign!(create_join_event::v2::RoomState::new(), { Ok(assign!(create_join_event::v2::RoomState::new(), {
auth_chain, auth_chain,
state, state,
event: to_raw_value(&CanonicalJsonValue::Object(value)).ok(), event: to_raw_value(&CanonicalJsonValue::Object(pdu)).ok(),
members_omitted: omit_members, members_omitted: omit_members,
servers_in_room, servers_in_room,
})) }))
@@ -309,22 +240,43 @@ pub(crate) async fn create_join_event_v2_route(
return Err!(Request(Forbidden("Server is banned on this homeserver."))); return Err!(Request(Forbidden("Server is banned on this homeserver.")));
} }
if let Some(server) = body.room_id.server_name() { let room_version = services.rooms.state.get_room_version(&body.room_id).await?;
if services.moderation.is_remote_server_forbidden(server) { let create_event = services
warn!( .rooms
"Server {} tried joining room ID {} through us which has a server name that is \ .state_accessor
globally forbidden. Rejecting.", .get_room_create_event(&body.room_id)
body.identity, &body.room_id, .await;
); let room_version_rules = room_version.rules().unwrap();
return Err!(Request(Forbidden(warn!(
"Room ID server name {server} is banned on this homeserver." 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 now = Instant::now();
let room_state = let room_state = create_join_event(
create_join_event(&services, &body.identity, &body.room_id, &body.pdu, body.omit_members) &services,
&body.identity,
&body.room_id,
&body.event_id,
&room_version_rules,
value,
body.omit_members,
)
.boxed() .boxed()
.await?; .await?;
info!( info!(
+29 -97
View File
@@ -1,21 +1,11 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{Err, Event, Result, debug_info, err, matrix::pdu::PduEvent, warn};
Err, Result, err, info,
matrix::{event::gen_event_id_canonical_json, pdu::PduEvent},
warn,
};
use futures::FutureExt; use futures::FutureExt;
use ruma::{ use ruma::{
OwnedUserId, api::federation::membership::create_knock_event, events::room::member::MembershipState,
api::federation::membership::create_knock_event,
events::{
StateEventType,
room::member::{MembershipState, RoomMemberEventContent},
},
serde::JsonObject,
}; };
use crate::Ruma; use crate::{Ruma, server::utils::validate_any_membership_event};
/// # `PUT /_matrix/federation/v1/send_knock/{roomId}/{eventId}` /// # `PUT /_matrix/federation/v1/send_knock/{roomId}/{eventId}`
/// ///
@@ -33,18 +23,7 @@ pub(crate) async fn create_knock_event_v1_route(
forbidden. Rejecting.", forbidden. Rejecting.",
body.identity, &body.room_id, 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 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.")));
}
} }
if !services.rooms.metadata.exists(&body.room_id).await { 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) .server_in_room(services.globals.server_name(), &body.room_id)
.await .await
{ {
info!( debug_info!(
origin = body.identity.as_str(), origin = body.identity.as_str(),
room_id = %body.room_id,
"Refusing to serve send_knock for room we aren't participating in" "Refusing to serve send_knock for room we aren't participating in"
); );
return Err!(Request(NotFound("This server is not participating in that room."))); return Err!(Request(NotFound("This server is not participating in that room.")));
@@ -72,88 +52,40 @@ pub(crate) async fn create_knock_event_v1_route(
.await?; .await?;
let room_version = services.rooms.state.get_room_version(&body.room_id).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(); let room_version_rules = room_version.rules().unwrap();
if !room_version_rules.authorization.knocking { if !room_version_rules.authorization.knocking {
return Err!(Request(Forbidden("Room version does not support 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) let (mut event, target_membership, sender, target) = validate_any_membership_event(
else { &services,
// Event could not be converted to canonical json &body.pdu,
return Err!(Request(InvalidParam("Could not convert event to canonical json."))); &room_version_rules,
}; create_event.event_id().to_owned(),
body.room_id.clone(),
let event_type: StateEventType = serde_json::from_value( body.event_id.clone(),
value
.get("type")
.ok_or_else(|| err!(Request(InvalidParam("Event has no event type."))))?
.clone()
.into(),
) )
.map_err(|e| err!(Request(InvalidParam("Event has invalid event type: {e}"))))?;
if event_type != StateEventType::RoomMember {
return Err!(Request(InvalidParam(
"Not allowed to send non-membership state event to knock endpoint.",
)));
}
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?; .await?;
// check if origin server is trying to send for another server if target_membership != MembershipState::Knock {
return Err!(Request(InvalidParam("Invalid membership (expected `knock`)")));
}
if sender.server_name() != body.identity { 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( event.insert("event_id".to_owned(), body.event_id.as_str().into());
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}"))))?;
if state_key != sender { let pdu = PduEvent::from_id_val(&body.event_id, event.clone())
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())
.map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?; .map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
let mutex_lock = services let mutex_lock = services
@@ -166,7 +98,7 @@ pub(crate) async fn create_knock_event_v1_route(
let pdu_id = services let pdu_id = services
.rooms .rooms
.event_handler .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() .boxed()
.await? .await?
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?; .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 let knock_room_state = services
.rooms .rooms
.state .state
.summary_stripped(&pdu, &body.room_id, &sender) .summary_stripped(&pdu, &body.room_id, &sender, true)
.await; .await;
Ok(create_knock_event::v1::Response::new(knock_room_state)) Ok(create_knock_event::v1::Response::new(knock_room_state))
+31 -104
View File
@@ -1,18 +1,11 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Result, err, info, matrix::event::gen_event_id_canonical_json}; use conduwuit::{Err, Result, debug_info, err};
use conduwuit_service::Services;
use futures::FutureExt; use futures::FutureExt;
use ruma::{ use ruma::{
OwnedRoomId, OwnedUserId, RoomId, ServerName, api::federation::membership::create_leave_event, events::room::member::MembershipState,
api::federation::membership::create_leave_event,
events::{
StateEventType,
room::member::{MembershipState, RoomMemberEventContent},
},
}; };
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}` /// # `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>, State(services): State<crate::State>,
body: Ruma<create_leave_event::v2::Request>, body: Ruma<create_leave_event::v2::Request>,
) -> Result<create_leave_event::v2::Response> { ) -> Result<create_leave_event::v2::Response> {
create_leave_event(&services, &body.identity, &body.room_id, &body.pdu).await?; let room_id = body.room_id.as_ref();
let origin = &body.identity;
Ok(create_leave_event::v2::Response::new())
}
async fn create_leave_event(
services: &Services,
origin: &ServerName,
room_id: &RoomId,
pdu: &RawJsonValue,
) -> Result {
if !services.rooms.metadata.exists(room_id).await { if !services.rooms.metadata.exists(room_id).await {
return Err!(Request(NotFound("Room is unknown to this server."))); 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) .server_in_room(services.globals.server_name(), room_id)
.await .await
{ {
info!( debug_info!(
origin = origin.as_str(), 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."))); 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) .acl_check(origin, room_id)
.await?; .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 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 room_version_rules = room_version.rules().unwrap();
let Ok((event_id, value)) = gen_event_id_canonical_json(pdu, &room_version_rules) else { let (value, membership, sender, target) = validate_any_membership_event(
// Event could not be converted to canonical json &services,
return Err!(Request(BadJson("Could not convert event to canonical json."))); &body.pdu,
}; &room_version_rules,
create_event.event_id.clone(),
let event_room_id: OwnedRoomId = serde_json::from_value( body.room_id.clone(),
serde_json::to_value( body.event_id.clone(),
value
.get("room_id")
.ok_or_else(|| err!(Request(BadJson("Event missing room_id property."))))?,
) )
.expect("CanonicalJson is valid json value"),
)
.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 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."
)));
}
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?; .await?;
if membership != MembershipState::Leave {
if sender.server_name() != origin { return Err!(Request(InvalidParam("Invalid membership (expected `leave`)")));
return Err!(Request(BadJson("Not allowed to leave on behalf of another server/user.")));
} }
if sender.server_name() != body.identity {
let state_key: OwnedUserId = serde_json::from_value( return Err!(Request(InvalidParam("Sender belongs to a different server")));
value }
.get("state_key") if sender != target {
.ok_or_else(|| err!(Request(BadJson("Event missing state_key property."))))? return Err!(Request(InvalidParam("Sender does not match state key")));
.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.")));
} }
let mutex_lock = services let mutex_lock = services
@@ -153,7 +78,7 @@ async fn create_leave_event(
let pdu_id = services let pdu_id = services
.rooms .rooms
.event_handler .event_handler
.handle_incoming_pdu(origin, room_id, &event_id, value, true) .handle_incoming_pdu(origin, room_id, &body.event_id, value, false)
.boxed() .boxed()
.await? .await?
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?; .ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
@@ -164,5 +89,7 @@ async fn create_leave_event(
.sending .sending
.send_pdu_room(room_id, &pdu_id) .send_pdu_room(room_id, &pdu_id)
.boxed() .boxed()
.await .await?;
Ok(create_leave_event::v2::Response::new())
} }
+105 -2
View File
@@ -1,7 +1,10 @@
use conduwuit::{Err, Result, is_false}; use conduwuit::{Err, Result, err, is_false};
use conduwuit_service::Services; use conduwuit_service::Services;
use futures::{FutureExt, future::OptionFuture, join}; 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) struct AccessCheck<'a> {
pub(super) services: &'a Services, pub(super) services: &'a Services,
@@ -63,3 +66,103 @@ pub(super) async fn assert(&self) -> Result {
Ok(()) 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
View File
@@ -370,6 +370,27 @@ pub struct Config {
#[serde(default = "default_ip_lookup_strategy")] #[serde(default = "default_ip_lookup_strategy")]
pub ip_lookup_strategy: u8, 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. /// Max request size for file uploads in bytes. Defaults to 20MB.
/// Also limits incoming federated media. /// Also limits incoming federated media.
/// ///
@@ -2421,6 +2442,7 @@ pub struct OauthConfig {
/// legacy authentication will be unable to log in. /// legacy authentication will be unable to log in.
/// ///
/// default: "hybrid" /// default: "hybrid"
#[serde(default)]
compatibility_mode: OAuthMode, compatibility_mode: OAuthMode,
/// display: hidden /// display: hidden
@@ -2476,7 +2498,7 @@ pub struct OidcConfig {
/// automatically fetch the identity provider's metadata from this URL. /// automatically fetch the identity provider's metadata from this URL.
/// Generally you should set this to the base domain your identity provider /// Generally you should set this to the base domain your identity provider
/// runs on. /// runs on.
pub discovery_url: Url, pub discovery_url: String,
/// The OAuth client ID for Continuwuity to use when communicating with the /// The OAuth client ID for Continuwuity to use when communicating with the
/// identity provider. /// identity provider.
+8
View File
@@ -149,6 +149,14 @@ macro_rules! err_log {
#[macro_export] #[macro_export]
#[collapse_debuginfo(yes)] #[collapse_debuginfo(yes)]
macro_rules! err_lev { macro_rules! err_lev {
(debug_info) => {
if $crate::debug::logging() {
$crate::tracing::Level::INFO
} else {
$crate::tracing::Level::DEBUG
}
};
(debug_warn) => { (debug_warn) => {
if $crate::debug::logging() { if $crate::debug::logging() {
$crate::tracing::Level::WARN $crate::tracing::Level::WARN
+7 -1
View File
@@ -17,7 +17,7 @@
}, },
}; };
use conduwuit::{Err, Result, debug, info, warn}; use conduwuit::{Err, Result, debug, err, info, warn};
use rocksdb::{ use rocksdb::{
AsColumnFamilyRef, BoundColumnFamily, DBCommon, DBWithThreadMode, MultiThreaded, AsColumnFamilyRef, BoundColumnFamily, DBCommon, DBWithThreadMode, MultiThreaded,
WaitForCompactOptions, WaitForCompactOptions,
@@ -127,6 +127,12 @@ pub fn current_sequence(&self) -> u64 {
sequence 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 { impl Drop for Engine {
+1 -1
View File
@@ -42,12 +42,12 @@ assets = [
[features] [features]
default = [ default = [
"standard", "standard",
"release_max_log_level",
"ring", "ring",
"bindgen-runtime", # replace with bindgen-static on alpine "bindgen-runtime", # replace with bindgen-static on alpine
] ]
standard = [ standard = [
"brotli_compression", "brotli_compression",
"direct_tls",
"element_hacks", "element_hacks",
"gzip_compression", "gzip_compression",
"io_uring", "io_uring",
+17 -1
View File
@@ -48,6 +48,22 @@ pub(crate) fn build(services: &Arc<Services>) -> Result<(Router, Guard)> {
))] ))]
let layers = layers.layer(compression_layer(server)); 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 services_ = services.clone();
let layers = layers let layers = layers
.layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) .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)), .on_response(DefaultOnResponse::new().level(Level::DEBUG)),
) )
.layer(axum::middleware::from_fn_with_state(Arc::clone(services), request::handle)) .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( .layer(ResponseBodyTimeoutLayer::new(Duration::from_secs(
server.config.client_response_timeout, server.config.client_response_timeout,
))) )))
+2 -2
View File
@@ -219,7 +219,7 @@ fn configure_output_err(mut output: MadSkin) -> MadSkin {
use termimad::{Alignment, CompoundStyle, LineStyle, crossterm::style::Color}; use termimad::{Alignment, CompoundStyle, LineStyle, crossterm::style::Color};
let code_style = CompoundStyle::with_fgbg(Color::AnsiValue(196), Color::AnsiValue(234)); 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 { output.code_block = LineStyle {
left_margin: 0, left_margin: 0,
right_margin: 0, right_margin: 0,
@@ -234,7 +234,7 @@ fn configure_output(mut output: MadSkin) -> MadSkin {
use termimad::{Alignment, CompoundStyle, LineStyle, crossterm::style::Color}; use termimad::{Alignment, CompoundStyle, LineStyle, crossterm::style::Color};
let code_style = CompoundStyle::with_fgbg(Color::AnsiValue(40), Color::AnsiValue(234)); 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 { output.code_block = LineStyle {
left_margin: 0, left_margin: 0,
right_margin: 0, right_margin: 0,
+10 -2
View File
@@ -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 ruma::events::room::message::RoomMessageEventContent;
use tokio::time::{Duration, sleep}; use tokio::time::{Duration, sleep};
@@ -7,10 +9,16 @@
pub(super) const SIGNAL: &str = "SIGUSR2"; pub(super) const SIGNAL: &str = "SIGUSR2";
impl super::Service { 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) { pub(super) async fn console_auto_start(&self) {
#[cfg(feature = "console")] #[cfg(feature = "console")]
if self.services.server.config.admin_console_automatic { 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 // Allow more of the startup sequence to execute before spawning
tokio::task::yield_now().await; tokio::task::yield_now().await;
self.console.start().await; self.console.start().await;

Some files were not shown because too many files have changed in this diff Show More